import cloneDeep from "lodash/cloneDeep";
import isEmpty from "lodash/isEmpty";
import forOwn from "lodash/forOwn";
import isNumber from "lodash/isNumber";
import Bugsnag from '@bugsnag/js';
import * as Api from '../services/api';
/* eslint-disable no-unused-expressions */
import { parse } from "node-html-parser";
import { EMAIL } from '../v2Containers/CreativesContainer/constants';

// example cdn url
// https://storage.crm.n.content-cdn.io/cdn-cgi/image/width=100,height=100,quality=75,format=auto/intouch_creative_assets/005710a7-6412-44fe-a19f-e72456b1.jpg

/**
 * regex tests presence of intouch_creative_assets in the url.
 * We are not testing for extensions such as jpg/png because some channels like LINE - IMAGEMAP url doesn't contain extension.
 */
const SPECIFIED = "specified";
const FORMAT_TYPES = { AUTO: "auto" };
const QUALITY_TYPE = {
  DECREASE: "decrease",
  PRESERVE: "preserve",
};
const CREATIVES_CDN_BASE_URL_KEY = 'CREATIVES_CDN_BASE_URL';
const CREATIVES_S3_BUCKET_PATH_KEY = 'CREATIVES_S3_BUCKET_PATH';
const CREATIVES_CDN_QUALITY_CONFIG_KEY = 'CREATIVES_CDN_QUALITY_CONFIG';
const CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX_KEY = 'CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX';
const CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_KEY = 'CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY';
const CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING_KEY = 'CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING';
export const CREATIVES_S3_ASSET_FILESIZES = 'CREATIVES_S3_ASSET_FILESIZES';
export const S3_CDN_MAP_KEY = 'S3_CDN_MAP';

const HTTPS_STR = "https://"
const ALLOWED_EXTENSIONS_STR = ".*(?:jpg|png|jpeg|gif)$"
const ANY_STR = ".*"

/**
 * CHANNEL_CONFIGS maintains the transformation details for each channel (and optionally subchannel ex. FACEBOOK/LINE)
 * In case of SUBCHANNELS, they are nested inside CHANNEL.
 */
const CHANNEL_CONFIGS = {
  EMAIL: {
    transformations: {
      width: SPECIFIED,
      height: SPECIFIED,
      format: FORMAT_TYPES.AUTO,
      quality: QUALITY_TYPE.DECREASE,
    },
  },
  RCS: {
    transformations: {
      width: 1440,
      height: 720,
      quality: QUALITY_TYPE.DECREASE,
    },
  },
  VIBER: {
    transformations: {
      width: 300,
      height: 400,
      quality: QUALITY_TYPE.DECREASE,
    },
  },
  WHATSAPP: {
    transformations: {
      quality: QUALITY_TYPE.DECREASE, //TODO whatsapp itself might reduce quality. To be checked
    },
  },
  MOBILE_PUSH: {
    transformations: {
      quality: QUALITY_TYPE.DECREASE,
    },
  },
  FACEBOOK: {
    IMAGE: {
      transformations: {
        width: 1080,
        height: 1080,
        quality: QUALITY_TYPE.DECREASE,
      },
    }
  },
  LINE: {
    IMAGE: {
      transformations: {
        width: 300,
        height: 400,
        quality: QUALITY_TYPE.DECREASE,
      },
    },
    CARD: {
      transformations: {
        width: 1024,
        height: 1024,
        quality: QUALITY_TYPE.DECREASE,
      },
    },
    RICH_MESSAGE: {
      //Rich Message in Line has Square and Custom templates. 
      //Width (1040px) is same in both whereas height varies. Hence including width only.
      transformations: {
        width: 1040,
        quality: QUALITY_TYPE.DECREASE,
      },
    }
  },
  WECHAT: {
    transformations: {
      quality: QUALITY_TYPE.DECREASE,
    }
  }
};

/**
 * Retrieves the cluster-specific S3 CDN configuration from the given URL based on S3_CDN_MAP.
 *
 * @param {string} url - The URL to check for cluster-specific S3 CDN configuration.
 * @returns {Object} - An object containing the cluster-specific S3 bucket path and CDN base URL.
 */
