import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { WEBHOOK_EVENTS } from "../lib/webhook-dispatcher.js";

export const webhookEventEnum = z.enum(WEBHOOK_EVENTS as unknown as [string, ...string[]]);

export const createWebhookInputSchema = z.object({
  name: z.string().min(1).max(200),
  url: z.string().url(),
  secret: z.string().optional(),
  events: z.array(webhookEventEnum).min(1),
  isActive: z.boolean().default(true),
});

export const updateWebhookInputSchema = z.object({
  name: z.string().min(1).max(200).optional(),
  url: z.string().url().optional(),
  secret: z.string().nullish(),
  events: z.array(webhookEventEnum).min(1).optional(),
  isActive: z.boolean().optional(),
});

type WebhookRecord = {
  id: string;
  name: string;
  url: string;
  secret?: string | null;
  events?: string[];
  isActive?: boolean;
};

type WebhookDb = {
  webhook: {
    findUnique: (args: { where: { id: string } }) => Promise<WebhookRecord | null>;
  };
};

export function buildWebhookCreateData(
  input: z.infer<typeof createWebhookInputSchema>,
) {
  return {
    name: input.name,
    url: input.url,
    ...(input.secret !== undefined ? { secret: input.secret } : {}),
    events: input.events,
    isActive: input.isActive,
  };
}

export function buildWebhookUpdateData(
  input: z.infer<typeof updateWebhookInputSchema>,
) {
  return {
    ...(input.name !== undefined ? { name: input.name } : {}),
    ...(input.url !== undefined ? { url: input.url } : {}),
    ...(input.secret !== undefined ? { secret: input.secret } : {}),
    ...(input.events !== undefined ? { events: input.events } : {}),
    ...(input.isActive !== undefined ? { isActive: input.isActive } : {}),
  };
}

export async function loadWebhookOrThrow(
  db: WebhookDb,
  id: string,
) {
  const webhook = await db.webhook.findUnique({ where: { id } });
  if (!webhook) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
  }
  return webhook;
}

export async function buildWebhookTestRequest(
  webhook: Pick<WebhookRecord, "id" | "name" | "secret">,
  timestamp = new Date().toISOString(),
) {
  const payload = {
    event: "webhook.test",
    timestamp,
    payload: {
      webhookId: webhook.id,
      webhookName: webhook.name,
      message: "This is a test payload from CapaKraken.",
    },
  };

  const body = JSON.stringify(payload);
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    "X-Webhook-Event": "webhook.test",
  };

  if (webhook.secret) {
    const { createHmac } = await import("node:crypto");
    headers["X-Webhook-Signature"] = createHmac("sha256", webhook.secret)
      .update(body)
      .digest("hex");
  }

  return { body, headers };
}

export async function sendWebhookTestRequest(
  webhook: Pick<WebhookRecord, "id" | "name" | "url" | "secret">,
  options: {
    fetchImpl?: typeof fetch;
    timeoutMs?: number;
    timestamp?: string;
  } = {},
): Promise<{ success: boolean; statusCode: number; statusText: string }> {
  const fetchImpl = options.fetchImpl ?? fetch;
  const timeoutMs = options.timeoutMs ?? 5_000;
  const { body, headers } = await buildWebhookTestRequest(webhook, options.timestamp);

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetchImpl(webhook.url, {
      method: "POST",
      headers,
      body,
      signal: controller.signal,
    });
    return {
      success: response.ok,
      statusCode: response.status,
      statusText: response.statusText,
    };
  } catch (error) {
    return {
      success: false,
      statusCode: 0,
      statusText: error instanceof Error ? error.message : "Unknown error",
    };
  } finally {
    clearTimeout(timeout);
  }
}
