import { ITelemetryCacheSync } from '../../storages/types';
import { submitterFactory, firstPushWindowDecorator } from './submitter';
import { TelemetryConfigStatsPayload, TelemetryConfigStats } from './types';
import { CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, NONE, DEBUG_ENUM, OPTIMIZED_ENUM, NONE_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
import { ISettings } from '../../types';
import SplitIO from '../../../types/splitio';
import { base } from '../../utils/settingsValidation';
import { usedKeysMap } from '../../utils/inputValidation/apiKey';
import { timer } from '../../utils/timeTracker/timer';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
import { objectAssign } from '../../utils/lang/objectAssign';
import { ISplitFiltersValidation } from '../../dtos/types';
import { checkIfServerSide } from '../../utils/key';

const OPERATION_MODE_MAP = {
  [STANDALONE_MODE]: STANDALONE_ENUM,
  [CONSUMER_MODE]: CONSUMER_ENUM,
  [CONSUMER_PARTIAL_MODE]: CONSUMER_PARTIAL_ENUM
} as Record<ISettings['mode'], (0 | 1 | 2)>;

const IMPRESSIONS_MODE_MAP = {
  [OPTIMIZED]: OPTIMIZED_ENUM,
  [DEBUG]: DEBUG_ENUM,
  [NONE]: NONE_ENUM
} as Record<ISettings['sync']['impressionsMode'], (0 | 1 | 2)>;

const USER_CONSENT_MAP = {
  [CONSENT_UNKNOWN]: 1,
  [CONSENT_GRANTED]: 2,
  [CONSENT_DECLINED]: 3
} as Record<SplitIO.ConsentStatus, number>;

function getActiveFactories() {
  return Object.keys(usedKeysMap).length;
}

function getRedundantActiveFactories() {
  return Object.keys(usedKeysMap).reduce((acum, sdkKey) => {
    return acum + usedKeysMap[sdkKey] - 1;
  }, 0);
}

function getTelemetryFlagSetsStats(splitFiltersValidation: ISplitFiltersValidation) {
  // Group every configured flag set in an unique array called originalSets
  let flagSetsTotal = 0;
  splitFiltersValidation.validFilters.forEach((filter: SplitIO.SplitFilter) => {
    if (filter.type === 'bySet') flagSetsTotal += filter.values.length;
  });

  const flagSetsValid = splitFiltersValidation.groupedFilters.bySet.length;
  const flagSetsIgnored = flagSetsTotal - flagSetsValid;
  return { flagSetsTotal, flagSetsIgnored };
}

export function getTelemetryConfigStats(mode: SplitIO.SDKMode, storageType: string): TelemetryConfigStats {
  return {
    oM: OPERATION_MODE_MAP[mode], // @ts-ignore lower case of storage type
    st: storageType.toLowerCase(),
    aF: getActiveFactories(),
    rF: getRedundantActiveFactories(),
  };
}

/**
 * Converts data from telemetry cache and settings into /metrics/config request payload.
 */
export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, settings: ISettings) {
  return {
    name: 'telemetry config',
    isEmpty() { return false; },
    clear() { },

    pop(): TelemetryConfigStatsPayload {
      const { urls, scheduler } = settings;
      const isServerSide = checkIfServerSide(settings);

      const { flagSetsTotal, flagSetsIgnored } = getTelemetryFlagSetsStats(settings.sync.__splitFiltersValidation);

      return objectAssign(getTelemetryConfigStats(settings.mode, settings.storage.type), {
        sE: settings.streamingEnabled,
        rR: {
          sp: scheduler.featuresRefreshRate / 1000,
          se: isServerSide ? scheduler.segmentsRefreshRate / 1000 : undefined,
          ms: isServerSide ? undefined : scheduler.segmentsRefreshRate / 1000,
          im: scheduler.impressionsRefreshRate / 1000,
          ev: scheduler.eventsPushRate / 1000,
          te: scheduler.telemetryRefreshRate / 1000,
        }, // refreshRates
        uO: {
          s: urls.sdk !== base.urls.sdk,
          e: urls.events !== base.urls.events,
          a: urls.auth !== base.urls.auth,
          st: urls.streaming !== base.urls.streaming,
          t: urls.telemetry !== base.urls.telemetry,
        }, // urlOverrides
        iQ: scheduler.impressionsQueueSize,
        eQ: scheduler.eventsQueueSize,
        iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
        iL: settings.impressionListener ? true : false,
        hP: false, // @TODO proxy not supported
        tR: telemetry.getTimeUntilReady() as number,
        tC: telemetry.getTimeUntilReadyFromCache(),
        nR: telemetry.getNonReadyUsage(),
        t: telemetry.popTags(),
        i: settings.integrations && settings.integrations.map(int => int.type),
        uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0,
        fsT: flagSetsTotal,
        fsI: flagSetsIgnored
      });
    }
  };
}

/**
 * Submitter that periodically posts telemetry data
 */
export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
  const { storage: { telemetry }, platform: { now } } = params;
  if (!telemetry || !now) return; // No submitter created if telemetry cache is not defined

  const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, serviceApi, readiness } = params;
  const stopTimer = timer(now);

  const submitter = firstPushWindowDecorator(
    submitterFactory(
      log, serviceApi.postMetricsUsage,
      telemetry,
      telemetryRefreshRate, undefined, 0, true
    ),
    telemetryRefreshRate
  );

  readiness.gate.once(SDK_READY_FROM_CACHE, () => {
    telemetry.recordTimeUntilReadyFromCache(stopTimer());
  });

  readiness.gate.once(SDK_READY, () => {
    telemetry.recordTimeUntilReady(stopTimer());

    // Post config data when the SDK is ready and if the telemetry submitter was started
    if (submitter.isRunning()) {
      const postMetricsConfigTask = submitterFactory(log, serviceApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, undefined, 0, true);
      postMetricsConfigTask.execute();
    }
  });

  return submitter;
}