const getClusterSpecificS3CdnConfigFromUrl = (url) => {
  const S3_CDN_MAP = getLocalStorageItem(S3_CDN_MAP_KEY, true);
  let CREATIVES_S3_BUCKET_PATH;
  let CREATIVES_CDN_BASE_URL;

  Object.entries(S3_CDN_MAP).some(([clusterS3Url, cdnConfigs]) => {
    const {cdn_host, bucket_path} = cdnConfigs;
    const s3UrlRegex = new RegExp(`^https://${clusterS3Url}/${bucket_path}/\.*`);
    const cdnUrlRegex = new RegExp(`^https://${cdn_host}/\.*${bucket_path}/\.*`);

    if (s3UrlRegex.test(url) || cdnUrlRegex.test(url)) {
      CREATIVES_S3_BUCKET_PATH = bucket_path;
      CREATIVES_CDN_BASE_URL = `https://${cdn_host}`;
      return true;
    }
  });
  return { CREATIVES_S3_BUCKET_PATH, CREATIVES_CDN_BASE_URL };
}

/**
 * 
 * @param {string} url : required 
 * @param {number} height : optional | required when height needs to be manually specified for channels such as email.
 * @param {number} width : optional | required when width needs to be manually specified for channels such as email.
 * @param {string} channelName : required 
 * @param {string} channelSubType : optional | required when channel has multiple subtypes
 * 
 * @returns 
 * getCdnUrl : utility function to replace and return s3/cdn url with cdn url.
 * Step 1. Basic validations:
 *  a) If data type of cdn items stored in local storage is incorrect(maybe as a result of manual modification), the received url in param is returned.
 *  b) If cdn local storage items are emtpy then received url in param is returned.
 *  c) If url doesn't match with regex.
 *  d) Channel/Subchannel not present in CHANNEL_CONFIGS
 *  e) No quality and default quality mentioned in CHANNEL_CONFIGS for given channel.
 *  f) If channels quality is non numeric(maybe as a result of manual modification) then quality=100 is returned.
 * 
 * Step 2. The CHANNEL_CONFIGS determine the applicableTransformations.
 * Step 3. If applicable transformations are present `CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX` is appended to cdn url and transformation properties are added.
 * Step 4. The original s3 file name is appened at the end of the url to get complete cdn url and value is returned.
 * 
 * Error handling:
 * In case of any unknown exception, url received in param is returned.
 */
