import { z } from "zod";
import type { PrismaClient } from "~/db.server";
import { $transaction, prisma } from "~/db.server";
import { logger } from "~/services/logger.server";
import { workerQueue } from "~/services/worker.server";
import { RuntimeEnvironmentType } from "~/database-types";
import type { ParamsSchema } from "./route";

type TriggerEndpointDeployHookOptions = z.infer<typeof ParamsSchema> & {
  body?: any;
};

export class TriggerEndpointIndexHookService {
  #prismaClient: PrismaClient;

  constructor(prismaClient: PrismaClient = prisma) {
    this.#prismaClient = prismaClient;
  }

  public async call({
    environmentId,
    endpointSlug,
    indexHookIdentifier,
    body,
  }: TriggerEndpointDeployHookOptions) {
    logger.debug("TriggerEndpointIndexHookService.call", {
      environmentId,
      endpointSlug,
      indexHookIdentifier,
      body,
    });

    await $transaction(this.#prismaClient, async (tx) => {
      const endpoint = await tx.endpoint.findUnique({
        where: {
          environmentId_slug: {
            environmentId,
            slug: endpointSlug,
          },
        },
        include: {
          environment: true,
        },
      });

      if (!endpoint || !endpoint.url) {
        throw new Error("Endpoint not found");
      }

      if (endpoint.indexingHookIdentifier !== indexHookIdentifier) {
        throw new Error("Index hook identifier is invalid");
      }

      const reason = parseReasonFromBody(body);

      const index = await tx.endpointIndex.create({
        data: {
          endpointId: endpoint.id,
          status: "PENDING",
          source: "HOOK",
          reason,
          sourceData: body,
        },
      });

      // Index the endpoint in 5 seconds from now
      await workerQueue.enqueue(
        "performEndpointIndexing",
        {
          id: index.id,
        },
        {
          runAt: new Date(Date.now() + 5000),
          maxAttempts:
            endpoint.environment.type === RuntimeEnvironmentType.DEVELOPMENT ? 1 : undefined,
          tx,
        }
      );
    });
  }
}

function parseReasonFromBody(body: any): string | undefined {
  const vercelDeployment = VercelDeploymentWebhookSchema.safeParse(body);

  if (!vercelDeployment.success) {
    return;
  }

  const { payload, type } = vercelDeployment.data;

  if (type !== "deployment.succeeded") {
    return;
  }

  const githubMeta = VercelDeploymentGithubMetaSchema.safeParse(payload.deployment.meta);

  if (!githubMeta.success) {
    return `Vercel project ${payload.deployment.name} was deployed to ${payload.deployment.url}`;
  }

  return `"${githubMeta.data.githubCommitMessage}" was deployed from ${
    githubMeta.data.githubCommitRef
  } (${githubMeta.data.githubCommitSha.slice(0, 7)}) to ${payload.deployment.name}`;
}

// Example payload: https://jsonhero.io/j/fhIwXEFmi7qa
const VercelDeploymentWebhookSchema = z.object({
  id: z.string(),
  payload: z.object({
    user: z.object({
      id: z.string(),
    }),
    team: z.object({
      id: z.string(),
    }),
    deployment: z.object({
      id: z.string(),
      meta: z.record(z.any()),
      name: z.string(),
      url: z.string(),
      inspectorUrl: z.string(),
    }),
    links: z.object({
      deployment: z.string(),
      project: z.string(),
    }),
    name: z.string(),
    plan: z.string(),
    project: z.object({
      id: z.string(),
    }),
    regions: z.array(z.string()),
    target: z.string(),
    type: z.string(),
    url: z.string(),
  }),
  createdAt: z.number(),
  type: z.enum([
    "deployment.succeeded",
    "deployment.failed",
    "deployment.ready",
    "deployment.created",
    "deployment.error",
    "deployment.canceled",
  ]),
});

const VercelDeploymentGithubMetaSchema = z.object({
  githubCommitAuthorName: z.string(),
  githubCommitMessage: z.string(),
  githubCommitOrg: z.string(),
  githubCommitRef: z.string(),
  githubCommitRepo: z.string(),
  githubCommitSha: z.string(),
  githubDeployment: z.string(),
  githubOrg: z.string(),
  githubRepo: z.string(),
  githubRepoOwnerType: z.string(),
  githubCommitRepoId: z.string(),
  githubRepoId: z.string(),
  githubRepoVisibility: z.string(),
  githubCommitAuthorLogin: z.string(),
  branchAlias: z.string(),
});
