import { Task } from '@lit/task';
import { v4 as uuidv4 } from 'uuid';

import type { PlayerEmbed, PlayerEmbedProps } from '..';
import type { ReactiveControllerHost } from 'lit';
import type { Environment } from '~/utils/env';

import { getEnvUrls } from '~/utils/env';

type PlayerEmbedHost = ReactiveControllerHost & PlayerEmbed;

type FetchTaskDeps = [
  PlayerEmbedProps['env'],
  PlayerEmbedProps['apiKey'],
  PlayerEmbedProps['data'],
  PlayerEmbedProps['vouchId'],
  PlayerEmbedProps['templateId']
];

type FilterTaskDeps = [PlayerEmbedProps['data'], PlayerEmbedProps['questions']];

class FetcherController {
  host: PlayerEmbedHost;

  private _fetching = false;
  private _vouch: PlayerEmbedProps['data'];

  set fetching(value) {
    if (this._fetching !== value) {
      this._fetching = value;
      this.host.requestUpdate();
    }
  }
  get fetching() {
    return this._fetching;
  }

  private getVouch = async (env: Environment, apiKey: string, vouchId: string) => {
    const { embedApiUrl } = getEnvUrls(env);

    const cacheCheck = uuidv4();
    const res = await fetch(`${embedApiUrl}/vouches/${vouchId}`, {
      method: 'GET',
      headers: [
        ['X-Api-Key', apiKey],
        ['X-Cache-Check', cacheCheck]
      ]
    });

    const vouch = await res.json();
    this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouch?.id }));

    // HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
    // so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
    // API call with the `Cache-Control` header which will re-fill the cache
    const resCacheCheck = res?.headers?.get('X-Cache-Check');
    if (resCacheCheck !== cacheCheck) {
      fetch(`${embedApiUrl}/vouches/${vouchId}`, {
        method: 'GET',
        headers: [
          ['X-Api-Key', apiKey],
          ['Cache-Control', 'max-age=0']
        ]
      });
    }

    return vouch;
  };

  private getTemplate = async (env: Environment, apiKey: string, templateId: string) => {
    const { embedApiUrl } = getEnvUrls(env);

    const cacheCheck = uuidv4();
    const res = await fetch(`${embedApiUrl}/templates/${templateId}`, {
      method: 'GET',
      headers: [
        ['X-Api-Key', apiKey],
        ['X-Cache-Check', cacheCheck]
      ]
    });
    const template = await res.json();

    // HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
    // so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
    // API call with the `Cache-Control` header which will re-fill the cache
    const resCacheCheck = res?.headers?.get('X-Cache-Check');
    if (resCacheCheck !== cacheCheck) {
      fetch(`${embedApiUrl}/templates/${templateId}`, {
        method: 'GET',
        headers: [
          ['X-Api-Key', apiKey],
          ['Cache-Control', 'max-age=0']
        ]
      });
    }

    return template;
  };

  constructor(host: PlayerEmbedHost) {
    this.host = host;
    new Task<FetchTaskDeps, void>(
      this.host,
      async ([env, apiKey, data, vouchId, templateId]: FetchTaskDeps) => {
        try {
          host.vouch = undefined;
          host.template = undefined;

          if (data) {
            let template;
            if (templateId) {
              this.fetching = true;
              template = await this.getTemplate(env, apiKey, templateId);
            }
            this._vouch = data;
            host.template = template ?? data?.settings?.template?.instance;
          } else if (vouchId) {
            this.fetching = true;

            const [vouch, template] = await Promise.all([
              this.getVouch(env, apiKey, vouchId),
              templateId ? this.getTemplate(env, apiKey, templateId) : null
            ]);
            this._vouch = vouch;
            host.template = template ?? vouch?.settings?.template?.instance;
          }
        } finally {
          this.fetching = false;
        }
      },
      () => [host.env, host.apiKey, host.data, host.vouchId, host.templateId]
    );

    // This second task is to be able to filter the vouch without fetching it again if only the questions changed
    new Task<FilterTaskDeps, void>(
      this.host,
      ([vouch, questions]: FilterTaskDeps) => {
        host.vouch = vouch
          ? {
              ...vouch,
              questions: {
                items: vouch?.questions.items.filter((_, index) => !questions?.length || questions?.includes(index + 1))
              }
            }
          : undefined;
      },
      () => [this._vouch, host.questions]
    );
  }
}

export { FetcherController };