export const getCdnUrl = ({
  url,
  height,
  width,
  channelName: receivedChannelName,
  channelSubType: receivedChannelSubType,
  emailOverrideQuality = null,
}) => {
  try{
    let cdnUrl;
    const channelName = receivedChannelName?.toUpperCase();
    const channelSubType = receivedChannelSubType?.toUpperCase();

    const CREATIVES_CDN_QUALITY_CONFIG = getLocalStorageItem(CREATIVES_CDN_QUALITY_CONFIG_KEY, true);
    const CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX = getLocalStorageItem(CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX_KEY);

    //Get cluster specific S3 CDN configuration from the given URL based on S3_CDN_MAP
    let { CREATIVES_S3_BUCKET_PATH, CREATIVES_CDN_BASE_URL } = getClusterSpecificS3CdnConfigFromUrl(url);

    //Basic validations on data stored in local storage.
    if (
      typeof CREATIVES_CDN_QUALITY_CONFIG !== "object" ||
      Array.isArray(CREATIVES_CDN_QUALITY_CONFIG) ||
      CREATIVES_CDN_QUALITY_CONFIG === null ||
      !CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX ||
      !CREATIVES_S3_BUCKET_PATH ||
      !CREATIVES_CDN_BASE_URL
    )
      return url;

    //remove all leading and trailing forward slash with empty string, if leading/trailing forward slash is present in node env by mistake then url will not be correct hence removing them.
    CREATIVES_S3_BUCKET_PATH = CREATIVES_S3_BUCKET_PATH?.replace(/^\/|\/$/g, '');

    const QUALITY = CREATIVES_CDN_QUALITY_CONFIG?.[channelName] || CREATIVES_CDN_QUALITY_CONFIG?.DEFAULT;
    const [_, assetKey] = url.split(CREATIVES_S3_BUCKET_PATH);
  
    const regexWithExtension = new RegExp(HTTPS_STR + ANY_STR + CREATIVES_S3_BUCKET_PATH + ANY_STR + ALLOWED_EXTENSIONS_STR)
    const regexWithoutExtension = new RegExp(HTTPS_STR + ANY_STR + CREATIVES_S3_BUCKET_PATH + ANY_STR);
    
    if (
      !regexWithoutExtension.test(url) ||
      !CHANNEL_CONFIGS?.[channelName] ||
      (channelSubType && !CHANNEL_CONFIGS?.[channelName]?.[channelSubType]) ||
      !QUALITY ||
      !assetKey
    )
      return url;
    
    let skipTransformations = true;
    let applicableTransformations;

    //Skip transformations if extensions are not proper. Else consider transformations if available.
    if(regexWithExtension.test(url)){
      skipTransformations = false;
    }
  
    //Skip trasformations if transformation properties are not present in channel/subchannel.
    if (channelSubType) {
      applicableTransformations =
        CHANNEL_CONFIGS?.[channelName]?.[channelSubType]?.transformations;
    } else {
      applicableTransformations = CHANNEL_CONFIGS?.[channelName]?.transformations;
    }

    cdnUrl = `${CREATIVES_CDN_BASE_URL}`;    
  
    if (!isEmpty(applicableTransformations) && !skipTransformations) {
      cdnUrl += `/${CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX}/`;
  
      forOwn(applicableTransformations, (transformationParamVal, transformationParam)=>{
        try {
          switch (transformationParam) {
            case "height":
              if (transformationParamVal === SPECIFIED && height) {
                cdnUrl += `${transformationParam}=${height},`;
              } else if(transformationParamVal !== SPECIFIED && isNumber(transformationParamVal)) {
                cdnUrl += `${transformationParam}=${transformationParamVal},`;
              }
              break;
            case "width":
              if (transformationParamVal === SPECIFIED && width) {
                cdnUrl += `${transformationParam}=${width},`;
              } else if(transformationParamVal !== SPECIFIED && isNumber(transformationParamVal)) {
                cdnUrl += `${transformationParam}=${transformationParamVal},`;
              }
              break;
            case "format":
              if(transformationParamVal){
                cdnUrl += `${transformationParam}=${transformationParamVal},`;
              }
              break;
            case "quality":
              if (transformationParamVal === QUALITY_TYPE?.DECREASE && QUALITY && typeof QUALITY === "number") {
                if (
                  emailOverrideQuality &&
                  typeof emailOverrideQuality === "number"
                ) {
                  cdnUrl += `${transformationParam}=${emailOverrideQuality},`;
                } else {
                  cdnUrl += `${transformationParam}=${QUALITY},`;
                }
              } else {
                cdnUrl += `${transformationParam}=100,`;
              }
              break;
          }
        } catch (e) {
          Bugsnag.leaveBreadcrumb("some error occured while applying transformation")
          Bugsnag.notify(e, (event) => {
            event.severity = "error";
          });
        }
      })
    }
    cdnUrl += `/${CREATIVES_S3_BUCKET_PATH}${assetKey}`;
    return cdnUrl;
  }catch(e){
    Bugsnag.leaveBreadcrumb("Some exception occurred in getCdnUrl");
    Bugsnag.notify(e, (event) => {
      event.severity = "error";
    });
    return url;
  }
};

/**
 *
 * @param {string} htmlContents accepts htmlContents - strigified.
 * @returns {Promise} htmlContents with updated urls in image tag.
 * 
 * If email quality is to be overriden. We first iterate on all images and determine their respective quality.
 */
export const updateImagesInHtml = async (htmlContents) => {
  if(!htmlContents || typeof htmlContents !== "string"){
    return htmlContents;
  } 
  const copiedHtmlContents = htmlContents;
  try {
    const root = parse(htmlContents, { comment: true });
    const isEmailQualityOverridden = getLocalStorageItem(CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_KEY);
    let urlQualityMappings;
    if(isEmailQualityOverridden){
      const urlList = root?.querySelectorAll("img")?.map(element=>element.getAttribute("src"));
      urlQualityMappings = await getUrlsToQualityMapping(urlList);
    }
    root?.querySelectorAll("img")?.forEach((element) => {
      const imageSrc = element.getAttribute("src");
      const height = element.getAttribute("height");
      const width = element.getAttribute("width");

      let emailOverrideQuality = null;
      if (isEmailQualityOverridden) {
        const tempQuality = urlQualityMappings?.[imageSrc];
        if (tempQuality && tempQuality !== -1) {
          emailOverrideQuality = tempQuality;
        }
      }

      const newUrl = getCdnUrl({
        url: imageSrc,
        height,
        width,
        channelName: EMAIL,
        emailOverrideQuality,
      });
      element.setAttribute("src", newUrl);
    });
    return root.toString();
  } catch (e) {
    Bugsnag.leaveBreadcrumb("An error occured while updating images in html")
    Bugsnag.notify(e, (event) => {
      event.severity = "error";
    });
    return copiedHtmlContents; //return received input directly in case an exception is thrown
  }
};

