import { merge, get } from '../lang';
import { validateMode } from './mode';
import { validateSplitFilters } from './splitFilters';
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG, FLAG_SPEC_VERSION } from '../constants';
import { validImpressionsMode } from './impressionsMode';
import { ISettingsValidationParams } from './types';
import { ISettings } from '../../types';
import { validateKey } from '../inputValidation/key';
import { ERROR_MIN_CONFIG_PARAM, LOG_PREFIX_CLIENT_INSTANTIATION } from '../../logger/constants';

// Exported for telemetry
export const base = {
  // Define which kind of object you want to retrieve from SplitFactory
  mode: STANDALONE_MODE,

  core: {
    // API token (tight to an environment)
    authorizationKey: undefined,
    // key used in your system (only required for browser version)
    key: undefined,
    // toggle impressions tracking of labels
    labelsEnabled: true,
    // toggle sendind (true) or not sending (false) IP and Host Name with impressions, events, and telemetries requests (only used on nodejs version)
    IPAddressesEnabled: undefined
  },

  scheduler: {
    // fetch feature updates each 60 sec
    featuresRefreshRate: 60,
    // fetch segments updates each 60 sec
    segmentsRefreshRate: 60,
    // publish telemetry stats each 3600 secs (1 hour)
    telemetryRefreshRate: 3600,
    // publish evaluations each 300 sec (default value for OPTIMIZED impressions mode)
    impressionsRefreshRate: 300,
    // fetch offline changes each 15 sec
    offlineRefreshRate: 15,
    // publish events every 60 seconds after the first flush
    eventsPushRate: 60,
    // how many events will be queued before flushing
    eventsQueueSize: 500,
    // how many impressions will be queued before flushing
    impressionsQueueSize: 30000,
    // backoff base seconds to wait before re attempting to connect to push notifications
    pushRetryBackoffBase: 1,
  },

  urls: {
    // CDN having all the information for your environment
    sdk: 'https://sdk.split.io/api',
    // SDK event and impression endpoints
    events: 'https://events.split.io/api',
    // SDK Auth Server
    auth: 'https://auth.split.io/api',
    // Streaming Server
    streaming: 'https://streaming.split.io',
    // Telemetry Server
    telemetry: 'https://telemetry.split.io/api',
  },

  // Defines which kind of storage we should instantiate.
  storage: undefined,

  // Defines if the logs are enabled, SDK wide.
  debug: undefined,

  // Defines the impression listener.
  impressionListener: undefined,

  // Instance version.
  version: undefined,

  // List of integrations.
  integrations: undefined,

  // toggle using (true) or not using (false) Server-Side Events for synchronizing storage
  streamingEnabled: true,

  sync: {
    splitFilters: undefined,
    // impressions collection mode
    impressionsMode: OPTIMIZED,
    enabled: true,
    flagSpecVersion: FLAG_SPEC_VERSION
  },

  // Logger
  log: undefined
};

function fromSecondsToMillis(n: number) {
  return Math.round(n * 1000);
}

/**
 * Validates the given config and use it to build a settings object.
 * NOTE: it doesn't validate the SDK Key. Call `validateApiKey` or `validateAndTrackApiKey` for that after settings validation.
 *
 * @param config - user defined configuration
 * @param validationParams - defaults and fields validators used to validate and creates a settings object from a given config
 */
