import {
  type ProjectAlertChannel,
  type ProjectAlertType,
  type RuntimeEnvironmentType,
} from "@trigger.dev/database";
import { nanoid } from "nanoid";
import { env } from "~/env.server";
import { findProjectByRef } from "~/models/project.server";
import { encryptSecret } from "~/services/secrets/secretStore.server";
import { alertsWorker } from "~/v3/alertsWorker.server";
import { generateFriendlyId } from "~/v3/friendlyIdentifiers";
import { BaseService, ServiceValidationError } from "../baseService.server";
import { assertSafeWebhookUrl, UnsafeWebhookUrlError } from "./safeWebhookUrl.server";

export type CreateAlertChannelOptions = {
  name: string;
  alertTypes: ProjectAlertType[];
  environmentTypes: RuntimeEnvironmentType[];
  deduplicationKey?: string;
  channel:
    | {
        type: "EMAIL";
        email: string;
      }
    | {
        type: "WEBHOOK";
        url: string;
        secret?: string;
      }
    | {
        type: "SLACK";
        channelId: string;
        channelName: string;
        integrationId: string | undefined;
      };
};

export class CreateAlertChannelService extends BaseService {
  public async call(
    projectRef: string,
    userId: string,
    options: CreateAlertChannelOptions
  ): Promise<ProjectAlertChannel> {
    const project = await findProjectByRef(projectRef, userId);

    if (!project) {
      throw new ServiceValidationError("Project not found");
    }

    // Validate webhook URLs here (not per-route) so every caller is covered.
    // Delivery re-validates at connect time via safeWebhookFetch.
    if (options.channel.type === "WEBHOOK") {
      try {
        await assertSafeWebhookUrl(options.channel.url);
      } catch (error) {
        if (error instanceof UnsafeWebhookUrlError) {
          throw new ServiceValidationError(error.message);
        }
        throw error;
      }
    }

    const environmentTypes =
      options.environmentTypes.length === 0
        ? (["STAGING", "PRODUCTION"] satisfies RuntimeEnvironmentType[])
        : options.environmentTypes;

    const existingAlertChannel = options.deduplicationKey
      ? await this._prisma.projectAlertChannel.findFirst({
          where: {
            projectId: project.id,
            deduplicationKey: options.deduplicationKey,
          },
        })
      : undefined;

    if (existingAlertChannel) {
      const updated = await this._prisma.projectAlertChannel.update({
        where: { id: existingAlertChannel.id },
        data: {
          name: options.name,
          alertTypes: options.alertTypes,
          type: options.channel.type,
          properties: await this.#createProperties(options.channel),
          environmentTypes,
          enabled: true,
        },
      });

      if (options.alertTypes.includes("ERROR_GROUP")) {
        await this.#scheduleErrorAlertEvaluation(project.id);
      }

      return updated;
    }

    const alertChannel = await this._prisma.projectAlertChannel.create({
      data: {
        friendlyId: generateFriendlyId("alert_channel"),
        name: options.name,
        alertTypes: options.alertTypes,
        projectId: project.id,
        type: options.channel.type,
        properties: await this.#createProperties(options.channel),
        enabled: true,
        deduplicationKey: options.deduplicationKey,
        userProvidedDeduplicationKey: options.deduplicationKey ? true : false,
        environmentTypes,
      },
    });

    if (options.alertTypes.includes("ERROR_GROUP")) {
      await this.#scheduleErrorAlertEvaluation(project.id);
    }

    return alertChannel;
  }

  async #scheduleErrorAlertEvaluation(projectId: string): Promise<void> {
    await alertsWorker.enqueue({
      id: `evaluateErrorAlerts:${projectId}`,
      job: "v3.evaluateErrorAlerts",
      payload: {
        projectId,
        scheduledAt: Date.now(),
      },
    });
  }

  async #createProperties(channel: CreateAlertChannelOptions["channel"]) {
    switch (channel.type) {
      case "EMAIL":
        return {
          email: channel.email,
        };
      case "WEBHOOK":
        return {
          url: channel.url,
          secret: await encryptSecret(env.ENCRYPTION_KEY, channel.secret ?? nanoid()),
          version: "v2",
        };
      case "SLACK":
        return {
          channelId: channel.channelId,
          channelName: channel.channelName,
          integrationId: channel.integrationId,
        };
    }
  }
}
