import { wait } from 'helpers/wait';

type QueueItem = {
  key: string;
  fn: () => Promise<unknown> | unknown;
  onSuccess?: () => Promise<unknown> | unknown;
  onError?: (error: unknown) => Promise<unknown> | unknown;
  onResolved?: () => Promise<unknown> | unknown;
};

class Queue {
  private items: QueueItem[] = [];
  keys = new Set<string>();

  enqueue(item: QueueItem): void {
    if (this.keys.has(item.key)) {
      throw new Error('DUPLICATE_KEY');
    }
    this.keys.add(item.key);
    this.items.push(item);
  }

  dequeue(): QueueItem | undefined {
    return this.items.shift();
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }

  size(): number {
    return this.items.length;
  }
}

export class QueueManager {
  private queue = new Queue();
  private isRunning = false;

  add(item: QueueItem) {
    try {
      this.queue.enqueue(item);
      if (!this.isRunning) {
        this.isRunning = true;
        this.run();
      }
      return this.queue.size();
    } catch {
      return {
        error: 'There is already a refresh job running or in queue for this google account',
      };
    }
  }

  private async run() {
    this.isRunning = true;
    if (this.queue.isEmpty()) {
      this.isRunning = false;
      return;
    }

    const item = this.queue.dequeue();
    if (!item) {
      this.isRunning = false;
      return;
    }

    const { key, fn, onSuccess, onError, onResolved } = item;
    try {
      await fn();
      if (onSuccess) await onSuccess();
    } catch (ex) {
      if (onError) await onError(ex);
    } finally {
      if (onResolved) await onResolved();
      this.isRunning = false;
      this.queue.keys.delete(key);

      if (!this.queue.isEmpty()) {
        await wait(1000);
        await this.run();
      }
    }
  }
}