/**
 *
 * @param {*} contents
 * @returns
 * Transforms the email template's html by images replacing the s3 url / cdn url with cdn url.
 */
export function transformEmailTemplates(contents){
  const contentsCopy = cloneDeep(contents);
  try {
    const base = contentsCopy?.versions?.base;
    const languages = base?.selectedLanguages;
    let promises = [];
    languages?.forEach((lang) => {
      const htmlContents = base?.[lang]?.["template-content"];
      promises.push(
        updateImagesInHtml(htmlContents).then(newHtmlContents=>{
          base[lang]["template-content"] = newHtmlContents;
        })
      )
    });
    return Promise.all(promises).then(_=>contentsCopy).catch(e=>{
      Bugsnag.leaveBreadcrumb(
        "Some error has occured in transforming email templates while setting newHtmlContents. Returning without transformation."
      );
      Bugsnag.notify(e, (event) => {
        event.severity = "error";
      });
      return contents;
    });
  } catch (e) {
    Bugsnag.leaveBreadcrumb(
      "Some error has occured while transforming email templates, probably input is not in proper format"
    );
    Bugsnag.notify(e, (event) => {
      event.severity = "error";
    });
    return contents; //return received input directly in case an exception is thrown
  }
};


/**
 * 
 * @param {*} key 
 * @param {*} parse 
 * @returns Localstorage item with mentioned key. Parses the value if second param `parse` is true.
 */
export function getLocalStorageItem(key, parse = false){
  const val = localStorage.getItem(key);
  if(parse){
    return JSON.parse(val);
  }
  return val;
}

/**
 * Removes specified key from local storage.
 */
export function removeLocalStorageItem(key) {
  localStorage.removeItem(key);
  return;
}

/**
 * Util function. Removes all cdn related local storage items.
 */
export function removeAllCdnLocalStorageItems() {
  try{
    removeLocalStorageItem(CREATIVES_CDN_BASE_URL_KEY);
    removeLocalStorageItem(CREATIVES_CDN_QUALITY_CONFIG_KEY);
    removeLocalStorageItem(CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX_KEY);
    removeLocalStorageItem(CREATIVES_S3_BUCKET_PATH_KEY);
    removeLocalStorageItem(CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_KEY);
    removeLocalStorageItem(CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING_KEY);
    removeLocalStorageItem(S3_CDN_MAP_KEY);
  }catch(e){
    Bugsnag.leaveBreadcrumb("some error occured while deleting all keys for cdn");
    Bugsnag.notify(e, (event) => {
      event.severity = "error";
    });
  }
}

/**
 * Safe JSON.parse — returns fallback if value is empty or malformed.
 * Used to parse the JSON-string env vars (qualityCfg, mapping, s3CdnMap) injected via window.APP_ENV.
 */
function safeJsonParse(value, fallback) {
  if (!value) return fallback;
  try {
    return JSON.parse(value);
  } catch (e) {
    return fallback;
  }
}

/**
 * Reads CDN config from window.APP_ENV (injected at container startup by entrypoint.sh)
 * and populates localStorage via saveCdnConfigs — same shape as the legacy API response.
 *
 * Returns true if env config was found and applied, false otherwise (saga falls back to API).
 *
 * In deployed containers, window.APP_ENV is populated → no API call. In local dev
 * (`npm start`), window.APP_ENV is empty → returns false → saga falls back to the API
 * (legacy path, retained for the rollout window).
 *
 * Closes the VAPT info-disclosure surface (CAP-183204) by eliminating the
 * /getCdnTransformationConfig API response in production.
 */