export function settingsValidation(config: unknown, validationParams: ISettingsValidationParams) {

  const { defaults, runtime, storage, integrations, logger, consent, flagSpec } = validationParams;

  // creates a settings object merging base, defaults and config objects.
  const withDefaults = merge({}, base, defaults, config) as ISettings;
  // Keeps reference to the `features` property
  withDefaults.features = get(config, 'features');

  // ensure a valid logger.
  // First thing to validate, since other validators might use the logger.
  const log = logger(withDefaults); // @ts-ignore, modify readonly prop
  withDefaults.log = log;

  // ensure a valid impressionsMode
  const sync = withDefaults.sync;
  sync.impressionsMode = validImpressionsMode(log, sync.impressionsMode);

  function validateMinValue(paramName: string, actualValue: number, minValue: number) {
    if (actualValue >= minValue) return actualValue;
    // actualValue is not a number or is lower than minValue
    log.error(ERROR_MIN_CONFIG_PARAM, [paramName, minValue]);
    return minValue;
  }

  // Scheduler periods
  const { scheduler, startup } = withDefaults;
  scheduler.featuresRefreshRate = fromSecondsToMillis(scheduler.featuresRefreshRate);
  scheduler.segmentsRefreshRate = fromSecondsToMillis(scheduler.segmentsRefreshRate);
  scheduler.offlineRefreshRate = fromSecondsToMillis(scheduler.offlineRefreshRate);
  scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
  scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));

  // Default impressionsRefreshRate for DEBUG mode is 60 secs
  if (get(config, 'scheduler.impressionsRefreshRate') === undefined && sync.impressionsMode === DEBUG) scheduler.impressionsRefreshRate = 60;
  scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);

  // Log deprecation for old telemetry param
  if (scheduler.metricsRefreshRate) log.warn('`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs');

  // Startup periods
  startup.requestTimeoutBeforeReady = fromSecondsToMillis(startup.requestTimeoutBeforeReady);
  startup.readyTimeout = fromSecondsToMillis(startup.readyTimeout);
  startup.eventsFirstPushWindow = fromSecondsToMillis(startup.eventsFirstPushWindow);

  // ensure a valid SDK mode
  // @ts-ignore, modify readonly prop
  withDefaults.mode = validateMode(withDefaults.core.authorizationKey, withDefaults.mode);

  // ensure a valid Storage based on mode defined.
  // @ts-ignore, modify readonly prop
  if (storage) withDefaults.storage = storage(withDefaults);

  // Validate key and TT (for client-side)
  const maybeKey = withDefaults.core.key;
  if (validationParams.acceptKey) {
    // Although `key` is required in client-side, it can be omitted in LOCALHOST mode. In that case, the value `localhost_key` is used.
    if (withDefaults.mode === LOCALHOST_MODE && maybeKey === undefined) {
      withDefaults.core.key = 'localhost_key';
    } else {
      // Keeping same behavior than JS SDK: if settings key or TT are invalid,
      // `false` value is used as bound key/TT of the default client, which leads to some issues.
      // @ts-ignore, @TODO handle invalid keys as a non-recoverable error?
      withDefaults.core.key = validateKey(log, maybeKey, LOG_PREFIX_CLIENT_INSTANTIATION);
    }
  } else {
    // On server-side, key is undefined and used to distinguish from client-side
    if (maybeKey !== undefined) log.warn('Provided `key` is ignored in server-side SDK.'); // @ts-ignore
    withDefaults.core.key = undefined;
  }

  // Current ip/hostname information
  // @ts-ignore, modify readonly prop
  withDefaults.runtime = runtime(withDefaults);

  // ensure a valid list of integrations.
  // `integrations` returns an array of valid integration items.
  // @ts-ignore, modify readonly prop
  if (integrations) withDefaults.integrations = integrations(withDefaults);

  // validate push options
  if (withDefaults.streamingEnabled !== false) { // @ts-ignore, modify readonly prop
    withDefaults.streamingEnabled = true;
    // Backoff bases.
    // We are not checking if bases are positive numbers. Thus, we might be re-authenticating immediately (`setTimeout` with NaN or negative number)
    scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
  }

  // validate sync enabled
  if (sync.enabled !== false) {
    sync.enabled = true;
  }

  // validate the `splitFilters` settings and parse splits query
  const splitFiltersValidation = validateSplitFilters(log, sync.splitFilters, withDefaults.mode);
  sync.splitFilters = splitFiltersValidation.validFilters;
  sync.__splitFiltersValidation = splitFiltersValidation;

  // ensure a valid flag spec version
  sync.flagSpecVersion = flagSpec ? flagSpec(withDefaults) : FLAG_SPEC_VERSION;
  // ensure a valid user consent value
  // @ts-ignore, modify readonly prop
  withDefaults.userConsent = consent ? consent(withDefaults) : undefined;

  return withDefaults;
}
