import { SaleorAsyncWebhook, SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next";
import { WebhookEventTypeAsyncEnum, WebhookEventTypeSyncEnum } from "../../generated/graphql";
import { AppWebhookRepository } from "./app-webhook-repository";
import { AuthData } from "@saleor/app-sdk/APL";
import { createGraphQLClient } from "@saleor/apps-shared";

type AppWebhookMigratorOptions = {
  mode: "report" | "migrate";
};

type AppWebhookHandler = SaleorSyncWebhook | SaleorAsyncWebhook;

export class AppWebhookMigrator {
  private appWebhookRepository: AppWebhookRepository;
  private appId: string;
  private apiUrl: string;
  private mode: AppWebhookMigratorOptions["mode"];

  constructor(
    {
      appWebhookRepository,
      apiUrl,
      appId,
    }: {
      apiUrl: string;
      appId: string;
      appWebhookRepository: AppWebhookRepository;
    },
    { mode }: AppWebhookMigratorOptions,
  ) {
    this.appWebhookRepository = appWebhookRepository;

    this.appId = appId;
    this.apiUrl = apiUrl;
    this.mode = mode;
  }

  private registerWebhookFromHandler(webhookHandler: AppWebhookHandler) {
    const manifest = webhookHandler.getWebhookManifest(this.apiUrl);

    if (!manifest.query) {
      throw new Error("Webhook query is required");
    }

    if (!manifest.name) {
      throw new Error("Webhook name is required");
    }

    console.log(`⏳ Webhook ${manifest.name} will be registered`);

    if (this.mode === "migrate") {
      return this.appWebhookRepository.create({
        appId: this.appId,
        name: manifest.name,
        query: manifest.query,
        targetUrl: manifest.targetUrl,
        asyncEvents: (manifest.asyncEvents ?? []) as WebhookEventTypeAsyncEnum[],
        syncEvents: (manifest.syncEvents ?? []) as WebhookEventTypeSyncEnum[],
        isActive: manifest.isActive ?? true,
      });
    }
  }

  private async deleteWebhookById(webhookId: string) {
    console.log(`⏳ Webhook ${webhookId} will be deleted`);

    if (this.mode === "migrate") {
      await this.appWebhookRepository.delete(webhookId);

      console.log(`✅ Webhook ${webhookId} deleted`);
    }
  }

  private async disableWebhookById(webhookId: string) {
    console.log(`⏳ Webhook ${webhookId} will be disabled`);

    if (this.mode === "migrate") {
      await this.appWebhookRepository.disable(webhookId);

      console.log(`✅ Webhook ${webhookId} disabled`);
    }
  }

  /**
   * @returns all webhooks for the app
   * @throws error if fetching webhooks fails
   */
  async getAppWebhooks() {
    const webhooks = await this.appWebhookRepository.getAll();

    console.log(`📖 Webhooks for app ${this.appId}: `, webhooks);

    return webhooks;
  }

  private async disableFirstWebhookByName(webhookName: string) {
    const webhooks = await this.getAppWebhooks();

    const webhook = webhooks.find((webhook) => webhook.name === webhookName);

    if (!webhook) {
      console.log(`🚧 Webhook ${webhookName} not found`);

      return;
    }

    await this.disableWebhookById(webhook.id);
  }

  /**
   * Deletes first app webhook that matches the name.
   * @param webhookName - name of the webhook to delete
   */
  async DANGEROUS_DELETE_APP_WEBHOOK_BY_NAME(webhookName: string) {
    const webhooks = await this.getAppWebhooks();

    const webhook = webhooks.find((webhook) => webhook.name === webhookName);

    if (!webhook) {
      console.log(`🚧 Webhook ${webhookName} not found`);

      return;
    }

    await this.deleteWebhookById(webhook.id);
  }

  async updateWebhookQueryByHandler(webhookHandler: AppWebhookHandler) {
    const webhooks = await this.getAppWebhooks();

    const manifest = webhookHandler.getWebhookManifest(this.apiUrl);
    const webhookName = manifest.name;

    const webhook = webhooks.find((webhook) => webhook.name === webhookName);

    if (!webhook) {
      console.log(`🚧 Webhook ${webhookName} not found`);

      return;
    }

    console.log(`⏳ Webhook ${webhookName} query will be updated`);

    if (this.mode === "migrate") {
      await this.appWebhookRepository.update(webhook.id, { query: manifest.query });
      console.log(`✅ Webhook ${webhookName} query updated`);
    }
  }

  /**
   * Registers a webhook if it doesn't exist based on a handler.
   * @param webhookHandler - The handler of the webhook we want to register.
   * @example registerWebhookIfItDoesntExist(orderConfirmedAsyncWebhook)
   */
  async registerWebhookIfItDoesntExist(webhookHandler: SaleorSyncWebhook | SaleorAsyncWebhook) {
    const webhooks = await this.getAppWebhooks();

    const webhookExists = webhooks.some((webhook) => webhook.name === webhookHandler.name);

    if (webhookExists) {
      console.log(`🚧 Webhook ${webhookHandler.name} already exists`);

      return;
    }

    console.log(`⏳ Webhook ${webhookHandler.name} will be registered`);

    if (this.mode === "migrate") {
      await this.registerWebhookFromHandler(webhookHandler);
      console.log(`✅ Webhook ${webhookHandler.name} registered`);
    }
  }

  /**
   * Rolls back webhook migration by deleting the new webhook and enabling the old one.
   * @param prevWebhookName - The name of the webhook we wanted to migrate from.
   * @param nextWebhookHandler - The handler of the webhook we wanted to migrate to.
   * @example rollbackWebhookMigrations("OrderCreated", orderConfirmedAsyncWebhook)
   */
  async rollbackWebhookMigrations(
    prevWebhookName: string,
    nextWebhookHandler: SaleorSyncWebhook | SaleorAsyncWebhook,
  ) {
    const webhooks = await this.appWebhookRepository.getAll();

    const webhooksToRemove = webhooks.filter((webhook) => webhook.name === nextWebhookHandler.name);
    const webhooksToEnable = webhooks.filter((webhook) => webhook.name === prevWebhookName);

    for (const webhook of webhooksToRemove) {
      await this.deleteWebhookById(webhook.id);
    }

    for (const webhook of webhooksToEnable) {
      await this.appWebhookRepository.enable(webhook.id);
    }
  }
}

export function createAppWebhookMigrator(env: AuthData, options: AppWebhookMigratorOptions) {
  const client = createGraphQLClient({
    saleorApiUrl: env.saleorApiUrl,
    token: env.token,
  });
  const appWebhookRepository = new AppWebhookRepository(client);

  return new AppWebhookMigrator(
    {
      apiUrl: env.saleorApiUrl,
      appId: env.appId,
      appWebhookRepository,
    },
    options,
  );
}
