import {
  getUrlPrefix,
  getUrlVersion,
  handleAssetType,
  handleStorageType
} from "../internal/url/cloudinaryURL";
import {Transformation} from "../transformation/Transformation";
import ICloudConfig from "../config/interfaces/Config/ICloudConfig";
import IURLConfig from "../config/interfaces/Config/IURLConfig";
import IAuthTokenConfig from "../config/interfaces/Config/IAuthTokenConfig";
import URLConfig from "../config/URLConfig";
import {getSDKAnalyticsSignature} from "../sdkAnalytics/getSDKAnalyticsSignature";
import {ITrackedPropertiesThroughAnalytics} from "../sdkAnalytics/interfaces/ITrackedPropertiesThroughAnalytics";

/**
 * This const contains all the valid combination of asset/storage for URL shortening purposes
 * It's exported because it's used in a test, but it's not really shared enough to belong in a separate file
 */
export const SEO_TYPES: Record<string, string> = {
  "image/upload": "images",
  "image/private": "private_images",
  "image/authenticated": "authenticated_images",
  "raw/upload": "files",
  "video/upload": "videos"
};


/**
 * @desc Cloudinary file without a transformation
 * @summary SDK
 * @memberOf SDK
 */
class CloudinaryFile {
  protected assetType: string; // resourceType image/video, determined by the asset type
  protected cloudName: string; // populated from the cloud config
  protected apiKey: string; // populated from  the cloud config
  protected apiSecret: string; // populated from the cloud config
  protected authToken: IAuthTokenConfig; // populated from the cloud config
  protected urlConfig: IURLConfig;

  private version: number | string;
  private publicID: string;
  private extension: string;
  private signature: string;
  private suffix: string;
  private storageType: string; // type upload/private

  constructor(publicID: string, cloudConfig: ICloudConfig = {}, urlConfig?: IURLConfig) {
    this.setPublicID(publicID);
    this.cloudName = cloudConfig.cloudName;
    this.apiKey = cloudConfig.apiKey;
    this.apiSecret = cloudConfig.apiSecret;
    this.authToken = cloudConfig.authToken;
    this.urlConfig = new URLConfig(urlConfig);
  }

  setPublicID(publicID: string): this {
    // PublicID must be a string!
    this.publicID = publicID ? publicID.toString() : '';
    return this;
  }

  setStorageType(newType: string): this {
    this.storageType = newType;
    return this;
  }

  setSuffix(newSuffix: string): this {
    this.suffix = newSuffix;
    return this;
  }

  setSignature(signature: string): this {
    this.signature = signature;
    return this;
  }

  setVersion(newVersion: number | string): this {
    if (newVersion) {
      this.version = newVersion;
    }
    return this;
  }

  setAssetType(newType: string): this {
    if (newType) {
      this.assetType = newType;
    }
    return this;
  }

  sign(): this {
    return this;
  }

  toURL(overwriteOptions: {trackedAnalytics?: Partial<ITrackedPropertiesThroughAnalytics>} = {}): string {
    return this.createCloudinaryURL(null, overwriteOptions.trackedAnalytics);
  }

  /**
   * @description Validate various options before attempting to create a URL
   * The function will throw in case a violation
   * @throws Validation errors
   */
  validateAssetForURLCreation(): void {
    if (typeof this.cloudName === 'undefined') {
      throw 'You must supply a cloudName in either toURL() or when initializing the asset';
    }

    const suffixContainsDot = this.suffix && this.suffix.indexOf('.') >= 0;
    const suffixContainsSlash = this.suffix && this.suffix.indexOf('/') >= 0;

    if (suffixContainsDot || suffixContainsSlash) {
      throw '`suffix`` should not include . or /';
    }
  }



  /**
   * @description return an SEO friendly name for a combination of asset/storage, some examples:
   * * image/upload -> images
   * * video/upload -> videos
   * If no match is found, return `{asset}/{storage}`
   */
  getResourceType(): string {
    const assetType = handleAssetType(this.assetType);
    const storageType = handleStorageType(this.storageType);

    const hasSuffix = !!this.suffix;
    const regularSEOType = `${assetType}/${storageType}`;
    const shortSEOType = SEO_TYPES[`${assetType}/${storageType}`];
    const useRootPath = this.urlConfig.useRootPath;
    const shorten = this.urlConfig.shorten;

    // Quick exit incase of useRootPath
    if (useRootPath) {
      if (regularSEOType === 'image/upload') {
        return ''; // For image/upload we're done, just return nothing
      } else {
        throw new Error(`useRootPath can only be used with assetType: 'image' and storageType: 'upload'. Provided: ${regularSEOType} instead`);
      }
    }

    if (shorten && regularSEOType === 'image/upload') {
      return 'iu';
    }


    if (hasSuffix) {
      if (shortSEOType) {
        return shortSEOType;
      } else {
        throw new Error(`URL Suffix only supported for ${Object.keys(SEO_TYPES).join(', ')}, Provided: ${regularSEOType} instead`);
      }
    }

    // If all else fails, return the regular image/upload combination (asset/storage)
    return regularSEOType;
  }


  getSignature() {
    if (this.signature) {
      return `s--${this.signature}--`;
    } else {
      return '';
    }
  }

  /**
   *
   * @description Creates a fully qualified CloudinaryURL
   * @return {string} CloudinaryURL
   * @throws Validation Errors
   */
  createCloudinaryURL(transformation?: Transformation | string, trackedAnalytics?: Partial<ITrackedPropertiesThroughAnalytics>): string {
    // In accordance with the existing implementation, if no publicID exists we should return nothing.
    if (!this.publicID) {
      return '';
    }

    // Throws if some options are mis-configured
    // See the function for more information on when it throws
    this.validateAssetForURLCreation();

    const prefix = getUrlPrefix(this.cloudName, this.urlConfig);
    const transformationString = transformation ? transformation.toString() : '';
    const version = getUrlVersion(this.publicID, this.version, this.urlConfig.forceVersion);

    const publicID = this.publicID
      // Serialize the publicID, but leave slashes alone.
      // we can't use serializeCloudinaryCharacters because that does both things (, and /)
      .replace(/,/g, '%2C');

    // Resource type is a mixture of assetType, storageType and various URL Configurations
    // Note how `suffix` changes both image/upload (resourceType) and also is appended at the end
    const url = [prefix, this.getResourceType(), this.getSignature(), transformationString, version, publicID, this.suffix]
      .filter((a) => a)
      .join('/');

    if (typeof transformation === 'string') {
      return url;
    } else {
      const safeURL = encodeURI(url)
        .replace(/\?/g, '%3F')
        .replace(/=/g, '%3D');

      // True by default, has to be explicitly set to false to overwrite
      if (this.urlConfig.analytics !== false) {
        return `${safeURL}?_a=${getSDKAnalyticsSignature(trackedAnalytics)}`;
      } else {
        return safeURL;
      }
    }
  }
}

export {CloudinaryFile};