import { BlueprintTarget, CreateBlueprintSchema, UpdateBlueprintSchema, type BlueprintFieldDefinition } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { adminProcedure, createTRPCRouter, planningReadProcedure, protectedProcedure } from "../trpc.js";
import { createAuditEntry } from "../lib/audit.js";

export const blueprintRouter = createTRPCRouter({
  listSummaries: planningReadProcedure
    .query(async ({ ctx }) => {
      return ctx.db.blueprint.findMany({
        select: {
          id: true,
          name: true,
          _count: { select: { projects: true } },
        },
        orderBy: { name: "asc" },
      });
    }),

  list: planningReadProcedure
    .input(
      z.object({
        target: z.nativeEnum(BlueprintTarget).optional(),
        isActive: z.boolean().optional().default(true),
      }),
    )
    .query(async ({ ctx, input }) => {
      return ctx.db.blueprint.findMany({
        where: {
          ...(input.target ? { target: input.target } : {}),
          isActive: input.isActive,
        },
        orderBy: { name: "asc" },
      });
    }),

  getById: planningReadProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const blueprint = await findUniqueOrThrow(
        ctx.db.blueprint.findUnique({ where: { id: input.id } }),
        "Blueprint",
      );
      return blueprint;
    }),

  resolveByIdentifier: protectedProcedure
    .input(z.object({ identifier: z.string().trim().min(1) }))
    .query(async ({ ctx, input }) => {
      const identifier = input.identifier.trim();
      const select = {
        id: true,
        name: true,
        target: true,
        isActive: true,
      } as const;

      let blueprint = await ctx.db.blueprint.findUnique({
        where: { id: identifier },
        select,
      });

      if (!blueprint) {
        blueprint = await ctx.db.blueprint.findFirst({
          where: { name: { equals: identifier, mode: "insensitive" } },
          select,
        });
      }

      if (!blueprint) {
        blueprint = await ctx.db.blueprint.findFirst({
          where: { name: { contains: identifier, mode: "insensitive" } },
          select,
        });
      }

      if (!blueprint) {
        throw new TRPCError({ code: "NOT_FOUND", message: `Blueprint not found: ${identifier}` });
      }

      return blueprint;
    }),

  getByIdentifier: planningReadProcedure
    .input(z.object({ identifier: z.string().trim().min(1) }))
    .query(async ({ ctx, input }) => {
      const identifier = input.identifier.trim();
      let blueprint = await ctx.db.blueprint.findUnique({
        where: { id: identifier },
      });

      if (!blueprint) {
        blueprint = await ctx.db.blueprint.findFirst({
          where: { name: { equals: identifier, mode: "insensitive" } },
        });
      }

      if (!blueprint) {
        blueprint = await ctx.db.blueprint.findFirst({
          where: { name: { contains: identifier, mode: "insensitive" } },
        });
      }

      if (!blueprint) {
        throw new TRPCError({ code: "NOT_FOUND", message: `Blueprint not found: ${identifier}` });
      }

      return blueprint;
    }),

  create: adminProcedure
    .input(CreateBlueprintSchema)
    .mutation(async ({ ctx, input }) => {
      const blueprint = await ctx.db.blueprint.create({
        data: {
          name: input.name,
          target: input.target,
          description: input.description,
          fieldDefs: input.fieldDefs as unknown as import("@capakraken/db").Prisma.InputJsonValue,
          defaults: input.defaults as unknown as import("@capakraken/db").Prisma.InputJsonValue,
          validationRules: input.validationRules as unknown as import("@capakraken/db").Prisma.InputJsonValue,
        } as unknown as Parameters<typeof ctx.db.blueprint.create>[0]["data"],
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Blueprint",
        entityId: blueprint.id,
        entityName: blueprint.name,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: { name: input.name, target: input.target, description: input.description },
        source: "ui",
      });

      return blueprint;
    }),

  update: adminProcedure
    .input(z.object({ id: z.string(), data: UpdateBlueprintSchema }))
    .mutation(async ({ ctx, input }) => {
      const before = await findUniqueOrThrow(
        ctx.db.blueprint.findUnique({ where: { id: input.id } }),
        "Blueprint",
      );

      const updated = await ctx.db.blueprint.update({
        where: { id: input.id },
        data: {
          ...(input.data.name !== undefined ? { name: input.data.name } : {}),
          ...(input.data.description !== undefined ? { description: input.data.description } : {}),
          ...(input.data.fieldDefs !== undefined ? { fieldDefs: input.data.fieldDefs as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}),
          ...(input.data.defaults !== undefined ? { defaults: input.data.defaults as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}),
          ...(input.data.validationRules !== undefined ? { validationRules: input.data.validationRules as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}),
        } as unknown as Parameters<typeof ctx.db.blueprint.update>[0]["data"],
      });

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

      return updated;
    }),

  /** Dedicated mutation for saving role presets — separate from field defs to avoid Zod depth issues */
  updateRolePresets: adminProcedure
    .input(z.object({ id: z.string(), rolePresets: z.array(z.unknown()) }))
    .mutation(async ({ ctx, input }) => {
      const before = await findUniqueOrThrow(
        ctx.db.blueprint.findUnique({ where: { id: input.id } }),
        "Blueprint",
      );
      const updated = await ctx.db.blueprint.update({
        where: { id: input.id },
        data: { rolePresets: input.rolePresets as unknown as import("@capakraken/db").Prisma.InputJsonValue },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Blueprint",
        entityId: input.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before: { rolePresets: before.rolePresets },
        after: { rolePresets: input.rolePresets },
        source: "ui",
        summary: "Updated role presets",
      });

      return updated;
    }),

  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      // Soft delete — mark as inactive
      const deleted = await ctx.db.blueprint.update({
        where: { id: input.id },
        data: { isActive: false },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Blueprint",
        entityId: input.id,
        entityName: deleted.name,
        action: "DELETE",
        userId: ctx.dbUser?.id,
        source: "ui",
      });

      return deleted;
    }),

  batchDelete: adminProcedure
    .input(z.object({ ids: z.array(z.string()).min(1).max(100) }))
    .mutation(async ({ ctx, input }) => {
      // Soft delete
      const updated = await ctx.db.$transaction(
        input.ids.map((id) =>
          ctx.db.blueprint.update({ where: { id }, data: { isActive: false } }),
        ),
      );

      for (const bp of updated) {
        void createAuditEntry({
          db: ctx.db,
          entityType: "Blueprint",
          entityId: bp.id,
          entityName: bp.name,
          action: "DELETE",
          userId: ctx.dbUser?.id,
          source: "ui",
        });
      }

      return { count: updated.length };
    }),

  getGlobalFieldDefs: planningReadProcedure
    .input(z.object({ target: z.nativeEnum(BlueprintTarget) }))
    .query(async ({ ctx, input }) => {
      const blueprints = await ctx.db.blueprint.findMany({
        where: { target: input.target, isGlobal: true, isActive: true },
        select: { id: true, name: true, fieldDefs: true },
      });
      return blueprints.flatMap((b) =>
        (b.fieldDefs as unknown as BlueprintFieldDefinition[]).map((f) => ({
          ...f,
          blueprintId: b.id,
          blueprintName: b.name,
        })),
      );
    }),

  setGlobal: adminProcedure
    .input(z.object({ id: z.string(), isGlobal: z.boolean() }))
    .mutation(async ({ ctx, input }) => {
      const updated = await ctx.db.blueprint.update({
        where: { id: input.id },
        data: { isGlobal: input.isGlobal },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Blueprint",
        entityId: input.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        after: { isGlobal: input.isGlobal },
        source: "ui",
        summary: input.isGlobal ? "Set blueprint as global" : "Removed global flag from blueprint",
      });

      return updated;
    }),
});
