import {Services} from '#service';

import {dev} from '#utils/log';

import {getSourceOrigin} from '../url';

/**
 * The Client ID service key.
 * @const @private {string}
 */
const SERVICE_KEY_ = 'AIzaSyDKtqGxnoeIqVM33Uf7hRSa3GJxuzR7mLc';

/**
 * Tag for debug logging.
 * @const @private {string}
 */
const TAG_ = 'CacheCidApi';

/**
 * The URL for the cache-served CID API.
 * @const @private {string}
 */
const CACHE_API_URL = 'https://ampcid.google.com/v1/cache:getClientId?key=';

/**
 * The XHR timeout in milliseconds for requests to the CID API.
 * @const @private {number}
 */
const TIMEOUT_ = 30000;

/**
 * Exposes CID API for cache-served pages without a viewer.
 */
export class CacheCidApi {
  /** @param {!./ampdoc-impl.AmpDoc} ampdoc */
  constructor(ampdoc) {
    /** @private {!./ampdoc-impl.AmpDoc} */
    this.ampdoc_ = ampdoc;

    /** @private {!./viewer-interface.ViewerInterface} */
    this.viewer_ = Services.viewerForDoc(this.ampdoc_);

    /** @private {?Promise<?string>} */
    this.publisherCidPromise_ = null;

    /** @private {!./timer-impl.Timer} */
    this.timer_ = Services.timerFor(this.ampdoc_.win);
  }

  /**
   * Returns true if the page is embedded in CCT and is served by a proxy.
   * @return {boolean}
   */
  isSupported() {
    return this.viewer_.isCctEmbedded() && this.viewer_.isProxyOrigin();
  }

  /**
   * Returns scoped CID retrieved from the Viewer.
   * @param {string} scope
   * @return {!Promise<?string>}
   */
  getScopedCid(scope) {
    if (!this.viewer_.isCctEmbedded()) {
      return /** @type {!Promise<?string>} */ (Promise.resolve(null));
    }

    if (!this.publisherCidPromise_) {
      const url = CACHE_API_URL + SERVICE_KEY_;
      this.publisherCidPromise_ = this.fetchCid_(url);
    }

    return this.publisherCidPromise_.then((publisherCid) => {
      return publisherCid ? this.scopeCid_(publisherCid, scope) : null;
    });
  }

  /**
   * Returns scoped CID retrieved from the Viewer.
   * @param {string} url
   * @param {boolean=} useAlternate
   * @return {!Promise<?string>}
   */
  fetchCid_(url, useAlternate = true) {
    const payload = {
      'publisherOrigin': getSourceOrigin(this.ampdoc_.win.location),
    };

    // Make the XHR request to the cache endpoint.
    const timeoutMessage = 'fetchCidTimeout';
    return this.timer_
      .timeoutPromise(
        TIMEOUT_,
        Services.xhrFor(this.ampdoc_.win).fetchJson(url, {
          method: 'POST',
          ampCors: false,
          credentials: 'include',
          mode: 'cors',
          body: payload,
        }),
        timeoutMessage
      )
      .then((res) => {
        return res.json().then((response) => {
          if (response['optOut']) {
            return null;
          }
          const cid = response['publisherClientId'];
          if (!cid && useAlternate && response['alternateUrl']) {
            // If an alternate url is provided, try again with the alternate url
            // The client is still responsible for appending API keys to the URL.
            const alt = `${response['alternateUrl']}?key=${SERVICE_KEY_}`;
            return this.fetchCid_(dev().assertString(alt), false);
          }
          return cid;
        });
      })
      .catch((e) => {
        if (e && e.response) {
          e.response.json().then((res) => {
            dev().error(TAG_, JSON.stringify(res));
          });
        } else {
          const isTimeout = e && e.message == timeoutMessage;
          if (isTimeout) {
            dev().expectedError(TAG_, e);
          } else {
            dev().error(TAG_, e);
          }
        }
        return null;
      });
  }

  /**
   * Returns scoped CID extracted from the fetched publisherCid.
   * @param {string} publisherCid
   * @param {string} scope
   * @return {!Promise<string>}
   */
  scopeCid_(publisherCid, scope) {
    const text = publisherCid + ';' + scope;
    return Services.cryptoFor(this.ampdoc_.win)
      .sha384Base64(text)
      .then((enc) => {
        return 'amp-' + enc;
      });
  }
}
