/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: BUSL-1.1
 */

import { runInDebug } from '@ember/debug';
import { htmlSafe } from '@ember/template';

function sanitizeString(str) {
  return htmlSafe(
    String(str)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
  );
}

// 'environment' getter
// there are currently 3 levels of environment variables:
// 1. Those that can be set by the user by setting localStorage values
// 2. Those that can be set by the operator either via ui_config, or inferring
// from other server type properties (protocol)
// 3. Those that can be set only during development by adding cookie values
// via the browsers Web Inspector, or via the browsers hash (#COOKIE_NAME=1),
// which is useful for showing the UI with various settings enabled/disabled
export default function (config = {}, win = window, doc = document) {
  // look at the hash in the URL and transfer anything after the hash into
  // cookies to enable linking of the UI with various settings enabled
  runInDebug(() => {
    const cookies = function (str) {
      return str
        .split(';')
        .map((item) => item.trim())
        .filter((item) => item !== '')
        .filter((item) => item.split('=').shift().startsWith('CONSUL_'));
    };

    // Define the function that reads in "Scenarios", parse and set cookies and set theme if specified.
    // See https://github.com/hashicorp/consul/blob/main/ui/packages/consul-ui/docs/bookmarklets.mdx
    win['Scenario'] = function (str = '') {
      if (str.length > 0) {
        cookies(str).forEach((item) => {
          // this current outlier is the only one that
          // 1. Toggles
          // 2. Uses localStorage
          // Once we have a user facing widget to do this, it can all go
          if (item.startsWith('CONSUL_COLOR_SCHEME=')) {
            const [, value] = item.split('=');
            let current;
            try {
              current = JSON.parse(win.localStorage.getItem('consul:theme'));
            } catch (e) {
              current = {
                'color-scheme': 'light',
              };
            }
            win.localStorage.setItem(
              'consul:theme',
              `{"color-scheme": "${
                value === '!' ? (current['color-scheme'] === 'light' ? 'dark' : 'light') : value
              }"}`
            );
          } else {
            doc.cookie = `${item};Path=/`;
          }
        });
        win.location.hash = '';
        location.reload();
      } else {
        str = cookies(doc.cookie).join(';');
        const tab = win.open('', '_blank');
        if (tab) {
          const safeLocationHref = sanitizeString(location.href);
          const safeStr = sanitizeString(str);
          tab.document.write(`
            <body>
              <pre>${safeLocationHref}#${safeStr}</pre><br />
              <a href="#" onclick="window.opener.Scenario('${safeStr}');window.close();return false;">Scenario</a>
            </body>
          `);
        }
      }
    };

    if (
      typeof win.location !== 'undefined' &&
      typeof win.location.hash === 'string' &&
      win.location.hash.length > 0
    ) {
      win['Scenario'](win.location.hash.substr(1));
    }
  });

  // Defines a function that reads in the cookies and parses the cookie keys.
  const dev = function (str = doc.cookie) {
    return str
      .split(';')
      .filter((item) => item !== '')
      .map((item) => {
        const [key, ...rest] = item.trim().split('=');
        return [key, rest.join('=')];
      });
  };

  const user = function (str) {
    const item = win.localStorage.getItem(str);
    return item === null ? undefined : item;
  };
  const getResourceFor = function (src) {
    try {
      return (
        win.performance.getEntriesByType('resource').find((item) => {
          return item.initiatorType === 'script' && src === item.name;
        }) || {}
      );
    } catch (e) {
      return {};
    }
  };
  const operatorConfig = {
    ...config.operatorConfig,
    ...JSON.parse(doc.querySelector(`[data-${config.modulePrefix}-config]`).textContent),
  };
  const ui_config = operatorConfig.UIConfig || {};
  const scripts = doc.getElementsByTagName('script');
  // we use the currently executing script as a reference
  // to figure out where we are for other things such as
  // base url, api url etc
  const currentSrc = scripts[scripts.length - 1].src;
  let resource;
  // TODO: Potentially use ui_config {}, for example
  // turning off blocking queries if its a busy cluster
  // forcing/providing amount of possible HTTP connections
  // re-setting the base url for the API etc
  const operator = function (str, env) {
    let protocol, dashboards, provider, proxy;
    switch (str) {
      case 'CONSUL_NSPACES_ENABLED':
        return typeof operatorConfig.NamespacesEnabled === 'undefined'
          ? false
          : operatorConfig.NamespacesEnabled;
      case 'CONSUL_SSO_ENABLED':
        return typeof operatorConfig.SSOEnabled === 'undefined' ? false : operatorConfig.SSOEnabled;
      case 'CONSUL_ACLS_ENABLED':
        return typeof operatorConfig.ACLsEnabled === 'undefined'
          ? false
          : operatorConfig.ACLsEnabled;
      case 'CONSUL_PARTITIONS_ENABLED':
        return typeof operatorConfig.PartitionsEnabled === 'undefined'
          ? false
          : operatorConfig.PartitionsEnabled;
      case 'CONSUL_PEERINGS_ENABLED':
        return typeof operatorConfig.PeeringEnabled === 'undefined'
          ? false
          : operatorConfig.PeeringEnabled;
      case 'CONSUL_HCP_ENABLED':
        return typeof operatorConfig.HCPEnabled === 'undefined' ? false : operatorConfig.HCPEnabled;
      case 'CONSUL_DATACENTER_LOCAL':
        return operatorConfig.LocalDatacenter;
      case 'CONSUL_DATACENTER_PRIMARY':
        return operatorConfig.PrimaryDatacenter;
      case 'CONSUL_HCP_MANAGED_RUNTIME':
        return operatorConfig.HCPManagedRuntime;
      case 'CONSUL_API_PREFIX':
        // we want API prefix to look like an env var for if we ever change
        // operator config to be an API request, we need this variable before we
        // make and API request so this specific variable should never be
        // retrived via an API request
        return operatorConfig.APIPrefix;
      case 'CONSUL_HCP_URL':
        return operatorConfig.HCPURL;
      case 'CONSUL_UI_CONFIG':
        dashboards = {
          service: undefined,
        };
        provider = env('CONSUL_METRICS_PROVIDER');
        proxy = env('CONSUL_METRICS_PROXY_ENABLED');
        dashboards.service = env('CONSUL_SERVICE_DASHBOARD_URL');
        if (provider) {
          ui_config.metrics_provider = provider;
        }
        if (proxy) {
          ui_config.metrics_proxy_enabled = proxy;
        }
        if (dashboards.service) {
          ui_config.dashboard_url_templates = dashboards;
        }
        return ui_config;
      case 'CONSUL_BASE_UI_URL':
        return currentSrc.split('/').slice(0, -2).join('/');
      case 'CONSUL_HTTP_PROTOCOL':
        if (typeof resource === 'undefined') {
          // resource needs to be retrieved lazily as entries aren't guaranteed
          // to be available at script execution time (caching seems to affect this)
          // waiting until we retrieve this value lazily at runtime means that
          // the entries are always available as these values are only retrieved
          // after initialization
          // current is based on the assumption that whereever this script is it's
          // likely to be the same as the xmlhttprequests
          resource = getResourceFor(currentSrc);
        }
        return resource.nextHopProtocol || 'http/1.1';
      case 'CONSUL_HTTP_MAX_CONNECTIONS':
        protocol = env('CONSUL_HTTP_PROTOCOL');
        // http/2, http2+QUIC/39 and SPDY don't have connection limits
        switch (true) {
          case protocol.indexOf('h2') === 0:
          case protocol.indexOf('hq') === 0:
          case protocol.indexOf('spdy') === 0:
            // TODO: Change this to return -1 so we try to consistently
            // return a value from env vars
            return;
          default:
            // generally 6 are available
            // reserve 1 for traffic that we can't manage
            return 5;
        }
      case 'CONSUL_V2_CATALOG_ENABLED':
        return operatorConfig.V2CatalogEnabled === 'undefined'
          ? false
          : operatorConfig.V2CatalogEnabled;
    }
  };
  const ui = function (key) {
    let $ = {};
    switch (config.environment) {
      case 'development':
      case 'staging':
      case 'coverage':
      case 'test':
        $ = dev().reduce(function (prev, [key, value]) {
          switch (key) {
            case 'CONSUL_INTL_LOCALE':
              prev['CONSUL_INTL_LOCALE'] = String(value).toLowerCase();
              break;
            case 'CONSUL_INTL_DEBUG':
              prev['CONSUL_INTL_DEBUG'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_ACLS_ENABLE':
              prev['CONSUL_ACLS_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_AGENTLESS_ENABLE':
              prev['CONSUL_AGENTLESS_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_NSPACES_ENABLE':
              prev['CONSUL_NSPACES_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_SSO_ENABLE':
              prev['CONSUL_SSO_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_PARTITIONS_ENABLE':
              prev['CONSUL_PARTITIONS_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_METRICS_PROXY_ENABLE':
              prev['CONSUL_METRICS_PROXY_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_PEERINGS_ENABLE':
              prev['CONSUL_PEERINGS_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_HCP_ENABLE':
              prev['CONSUL_HCP_ENABLED'] = !!JSON.parse(String(value).toLowerCase());
              break;
            case 'CONSUL_UI_CONFIG':
              prev['CONSUL_UI_CONFIG'] = JSON.parse(value);
              break;
            case 'TokenSecretID':
              prev['CONSUL_HTTP_TOKEN'] = value;
              break;
            case 'CONSUL_V2_CATALOG_ENABLE':
              prev['CONSUL_V2_CATALOG_ENABLED'] = JSON.parse(value);
              break;
            default:
              prev[key] = value;
          }
          return prev;
        }, {});
        break;
      case 'production':
        $ = dev().reduce(function (prev, [key, value]) {
          switch (key) {
            case 'TokenSecretID':
              prev['CONSUL_HTTP_TOKEN'] = value;
              break;
          }
          return prev;
        }, {});
        break;
    }
    if (typeof $[key] !== 'undefined') {
      return $[key];
    }
    return config[key];
  };
  return function env(str) {
    switch (str) {
      // All user providable values should start with CONSUL_UI
      // We allow the user to set these ones via localStorage
      // user value is preferred.
      case 'CONSUL_UI_DISABLE_REALTIME':
      case 'CONSUL_UI_DISABLE_ANCHOR_SELECTION':
        // these are booleans cast things out
        return !!JSON.parse(String(user(str) || 0).toLowerCase()) || ui(str);
      case 'CONSUL_UI_REALTIME_RUNNER':
        // these are strings
        return user(str) || ui(str);
      case 'CONSUL_UI_CONFIG':
      case 'CONSUL_DATACENTER_LOCAL':
      case 'CONSUL_DATACENTER_PRIMARY':
      case 'CONSUL_HCP_MANAGED_RUNTIME':
      case 'CONSUL_API_PREFIX':
      case 'CONSUL_HCP_URL':
      case 'CONSUL_ACLS_ENABLED':
      case 'CONSUL_NSPACES_ENABLED':
      case 'CONSUL_PEERINGS_ENABLED':
      case 'CONSUL_AGENTLESS_ENABLED':
      case 'CONSUL_HCP_ENABLED':
      case 'CONSUL_SSO_ENABLED':
      case 'CONSUL_PARTITIONS_ENABLED':
      case 'CONSUL_METRICS_PROVIDER':
      case 'CONSUL_METRICS_PROXY_ENABLE':
      case 'CONSUL_SERVICE_DASHBOARD_URL':
      case 'CONSUL_BASE_UI_URL':
      case 'CONSUL_V2_CATALOG_ENABLED':
      case 'CONSUL_HTTP_PROTOCOL':
      case 'CONSUL_HTTP_MAX_CONNECTIONS': {
        // We allow the operator to set these ones via various methods
        // although UI developer config is preferred
        const _ui = ui(str);
        return typeof _ui !== 'undefined' ? _ui : operator(str, env);
      }
      default:
        return ui(str);
    }
  };
}
