import * as plugins from './levelcache.plugins.js';
import { CacheDiskManager } from './levelcache.classes.cache.diskmanager.js';
import { CacheMemoryManager } from './levelcache.classes.cache.memorymanager.js';
import { CacheS3Manager } from './levelcache.classes.cache.s3manager.js';
import { CacheEntry } from './levelcache.classes.cacheentry.js';
import { CacheRouter } from './levelcache.classes.cacherouter.js';
import { AbstractCache } from './levelcache.abstract.classes.cache.js';

export interface ILevelCacheConstructorOptions {
  /**
   * a unique id when having more than one cache
   */
  cacheId: string;
  maxMemoryStorageInMB?: number;
  maxDiskStorageInMB?: number;
  maxS3StorageInMB?: number;
  diskStoragePath?: string;
  s3Config?: plugins.tsclass.storage.IS3Descriptor;
  s3BucketName?: string;
  forceLevel?: 'memory' | 'disk' | 's3';
  expirationInMs?: number;
  immutableCache?: boolean;
  persistentCache?: boolean;
}

/**
 * a leveled cache for storing things for a short time
 */
export class LevelCache extends AbstractCache {
  private readyDeferred = plugins.smartpromise.defer<void>();

  public ready = this.readyDeferred.promise;

  public status: 'active' = 'active'; // artifact of AbstractCache

  public cacheRouter = new CacheRouter(this);
  public cacheDiskManager: CacheDiskManager;
  public cacheMemoryManager: CacheMemoryManager;
  public cacheS3Manager: CacheS3Manager;

  public options: ILevelCacheConstructorOptions;

  constructor(optionsArg: ILevelCacheConstructorOptions) {
    super();
    this.options = {
      maxMemoryStorageInMB: 0.5,
      maxDiskStorageInMB: 10,
      maxS3StorageInMB: 50,
      ...optionsArg,
    };
    this.init();
  }

  public async init() {
    this.cacheMemoryManager = new CacheMemoryManager(this);
    this.cacheDiskManager = new CacheDiskManager(this);
    this.cacheS3Manager = new CacheS3Manager(this);
    await Promise.all([
      this.cacheDiskManager.ready,
      this.cacheMemoryManager.ready,
      this.cacheS3Manager.ready,
    ]);
    this.readyDeferred.resolve();
  }

  // store things
  public async storeCacheEntryByKey(keyArg: string, cacheEntryArg: CacheEntry): Promise<void> {
    cacheEntryArg.key = keyArg;
    const targetCache = await this.cacheRouter.getCacheForStoreAction(keyArg, cacheEntryArg);
    cacheEntryArg.createdAt = Date.now();
    await targetCache.storeCacheEntryByKey(keyArg, cacheEntryArg);
  }

  // retrieve things
  /**
   * retrieve cache entry
   */
  public async retrieveCacheEntryByKey(keyArg: string): Promise<CacheEntry> {
    const targetCache = await this.cacheRouter.getCacheForRetrieveAction(keyArg);
    if (targetCache) {
      const cacheEntry = await targetCache.retrieveCacheEntryByKey(keyArg);
      if (cacheEntry.createdAt + cacheEntry.ttl < Date.now()) {
        await this.deleteCacheEntryByKey(keyArg).catch();
        return null;
      }
      return cacheEntry;
    } else {
      return null;
    }
  }

  public async checkKeyPresence(keyArg: string): Promise<boolean> {
    return plugins.smartpromise.getFirstTrueOrFalse([
      this.cacheMemoryManager.checkKeyPresence(keyArg),
      this.cacheDiskManager.checkKeyPresence(keyArg),
      this.cacheS3Manager.checkKeyPresence(keyArg),
    ]);
  }

  public async deleteCacheEntryByKey(keyArg) {
    await Promise.all([
      this.cacheMemoryManager.deleteCacheEntryByKey(keyArg),
      this.cacheDiskManager.deleteCacheEntryByKey(keyArg),
      this.cacheS3Manager.deleteCacheEntryByKey(keyArg),
    ]);
  }

  // cache maintenance
  public async cleanOutdated() {}

  /**
   * cleans the complete cache
   */
  public async cleanAll(): Promise<void> {
    await Promise.all([
      this.cacheDiskManager.cleanAll(),
      this.cacheDiskManager.cleanAll(),
      this.cacheS3Manager.cleanAll(),
    ]);
  }
}
