import { TRPCError } from "@trpc/server";
import { z } from "zod";

import * as webhookRepo from "@kan/db/repository/webhook.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { webhookEvents } from "@kan/db/schema";

import { createTRPCRouter, protectedProcedure } from "../trpc";
import { assertPermission } from "../utils/permissions";
import {
  webhookUrlSchema,
  sendWebhookToUrl,
  createCardWebhookPayload,
} from "../utils/webhook";

const webhookEventSchema = z.enum(webhookEvents);

export const webhookRouter = createTRPCRouter({
  list: protectedProcedure
    .meta({
      openapi: {
        summary: "Get all webhooks for a workspace",
        method: "GET",
        path: "/workspaces/{workspacePublicId}/webhooks",
        description: "Retrieves all webhooks configured for a workspace",
        tags: ["Webhooks"],
        protect: true,
      },
    })
    .input(z.object({ workspacePublicId: z.string().min(12) }))
    .output(
      z.array(
        z.object({
          publicId: z.string(),
          name: z.string(),
          url: z.string(),
          events: z.array(webhookEventSchema),
          active: z.boolean(),
          createdAt: z.date(),
          updatedAt: z.date().nullable(),
        }),
      ),
    )
    .query(async ({ ctx, input }) => {
      const userId = ctx.user?.id;

      if (!userId)
        throw new TRPCError({
          message: "User not authenticated",
          code: "UNAUTHORIZED",
        });

      const workspace = await workspaceRepo.getByPublicId(
        ctx.db,
        input.workspacePublicId,
      );

      if (!workspace)
        throw new TRPCError({
          message: "Workspace not found",
          code: "NOT_FOUND",
        });

      await assertPermission(ctx.db, userId, workspace.id, "workspace:manage");

      return webhookRepo.getAllByWorkspaceId(ctx.db, workspace.id);
    }),

  create: protectedProcedure
    .meta({
      openapi: {
        summary: "Create a webhook",
        method: "POST",
        path: "/workspaces/{workspacePublicId}/webhooks",
        description: "Creates a new webhook for a workspace",
        tags: ["Webhooks"],
        protect: true,
      },
    })
    .input(
      z.object({
        workspacePublicId: z.string().min(12),
        name: z.string().min(1).max(255),
        url: webhookUrlSchema,
        secret: z.string().max(512).optional(),
        events: z.array(webhookEventSchema).min(1),
      }),
    )
    .output(
      z.object({
        publicId: z.string(),
        name: z.string(),
        url: z.string(),
        events: z.array(webhookEventSchema),
        active: z.boolean(),
        createdAt: z.date(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const userId = ctx.user?.id;

      if (!userId)
        throw new TRPCError({
          message: "User not authenticated",
          code: "UNAUTHORIZED",
        });

      const workspace = await workspaceRepo.getByPublicId(
        ctx.db,
        input.workspacePublicId,
      );

      if (!workspace)
        throw new TRPCError({
          message: "Workspace not found",
          code: "NOT_FOUND",
        });

      await assertPermission(ctx.db, userId, workspace.id, "workspace:manage");

      const result = await webhookRepo.create(ctx.db, {
        workspaceId: workspace.id,
        name: input.name,
        url: input.url,
        secret: input.secret,
        events: input.events,
        createdBy: userId,
      });

      if (!result)
        throw new TRPCError({
          message: "Unable to create webhook",
          code: "INTERNAL_SERVER_ERROR",
        });

      return result;
    }),

  update: protectedProcedure
    .meta({
      openapi: {
        summary: "Update a webhook",
        method: "PUT",
        path: "/workspaces/{workspacePublicId}/webhooks/{webhookPublicId}",
        description: "Updates a webhook by its public ID",
        tags: ["Webhooks"],
        protect: true,
      },
    })
    .input(
      z.object({
        workspacePublicId: z.string().min(12),
        webhookPublicId: z.string().min(12),
        name: z.string().min(1).max(255).optional(),
        url: webhookUrlSchema.optional(),
        secret: z.string().max(512).optional(),
        events: z.array(webhookEventSchema).min(1).optional(),
        active: z.boolean().optional(),
      }),
    )
    .output(
      z.object({
        publicId: z.string(),
        name: z.string(),
        url: z.string(),
        events: z.array(webhookEventSchema),
        active: z.boolean(),
        createdAt: z.date(),
        updatedAt: z.date().nullable(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const userId = ctx.user?.id;

      if (!userId)
        throw new TRPCError({
          message: "User not authenticated",
          code: "UNAUTHORIZED",
        });

      const workspace = await workspaceRepo.getByPublicId(
        ctx.db,
        input.workspacePublicId,
      );

      if (!workspace)
        throw new TRPCError({
          message: "Workspace not found",
          code: "NOT_FOUND",
        });

      await assertPermission(ctx.db, userId, workspace.id, "workspace:manage");

      const webhook = await webhookRepo.getByPublicId(
        ctx.db,
        input.webhookPublicId,
      );

      if (!webhook || webhook.workspaceId !== workspace.id)
        throw new TRPCError({
          message: "Webhook not found",
          code: "NOT_FOUND",
        });

      const result = await webhookRepo.update(ctx.db, input.webhookPublicId, {
        name: input.name,
        url: input.url,
        secret: input.secret,
        events: input.events,
        active: input.active,
      });

      if (!result)
        throw new TRPCError({
          message: "Unable to update webhook",
          code: "INTERNAL_SERVER_ERROR",
        });

      return result;
    }),

  delete: protectedProcedure
    .meta({
      openapi: {
        summary: "Delete a webhook",
        method: "DELETE",
        path: "/workspaces/{workspacePublicId}/webhooks/{webhookPublicId}",
        description: "Deletes a webhook by its public ID",
        tags: ["Webhooks"],
        protect: true,
      },
    })
    .input(
      z.object({
        workspacePublicId: z.string().min(12),
        webhookPublicId: z.string().min(12),
      }),
    )
    .output(z.object({ success: z.boolean() }))
    .mutation(async ({ ctx, input }) => {
      const userId = ctx.user?.id;

      if (!userId)
        throw new TRPCError({
          message: "User not authenticated",
          code: "UNAUTHORIZED",
        });

      const workspace = await workspaceRepo.getByPublicId(
        ctx.db,
        input.workspacePublicId,
      );

      if (!workspace)
        throw new TRPCError({
          message: "Workspace not found",
          code: "NOT_FOUND",
        });

      await assertPermission(ctx.db, userId, workspace.id, "workspace:manage");

      const webhook = await webhookRepo.getByPublicId(
        ctx.db,
        input.webhookPublicId,
      );

      if (!webhook || webhook.workspaceId !== workspace.id)
        throw new TRPCError({
          message: "Webhook not found",
          code: "NOT_FOUND",
        });

      await webhookRepo.hardDelete(ctx.db, input.webhookPublicId);

      return { success: true };
    }),

  test: protectedProcedure
    .meta({
      openapi: {
        summary: "Test a webhook",
        method: "POST",
        path: "/workspaces/{workspacePublicId}/webhooks/{webhookPublicId}/test",
        description: "Sends a test payload to a webhook",
        tags: ["Webhooks"],
        protect: true,
      },
    })
    .input(
      z.object({
        workspacePublicId: z.string().min(12),
        webhookPublicId: z.string().min(12),
      }),
    )
    .output(
      z.object({
        success: z.boolean(),
        statusCode: z.number().optional(),
        error: z.string().optional(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const userId = ctx.user?.id;

      if (!userId)
        throw new TRPCError({
          message: "User not authenticated",
          code: "UNAUTHORIZED",
        });

      const workspace = await workspaceRepo.getByPublicId(
        ctx.db,
        input.workspacePublicId,
      );

      if (!workspace)
        throw new TRPCError({
          message: "Workspace not found",
          code: "NOT_FOUND",
        });

      await assertPermission(ctx.db, userId, workspace.id, "workspace:manage");

      const webhook = await webhookRepo.getByPublicId(
        ctx.db,
        input.webhookPublicId,
      );

      if (!webhook || webhook.workspaceId !== workspace.id)
        throw new TRPCError({
          message: "Webhook not found",
          code: "NOT_FOUND",
        });

      const testPayload = createCardWebhookPayload("card.created", {
        id: "test-card-id",
        title: "Test Card",
        description: "This is a test webhook payload",
        dueDate: null,
        listId: "test-list-id",
      }, {
        boardId: "test-board-id",
        boardName: "Test Board",
        listName: "Test List",
        user: {
          id: userId,
          name: ctx.user?.name ?? "Test User",
        },
      });

      const result = await sendWebhookToUrl(
        webhook.url,
        webhook.secret ?? undefined,
        testPayload,
      );

      return result;
    }),
});
