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

type BlueprintProcedureContext = Pick<TRPCContext, "db" | "dbUser">;

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

function withAuditUser(userId: string | undefined) {
  return userId ? { userId } : {};
}

async function getBlueprintOrThrow(ctx: BlueprintProcedureContext, id: string) {
  return findUniqueOrThrow(ctx.db.blueprint.findUnique({ where: { id } }), "Blueprint");
}

export const blueprintIdInputSchema = z.object({ id: z.string() });

export const blueprintIdentifierInputSchema = z.object({
  identifier: z.string().trim().min(1),
});

export const blueprintListInputSchema = z.object({
  target: z.nativeEnum(BlueprintTarget).optional(),
  isActive: z.boolean().optional().default(true),
});

export const blueprintUpdateInputSchema = z.object({
  id: z.string(),
  data: UpdateBlueprintSchema,
});

export const blueprintRolePresetsInputSchema = z.object({
  id: z.string(),
  rolePresets: z.array(z.unknown()),
});

export const blueprintBatchDeleteInputSchema = z.object({
  ids: z.array(z.string()).min(1).max(100),
});

export const blueprintGlobalFieldDefsInputSchema = z.object({
  target: z.nativeEnum(BlueprintTarget),
});

export const blueprintSetGlobalInputSchema = z.object({
  id: z.string(),
  isGlobal: z.boolean(),
});

type BlueprintIdInput = z.infer<typeof blueprintIdInputSchema>;
type BlueprintIdentifierInput = z.infer<typeof blueprintIdentifierInputSchema>;
type BlueprintListInput = z.infer<typeof blueprintListInputSchema>;
type BlueprintCreateInput = z.infer<typeof CreateBlueprintSchema>;
type BlueprintUpdateInput = z.infer<typeof blueprintUpdateInputSchema>;
type BlueprintRolePresetsInput = z.infer<typeof blueprintRolePresetsInputSchema>;
type BlueprintBatchDeleteInput = z.infer<typeof blueprintBatchDeleteInputSchema>;
type BlueprintGlobalFieldDefsInput = z.infer<typeof blueprintGlobalFieldDefsInputSchema>;
type BlueprintSetGlobalInput = z.infer<typeof blueprintSetGlobalInputSchema>;

export async function listBlueprintSummaries(ctx: BlueprintProcedureContext) {
  return ctx.db.blueprint.findMany({
    select: {
      id: true,
      name: true,
      _count: { select: { projects: true } },
    },
    orderBy: { name: "asc" },
  });
}

export async function listBlueprints(ctx: BlueprintProcedureContext, input: BlueprintListInput) {
  return ctx.db.blueprint.findMany({
    where: {
      ...(input.target ? { target: input.target } : {}),
      isActive: input.isActive,
    },
    orderBy: { name: "asc" },
  });
}

export async function getBlueprintById(ctx: BlueprintProcedureContext, input: BlueprintIdInput) {
  return getBlueprintOrThrow(ctx, input.id);
}

export async function resolveBlueprintByIdentifier(
  ctx: BlueprintProcedureContext,
  input: BlueprintIdentifierInput,
) {
  return findBlueprintByIdentifier<BlueprintIdentifierReadModel>(ctx.db, input.identifier, {
    select: {
      id: true,
      name: true,
      target: true,
      isActive: true,
    },
  });
}

export async function getBlueprintDetailByIdentifier(
  ctx: BlueprintProcedureContext,
  input: BlueprintIdentifierInput,
) {
  return findBlueprintByIdentifier<BlueprintDetailReadModel>(ctx.db, input.identifier, {});
}

export async function createBlueprint(ctx: BlueprintProcedureContext, input: BlueprintCreateInput) {
  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",
    ...withAuditUser(ctx.dbUser?.id),
    after: { name: input.name, target: input.target, description: input.description },
    source: "ui",
  });

  return blueprint;
}

export async function updateBlueprint(ctx: BlueprintProcedureContext, input: BlueprintUpdateInput) {
  const before = await getBlueprintOrThrow(ctx, input.id);

  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",
    ...withAuditUser(ctx.dbUser?.id),
    before: before as unknown as Record<string, unknown>,
    after: updated as unknown as Record<string, unknown>,
    source: "ui",
  });

  return updated;
}

export async function updateBlueprintRolePresets(
  ctx: BlueprintProcedureContext,
  input: BlueprintRolePresetsInput,
) {
  const before = await getBlueprintOrThrow(ctx, input.id);
  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",
    ...withAuditUser(ctx.dbUser?.id),
    before: { rolePresets: before.rolePresets },
    after: { rolePresets: input.rolePresets },
    source: "ui",
    summary: "Updated role presets",
  });

  return updated;
}

export async function deleteBlueprint(ctx: BlueprintProcedureContext, input: BlueprintIdInput) {
  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",
    ...withAuditUser(ctx.dbUser?.id),
    source: "ui",
  });

  return deleted;
}

export async function batchDeleteBlueprints(
  ctx: BlueprintProcedureContext,
  input: BlueprintBatchDeleteInput,
) {
  const updated = await ctx.db.$transaction(
    input.ids.map((id) =>
      ctx.db.blueprint.update({
        where: { id },
        data: { isActive: false },
      }),
    ),
  );

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

  return { count: updated.length };
}

export async function getGlobalBlueprintFieldDefs(
  ctx: BlueprintProcedureContext,
  input: BlueprintGlobalFieldDefsInput,
) {
  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);
}

export async function setBlueprintGlobal(
  ctx: BlueprintProcedureContext,
  input: BlueprintSetGlobalInput,
) {
  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",
    ...withAuditUser(ctx.dbUser?.id),
    after: { isGlobal: input.isGlobal },
    source: "ui",
    summary: input.isGlobal ? "Set blueprint as global" : "Removed global flag from blueprint",
  });

  return updated;
}
