import { IMySegmentsSyncTask, IPollingManagerCS } from './types';
import { forOwn } from '../../utils/lang';
import { IReadinessManager } from '../../readiness/types';
import { IStorageSync } from '../../storages/types';
import { mySegmentsSyncTaskFactory } from './syncTasks/mySegmentsSyncTask';
import { definitionsSyncTaskFactory } from './syncTasks/definitionsSyncTask';
import { getMatching } from '../../utils/key';
import { SDK_DEFINITIONS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
import { usesSegmentsSync } from '../../storages/AbstractDefinitionsCacheSync';
import { SdkUpdateMetadata } from '../../../types/splitio';
import { IDefinitionChangesFetcher } from './fetchers/types';

/**
 * Expose start / stop mechanism for polling data from services.
 * For client-side API with multiple clients.
 */
export function pollingManagerCSFactory(
  params: ISdkFactoryContextSync,
  definitionChangesFetcher: IDefinitionChangesFetcher
): IPollingManagerCS {

  const { serviceApi, storage, readiness, settings } = params;
  const log = settings.log;

  const definitionsSyncTask = definitionsSyncTaskFactory(definitionChangesFetcher, storage, readiness, settings);

  // Map of matching keys to their corresponding MySegmentsSyncTask.
  const mySegmentsSyncTasks: Record<string, IMySegmentsSyncTask> = {};

  const matchingKey = getMatching(settings.core.key);
  const mySegmentsSyncTask = add(matchingKey, readiness, storage);

  function startMySegmentsSyncTasks() {
    forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
      mySegmentsSyncTask.start();
    });
  }

  function stopMySegmentsSyncTasks() {
    forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
      if (mySegmentsSyncTask.isRunning()) mySegmentsSyncTask.stop();
    });
  }

  // smart pausing
  readiness.definitions.on(SDK_DEFINITIONS_ARRIVED, () => {
    if (!definitionsSyncTask.isRunning()) return; // noop if not doing polling
    const usingSegments = usesSegmentsSync(storage);
    if (usingSegments !== mySegmentsSyncTask.isRunning()) {
      log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
      if (usingSegments) {
        startMySegmentsSyncTasks();
      } else {
        stopMySegmentsSyncTasks();
      }
    }
  });

  function add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync) {
    const mySegmentsSyncTask = mySegmentsSyncTaskFactory(serviceApi.fetchMemberships, storage, readiness, settings, matchingKey);

    // smart ready
    function smartReady(metadata: SdkUpdateMetadata) {
      if (!readiness.isReady() && !usesSegmentsSync(storage)) readiness.segments.emit(SDK_SEGMENTS_ARRIVED, metadata);
    }
    if (!usesSegmentsSync(storage)) setTimeout(smartReady, 0);
    else readiness.definitions.once(SDK_DEFINITIONS_ARRIVED, smartReady);

    mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
    return mySegmentsSyncTask;
  }

  return {
    definitionsSyncTask,
    segmentsSyncTask: mySegmentsSyncTask,

    // Start periodic fetching (polling)
    start() {
      log.info(POLLING_START);

      definitionsSyncTask.start();
      if (usesSegmentsSync(storage)) startMySegmentsSyncTasks();
    },

    // Stop periodic fetching (polling)
    stop() {
      log.info(POLLING_STOP);

      if (definitionsSyncTask.isRunning()) definitionsSyncTask.stop();
      stopMySegmentsSyncTasks();
    },

    // Used by SyncManager to know if running in polling mode.
    isRunning: definitionsSyncTask.isRunning,

    // fetch definitions and segments
    syncAll() {
      const promises = [definitionsSyncTask.execute()];
      forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
        promises.push(mySegmentsSyncTask.execute());
      });
      return Promise.all(promises);
    },

    // Support for handling mySegments sync of multiple clients
    add,

    remove(matchingKey: string) {
      delete mySegmentsSyncTasks[matchingKey];
    },

    get(matchingKey: string) {
      return mySegmentsSyncTasks[matchingKey];
    }
  };

}