export function initCdnConfigFromEnv() {
  try {
    const env = (typeof window !== 'undefined' && window.APP_ENV) || {};

    // All four fields are required to construct a CDN URL. If any is missing,
    // partial config would silently make every getCdnUrl call fall through to the
    // raw S3 URL. Return false so the saga falls back to the API (which may still
    // have the values during the cluster-by-cluster rollout window).
    if (!env.CDN_HOSTNAME
        || !env.CDN_IMG_TRANSFORMATION_URL_SUFFIX
        || !env.CREATIVE_ASSETS_BUCKET_PATH
        || !env.S3_CDN_MAP) {
      return false;
    }

    // Parse and validate S3_CDN_MAP before proceeding; malformed or empty map
    // means CDN lookups would silently fail, so fall back to the API instead.
    const parsedS3CdnMap = safeJsonParse(env.S3_CDN_MAP, null);
    if (
      !parsedS3CdnMap
      || typeof parsedS3CdnMap !== 'object'
      || Array.isArray(parsedS3CdnMap)
      || !Object.keys(parsedS3CdnMap).length
    ) {
      return false;
    }

    saveCdnConfigs({
      hostname: env.CDN_HOSTNAME,
      transformationUrlSuffix: env.CDN_IMG_TRANSFORMATION_URL_SUFFIX,
      bucketPath: env.CREATIVE_ASSETS_BUCKET_PATH,
      qualityCfg: safeJsonParse(env.CDN_IMG_QUALITY_CFG, {}),
      overrideEmailQuality: env.OVERRIDE_IMAGE_QUALITY === 'true',
      overrideEmailQualityMapping: safeJsonParse(env.IMAGE_QUALITY_MAPPING, []),
      s3CdnMap: parsedS3CdnMap,
    });
    return true;
  } catch (e) {
    Bugsnag.leaveBreadcrumb('initCdnConfigFromEnv failed to apply window.APP_ENV');
    Bugsnag.notify(e, (event) => {
      event.severity = 'error';
    });
    return false;
  }
}

/**
 *
 * @param {*} configsResponse
 * This util function saves the getCdnConfigs response into the local storage.
 * The following response items are mapped into the following keys in local storage:
 * hostname -> CREATIVES_CDN_BASE_URL
 * qualityCfg ->CREATIVES_CDN_QUALITY_CONFIG
 * transformationUrlSuffix -> CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX
 *
 * 1. If configsReponse is empty. All above mentioned keys are deleted from localstorage.
 * 2. If any one of the above keys is missing. The respective keys are deleted from localstorage.
 * 3. Else it is saved into the localstorage.
 */
export function saveCdnConfigs(configsResponse) {
  try {
    //If no response from api, then remove localStorage items if they were set previously
    if (isEmpty(configsResponse)) {
      removeAllCdnLocalStorageItems();
      return;
    }

    if (configsResponse?.hostname) {
      localStorage.setItem(
        CREATIVES_CDN_BASE_URL_KEY,
        configsResponse?.hostname
      );
    } else {
      removeLocalStorageItem(CREATIVES_CDN_BASE_URL_KEY);
    }

    if (configsResponse?.bucketPath) {
      localStorage.setItem(
        CREATIVES_S3_BUCKET_PATH_KEY,
        configsResponse?.bucketPath
      );
    } else {
      removeLocalStorageItem(CREATIVES_S3_BUCKET_PATH_KEY);
    }

    if (!isEmpty(configsResponse?.qualityCfg)) {
      const qualityString = JSON.stringify(configsResponse?.qualityCfg);
      localStorage.setItem(CREATIVES_CDN_QUALITY_CONFIG_KEY, qualityString);
    } else {
      removeLocalStorageItem(CREATIVES_CDN_QUALITY_CONFIG_KEY);
    }

    if (configsResponse?.transformationUrlSuffix) {
      localStorage.setItem(
        CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX_KEY,
        configsResponse?.transformationUrlSuffix
      );
    } else {
      removeLocalStorageItem(CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX_KEY);
    }

    if (
      configsResponse?.overrideEmailQuality &&
      !isEmpty(configsResponse?.overrideEmailQualityMapping)
    ) {
      localStorage.setItem(
        CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_KEY,
        true
      );

      const overrideEmailQualityMappingString = JSON.stringify(
        configsResponse?.overrideEmailQualityMapping
      );
      localStorage.setItem(
        CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING_KEY,
        overrideEmailQualityMappingString
      );
    } else {
      removeLocalStorageItem(
        CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_KEY
      );
      removeLocalStorageItem(
        CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING_KEY
      );
    }

    if (configsResponse?.s3CdnMap) {
      const strigifiedS3CdnMap = JSON.stringify(configsResponse?.s3CdnMap);
      localStorage.setItem(S3_CDN_MAP_KEY, strigifiedS3CdnMap);
    } else {
      removeLocalStorageItem(S3_CDN_MAP_KEY);
    }
  } catch (e) {
    Bugsnag.leaveBreadcrumb("some error while setting localstorage cdn configs");
    Bugsnag.notify(e, (event) => {
      event.severity = "error";
    });
  }
}

