import { ISettings } from '../../types';
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
import { getStorageHash } from '../KeyBuilder';
import { LOG_PREFIX } from './constants';
import type { DefinitionsCacheInLocal } from './DefinitionsCacheInLocal';
import type { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
import { KeyBuilderCS } from '../KeyBuilderCS';
import SplitIO from '../../../types/splitio';
import { StorageAdapter } from '../types';

const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
const MILLIS_IN_A_DAY = 86400000;

/**
 * Validates if cache should be cleared and sets the cache `hash` if needed.
 *
 * @returns `true` if cache should be cleared, `false` otherwise
 */
function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
  const { log, initialRolloutPlan } = settings;

  // Check expiration
  const lastUpdatedTimestamp = parseInt(storage.getItem(keys.buildLastUpdatedKey()) as string, 10);
  if (!isNaNNumber(lastUpdatedTimestamp)) {
    const cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
    const expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
    if (lastUpdatedTimestamp < expirationTimestamp) {
      log.info(LOG_PREFIX + 'Cache expired more than ' + cacheExpirationInDays + ' days ago. Cleaning up cache');
      return true;
    }
  }

  // Check hash
  const storageHashKey = keys.buildHashKey();
  const storageHash = storage.getItem(storageHashKey);
  const currentStorageHash = getStorageHash(settings);

  if (storageHash !== currentStorageHash) {
    try {
      storage.setItem(storageHashKey, currentStorageHash);
    } catch (e) {
      log.error(LOG_PREFIX + e);
    }
    if (isThereCache && !initialRolloutPlan) {
      log.info(LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache');
      return true;
    }
    return false; // No cache to clear
  }

  // Clear on init
  if (options.clearOnInit) {
    const lastClearTimestamp = parseInt(storage.getItem(keys.buildLastClear()) as string, 10);

    if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
      log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
      return true;
    }
  }
}

/**
 * Clean cache if:
 * - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp`
 * - its hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified
 * - `clearOnInit` was set and cache was not cleared in the last 24 hours
 *
 * @returns Metadata object with `initialCacheLoad` (true if is fresh install, false if is ready from cache) and `lastUpdateTimestamp` (timestamp of last cache update or undefined)
 */
export function validateCache(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, definitions: DefinitionsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): Promise<SplitIO.SdkReadyMetadata> {

  return Promise.resolve(storage.load && storage.load()).then(() => {
    const currentTimestamp = Date.now();
    const isThereCache = definitions.getChangeNumber() > -1;

    // Get lastUpdateTimestamp from storage
    const lastUpdatedTimestampStr = storage.getItem(keys.buildLastUpdatedKey());
    const lastUpdatedTimestamp = lastUpdatedTimestampStr ? parseInt(lastUpdatedTimestampStr, 10) : undefined;
    const lastUpdateTimestamp = (!isNaNNumber(lastUpdatedTimestamp) && lastUpdatedTimestamp !== undefined) ? lastUpdatedTimestamp : undefined;

    if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) {
      definitions.clear();
      rbSegments.clear();
      segments.clear();
      largeSegments.clear();

      // Update last clear timestamp
      try {
        storage.setItem(keys.buildLastClear(), currentTimestamp + '');
      } catch (e) {
        settings.log.error(LOG_PREFIX + e);
      }

      // Persist clear
      if (storage.save) storage.save();

      return {
        initialCacheLoad: true, // Cache was cleared, so this is an initial load (no cache existed)
        lastUpdateTimestamp: undefined
      };
    }

    // Check if ready from cache
    return {
      initialCacheLoad: !isThereCache, // true if no cache exists (initial load), false if cache exists (ready from cache)
      lastUpdateTimestamp
    };
  });
}
