import { addSeconds } from 'date-fns/addSeconds';
import { isAfter } from 'date-fns/isAfter';

export interface Storage {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
  removeItem(key: string): void;
}

export class InMemoryStorage {
  private readonly data = new Map<string, string>();

  getItem(key: string): string | null {
    const d = this.data.get(key);
    return d ? d : null;
  }

  setItem(key: string, value: string) {
    this.data.set(key, value);
  }

  removeItem(key: string) {
    this.data.delete(key);
  }
}

export class Cache {
  constructor(private readonly storage: Storage) {}

  // load first tries to load data from storage, if there is no data or it is expired then it executes the given function and caches the result
  async load<T>(key: string, ttl: number, func: () => Promise<T>): Promise<T> {
    const data = this.get<T>(key);
    if (data) {
      return data;
    }

    const res = await func();

    this.storage.setItem(
      key,
      JSON.stringify({
        data: res,
        exp: addSeconds(new Date(), ttl),
      })
    );

    return res;
  }

  get<T>(key: string): T | null {
    try {
      const fromStorage = JSON.parse(this.storage.getItem(key) ?? '');
      if (!fromStorage.exp || !fromStorage.data) {
        return null;
      }
      if (isAfter(new Date(), new Date(fromStorage.exp))) {
        this.storage.removeItem(key);
        return null;
      }
      return fromStorage.data;
    } catch (err) {
      return null;
    }
  }
}