/**
 * 
 * @param {string} url 
 * @param {number} fileSize 
 * Stores/Updates the S3 url file size into the window object with key CREATIVES_S3_ASSET_FILESIZES.
 */
export function storeS3FileSizeDetails(url, fileSize) {
  if (!url || typeof url !== "string") return;
  if (!fileSize || typeof fileSize !== "number") return;
  if (
    window?.[CREATIVES_S3_ASSET_FILESIZES] &&
    Object.isFrozen(window?.[CREATIVES_S3_ASSET_FILESIZES])
  ) {
    return;
  }

  if (!window?.[CREATIVES_S3_ASSET_FILESIZES]) {
    window[CREATIVES_S3_ASSET_FILESIZES] = {};
  }
  window[CREATIVES_S3_ASSET_FILESIZES][url] = fileSize;
}

export function getKeyFromS3Url(url) {
  if (typeof url !== "string") return url;

  const { CREATIVES_S3_BUCKET_PATH }  = getClusterSpecificS3CdnConfigFromUrl(url);
  const bucketPathWithSlash = CREATIVES_S3_BUCKET_PATH + "/";
  const [, key] = url.split(bucketPathWithSlash);
  return key;
}


/**
 * 
 * @param {string} url 
 * @returns {
 *  isValid,
 *  isCdnUrl,
 * }
 * where 'isValid' means it is either s3 or cdn url.
 * 'isCdnUrl' refers that it is cdn url.
 */
export function checkValidUrl(url){
  const { CREATIVES_S3_BUCKET_PATH, CREATIVES_CDN_BASE_URL }  = getClusterSpecificS3CdnConfigFromUrl(url);

  const regexWithExtension = new RegExp(HTTPS_STR + ANY_STR + CREATIVES_S3_BUCKET_PATH + ANY_STR + ALLOWED_EXTENSIONS_STR)
  
  //It is S3 url if tests with regex but doesn't contain cdn base url.
  
  const isValidUrl = regexWithExtension.test(url);
  const isCdnUrl = url.includes(CREATIVES_CDN_BASE_URL);
  const isS3Url = isValidUrl && !isCdnUrl;

  return {
    isValidUrl,
    isS3Url,
    isCdnUrl,
  }
}

/**
 * 
 * @param {string} url | Accepts cdn url
 * @returns quality param value by extracting it from the url
 */
export function getCdnEmailImageQuality(url) {
  try {
    const regex = /quality=\d+/;
    if (!regex.test(url)) {
      return -1;
    }

    const qualityValString = url
      ?.match(regex)
      ?.join("")
      ?.replace("quality=", "");

    const qualityVal = Number(qualityValString);

    return qualityVal ? qualityVal : -1;
  } catch (e) {
    return -1;
  }
}

/**
 * 
 * @param {string} url 
 * Accepts url (either s3 or cdn) and returns the quality override used incase of email.
 * 1. If it is s3 url, it returns quality inversly proportional to its filesize.
 * 2. If it is cdn url, it extracts quality from url and returns the same.
 */
export function getS3EmailImageQuality(url){
  if(!url || typeof url !== "string") return -1;

  const fileSize = getFizeSizeFromUrl(url);

  if(!fileSize) return;

  return getEmailImageOverrideQuality(fileSize);
}

