import { BlueprintTarget, CreateBlueprintSchema, UpdateBlueprintSchema } from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { adminProcedure, createTRPCRouter, planningReadProcedure, protectedProcedure } from "../trpc.js";
import { createAuditEntry } from "../lib/audit.js";
import {
  buildBlueprintCreateData,
  buildBlueprintRolePresetsUpdateData,
  buildBlueprintUpdateData,
  expandGlobalBlueprintFieldDefs,
  findBlueprintByIdentifier,
} from "./blueprint-support.js";

type BlueprintIdentifierReadModel = {
  id: string;
  name: string;
  target: BlueprintTarget;
  isActive: boolean;
};

type BlueprintDetailReadModel = {
  id: string;
  name: string;
  target: BlueprintTarget;
  description: string | null;
  fieldDefs: unknown;
  defaults: unknown;
  validationRules: unknown;
  rolePresets: unknown;
  isActive: boolean;
};

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 }) => {
      return findBlueprintByIdentifier<BlueprintIdentifierReadModel>(ctx.db, input.identifier, {
        select: {
          id: true,
          name: true,
          target: true,
          isActive: true,
        },
      });
    }),

  getByIdentifier: planningReadProcedure
    .input(z.object({ identifier: z.string().trim().min(1) }))
    .query(async ({ ctx, input }) => {
      return findBlueprintByIdentifier<BlueprintDetailReadModel>(ctx.db, input.identifier, {});
    }),

  create: adminProcedure
    .input(CreateBlueprintSchema)
    .mutation(async ({ ctx, input }) => {
      const blueprint = await ctx.db.blueprint.create({
        data: buildBlueprintCreateData(input),
      });

      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: buildBlueprintUpdateData(input.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: buildBlueprintRolePresetsUpdateData(input.rolePresets),
      });

      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 expandGlobalBlueprintFieldDefs(blueprints);
    }),

  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;
    }),
});
