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

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

export const webhookRouter = createTRPCRouter({
  /** List all webhooks. */
  list: adminProcedure.query(async ({ ctx }) => {
    return ctx.db.webhook.findMany({
      orderBy: { createdAt: "desc" },
    });
  }),

  /** Get a single webhook by ID. */
  getById: adminProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const wh = await ctx.db.webhook.findUnique({ where: { id: input.id } });
      if (!wh) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
      }
      return wh;
    }),

  /** Create a new webhook. */
  create: adminProcedure
    .input(
      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),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const webhook = await ctx.db.webhook.create({
        data: {
          name: input.name,
          url: input.url,
          ...(input.secret !== undefined ? { secret: input.secret } : {}),
          events: input.events,
          isActive: input.isActive,
        },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Webhook",
        entityId: webhook.id,
        entityName: webhook.name,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: webhook as unknown as Record<string, unknown>,
        source: "ui",
      });

      return webhook;
    }),

  /** Update an existing webhook. */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        data: 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(),
        }),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const existing = await ctx.db.webhook.findUnique({ where: { id: input.id } });
      if (!existing) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
      }

      const updated = await ctx.db.webhook.update({
        where: { id: input.id },
        data: {
          ...(input.data.name !== undefined ? { name: input.data.name } : {}),
          ...(input.data.url !== undefined ? { url: input.data.url } : {}),
          ...(input.data.secret !== undefined ? { secret: input.data.secret } : {}),
          ...(input.data.events !== undefined ? { events: input.data.events } : {}),
          ...(input.data.isActive !== undefined ? { isActive: input.data.isActive } : {}),
        },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Webhook",
        entityId: input.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before: existing as unknown as Record<string, unknown>,
        after: updated as unknown as Record<string, unknown>,
        source: "ui",
      });

      return updated;
    }),

  /** Delete a webhook. */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const existing = await ctx.db.webhook.findUnique({ where: { id: input.id } });
      if (!existing) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
      }
      await ctx.db.webhook.delete({ where: { id: input.id } });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Webhook",
        entityId: input.id,
        entityName: existing.name,
        action: "DELETE",
        userId: ctx.dbUser?.id,
        before: existing as unknown as Record<string, unknown>,
        source: "ui",
      });
    }),

  /** Send a test payload to a webhook URL. */
  test: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const wh = await ctx.db.webhook.findUnique({ where: { id: input.id } });
      if (!wh) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
      }

      const testPayload = {
        event: "webhook.test",
        timestamp: new Date().toISOString(),
        payload: {
          webhookId: wh.id,
          webhookName: wh.name,
          message: "This is a test payload from CapaKraken.",
        },
      };

      const body = JSON.stringify(testPayload);

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

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

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

      let result: { success: boolean; statusCode: number; statusText: string };

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

      void createAuditEntry({
        db: ctx.db,
        entityType: "Webhook",
        entityId: wh.id,
        entityName: wh.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        summary: `Tested webhook (result: ${result.success ? "success" : "failed"})`,
        metadata: result as unknown as Record<string, unknown>,
        source: "ui",
      });

      return result;
    }),
});