export function getS3EmailImageQualityMapping(
  urlKeysToFileSizeMapping,
  s3UrlList
) {
  if (typeof urlKeysToFileSizeMapping !== "object" || !urlKeysToFileSizeMapping)
    return {};

  const urlToImageQualityMapping = {};
  forOwn(urlKeysToFileSizeMapping, function (val, key, obj) {
    const s3Url = s3UrlList?.find((url) => url?.includes(key));
    if (s3Url) {
      urlToImageQualityMapping[s3Url] = getEmailImageOverrideQuality(val);
    }
  });
  return urlToImageQualityMapping;
}

/**
 * 
 * @param {string} url 
 * @returns file size in KB for the corresponding asset url
 */
export function getFizeSizeFromUrl(url){
  if(!window?.[CREATIVES_S3_ASSET_FILESIZES]?.[url]) return;

  const fileSizeInBytes = window?.[CREATIVES_S3_ASSET_FILESIZES]?.[url];

  return fileSizeInBytes / 1024;   //in KB
}

/**
 * 
 * @param {*} fileSize 
 * @returns quality to be used for email. Returns -1 in case mapping is not found.
 * Using linear search here since number of quality ranges will be around 5-7. Binary search will not benefit much. Since number of comparisons for 8 ranges will still be ~3.
 */
export function getEmailImageOverrideQuality(fileSize) {
  try {
    const CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING =
      getLocalStorageItem(
        CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING_KEY,
        true
      );

    if (isEmpty(CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING))
      return -1;

    let numRange = CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING?.length;
    for (let i = 0; i < numRange; i++) {
      if (
        CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING?.[i]?.[0] >=
        fileSize
      ) {
        return CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING?.[i]?.[1];
      }
    }

    return (
      CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING?.[
        numRange - 1
      ]?.[1] || -1
    );
  } catch (e) {
    return -1;
  }
}

/**
 * 
 * @param {array} urlList ex ['1.jpg','2.jpg'] // these can be any urls (s3, cdn or others).
 * This function accepts list of urls(can be s3, cdn or other).
 * This function returns quality corresponding to the url.
 * 
 * If it is cdn url. It extracts quality from cdn url.
 * If it is s3 url:
 *  a. If image is selected from gallery or image is manuall uploaded(not zip upload), 
 *     then we have file size information in window object. We deterermine quality from it.
 *  b. If not in a, we append the s3 url into nonSelectedS3Urls
 * 
 * If we have elements in nonSelectedS3Urls array. We make an api call to get file size details and then determine quality.
 * 
 * We return the quality mapping, key being url and value being quality
 * 
 */
export async function getUrlsToQualityMapping(urlList){
  if(!urlList?.length) return {};

  const urlQualityMapping = {};

  const nonSelectedS3Urls = []; //we do not know file sizes of these s3 urls yet.

  urlList?.forEach(url=>{
    const {isS3Url, isCdnUrl} = checkValidUrl(url);

    if (isCdnUrl){
      urlQualityMapping[url] = getCdnEmailImageQuality(url);
      return;
    }
    if(isS3Url){
      const s3FileQuality = getS3EmailImageQuality(url);
      if(s3FileQuality){
        urlQualityMapping[url] = s3FileQuality;
        return;
      }
      if(!s3FileQuality){
        nonSelectedS3Urls.push(url)
      }
    }
  })

  if(nonSelectedS3Urls?.length){
    const urlKeyList = nonSelectedS3Urls?.map(url=>getKeyFromS3Url(url));
    let nonSelectedS3UrlKeyMapping = await getS3FileSizesFromUrl(urlKeyList);
    const nonSelectedS3UrlQualityMapping = getS3EmailImageQualityMapping(
      nonSelectedS3UrlKeyMapping,
      nonSelectedS3Urls
    );
    return {...urlQualityMapping,...nonSelectedS3UrlQualityMapping};
  }
  return urlQualityMapping;
}

/**
 * This function accepts an array consisting of s3 urls keys(string) as input.
 * It returns url mapping with key being s3 url and mapping being file size in kb. 
 * 
 */
export async function getS3FileSizesFromUrl(keyList) {
  if(!keyList?.length){
    return {}
  }
  try{
    let data = await Api.getS3UrlFileSizes({ keys: keyList });
    if (data?.status?.code === 200 && data?.success && data?.response) {
      const result = data?.response;
      const urlMapping = {};
      for (let url of Object.keys(result)) {
        urlMapping[url] = result[url];
      }
      return urlMapping;
    } else {
      return {};
    }
  }catch(e){
    return {};
  }
}