import {
  ImportBatchStatus,
  StagedRecordStatus,
  DispoStagedRecordType,
} from "@capakraken/db";
import {
  assessDispoImportReadiness,
  commitDispoImportBatch,
  stageDispoImportBatch,
} from "@capakraken/application";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { adminProcedure, createTRPCRouter } from "../trpc.js";
import { createAuditEntry } from "../lib/audit.js";

// ─── Shared schemas ──────────────────────────────────────────────────────────

const paginationSchema = z.object({
  cursor: z.string().optional(),
  limit: z.number().int().min(1).max(200).default(50),
});

const importBatchStatusSchema = z.nativeEnum(ImportBatchStatus);
const stagedRecordStatusSchema = z.nativeEnum(StagedRecordStatus);
const stagedRecordTypeSchema = z.nativeEnum(DispoStagedRecordType);
const workbookPathSchema = z
  .string()
  .trim()
  .min(1, "Workbook path is required.")
  .refine((value) => value.toLowerCase().endsWith(".xlsx"), {
    message: "Only .xlsx workbook paths are supported.",
  });

// ─── Router ──────────────────────────────────────────────────────────────────

export const dispoRouter = createTRPCRouter({
  // ── 1. stageImportBatch ──────────────────────────────────────────────────

  stageImportBatch: adminProcedure
    .input(
      z.object({
        chargeabilityWorkbookPath: workbookPathSchema,
        costWorkbookPath: workbookPathSchema.optional(),
        notes: z.string().nullish(),
        planningWorkbookPath: workbookPathSchema,
        referenceWorkbookPath: workbookPathSchema,
        rosterWorkbookPath: workbookPathSchema.optional(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      return stageDispoImportBatch(ctx.db, {
        chargeabilityWorkbookPath: input.chargeabilityWorkbookPath,
        planningWorkbookPath: input.planningWorkbookPath,
        referenceWorkbookPath: input.referenceWorkbookPath,
        ...(input.costWorkbookPath !== undefined ? { costWorkbookPath: input.costWorkbookPath } : {}),
        ...(input.notes !== undefined ? { notes: input.notes } : {}),
        ...(input.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: input.rosterWorkbookPath } : {}),
      });
    }),

  // ── 2. validateImportBatch ───────────────────────────────────────────────

  validateImportBatch: adminProcedure
    .input(
      z.object({
        chargeabilityWorkbookPath: workbookPathSchema,
        costWorkbookPath: workbookPathSchema.optional(),
        importBatchId: z.string().optional(),
        notes: z.string().nullish(),
        planningWorkbookPath: workbookPathSchema,
        referenceWorkbookPath: workbookPathSchema,
        rosterWorkbookPath: workbookPathSchema.optional(),
      }),
    )
    .query(async ({ input }) => {
      return assessDispoImportReadiness({
        chargeabilityWorkbookPath: input.chargeabilityWorkbookPath,
        planningWorkbookPath: input.planningWorkbookPath,
        referenceWorkbookPath: input.referenceWorkbookPath,
        ...(input.costWorkbookPath !== undefined ? { costWorkbookPath: input.costWorkbookPath } : {}),
        ...(input.importBatchId !== undefined ? { importBatchId: input.importBatchId } : {}),
        ...(input.notes !== undefined ? { notes: input.notes } : {}),
        ...(input.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: input.rosterWorkbookPath } : {}),
      });
    }),

  // ── 3. listImportBatches ─────────────────────────────────────────────────

  listImportBatches: adminProcedure
    .input(
      paginationSchema.extend({
        status: importBatchStatusSchema.optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { cursor, limit, status } = input;

      const items = await ctx.db.importBatch.findMany({
        where: {
          ...(status !== undefined ? { status } : {}),
        },
        orderBy: { createdAt: "desc" },
        take: limit + 1,
        ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      });

      let nextCursor: string | undefined;
      if (items.length > limit) {
        const next = items.pop();
        nextCursor = next?.id;
      }

      return { items, nextCursor };
    }),

  // ── 4. getImportBatch ────────────────────────────────────────────────────

  getImportBatch: adminProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const batch = await ctx.db.importBatch.findUnique({
        where: { id: input.id },
      });

      if (!batch) {
        throw new TRPCError({ code: "NOT_FOUND", message: `Import batch "${input.id}" not found` });
      }

      const [
        resourceCount,
        clientCount,
        projectCount,
        assignmentCount,
        vacationCount,
        availabilityRuleCount,
        unresolvedCount,
      ] = await Promise.all([
        ctx.db.stagedResource.count({ where: { importBatchId: input.id } }),
        ctx.db.stagedClient.count({ where: { importBatchId: input.id } }),
        ctx.db.stagedProject.count({ where: { importBatchId: input.id } }),
        ctx.db.stagedAssignment.count({ where: { importBatchId: input.id } }),
        ctx.db.stagedVacation.count({ where: { importBatchId: input.id } }),
        ctx.db.stagedAvailabilityRule.count({ where: { importBatchId: input.id } }),
        ctx.db.stagedUnresolvedRecord.count({ where: { importBatchId: input.id } }),
      ]);

      return {
        ...batch,
        counts: {
          assignmentCount,
          availabilityRuleCount,
          clientCount,
          projectCount,
          resourceCount,
          unresolvedCount,
          vacationCount,
        },
      };
    }),

  // ── 5. cancelImportBatch ─────────────────────────────────────────────────

  cancelImportBatch: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const batch = await ctx.db.importBatch.findUnique({
        where: { id: input.id },
        select: { id: true, status: true },
      });

      if (!batch) {
        throw new TRPCError({ code: "NOT_FOUND", message: `Import batch "${input.id}" not found` });
      }

      const terminalStatuses: ImportBatchStatus[] = [
        ImportBatchStatus.COMMITTED,
        ImportBatchStatus.CANCELLED,
      ];
      if (terminalStatuses.includes(batch.status)) {
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: `Cannot cancel batch in status "${batch.status}"`,
        });
      }

      const cancelled = await ctx.db.importBatch.update({
        where: { id: input.id },
        data: { status: ImportBatchStatus.CANCELLED },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "ImportBatch",
        entityId: input.id,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        summary: "Cancelled import batch",
        after: cancelled as unknown as Record<string, unknown>,
        source: "ui",
      });

      return cancelled;
    }),

  // ── 6. listStagedResources ───────────────────────────────────────────────

  listStagedResources: adminProcedure
    .input(
      paginationSchema.extend({
        importBatchId: z.string(),
        status: stagedRecordStatusSchema.optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { cursor, importBatchId, limit, status } = input;

      const items = await ctx.db.stagedResource.findMany({
        where: {
          importBatchId,
          ...(status !== undefined ? { status } : {}),
        },
        orderBy: { canonicalExternalId: "asc" },
        take: limit + 1,
        ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      });

      let nextCursor: string | undefined;
      if (items.length > limit) {
        const next = items.pop();
        nextCursor = next?.id;
      }

      return { items, nextCursor };
    }),

  // ── 7. listStagedProjects ────────────────────────────────────────────────

  listStagedProjects: adminProcedure
    .input(
      paginationSchema.extend({
        importBatchId: z.string(),
        isTbd: z.boolean().optional(),
        status: stagedRecordStatusSchema.optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { cursor, importBatchId, isTbd, limit, status } = input;

      const items = await ctx.db.stagedProject.findMany({
        where: {
          importBatchId,
          ...(status !== undefined ? { status } : {}),
          ...(isTbd !== undefined ? { isTbd } : {}),
        },
        orderBy: { projectKey: "asc" },
        take: limit + 1,
        ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      });

      let nextCursor: string | undefined;
      if (items.length > limit) {
        const next = items.pop();
        nextCursor = next?.id;
      }

      return { items, nextCursor };
    }),

  // ── 8. listStagedAssignments ─────────────────────────────────────────────

  listStagedAssignments: adminProcedure
    .input(
      paginationSchema.extend({
        importBatchId: z.string(),
        resourceExternalId: z.string().optional(),
        status: stagedRecordStatusSchema.optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { cursor, importBatchId, limit, resourceExternalId, status } = input;

      const items = await ctx.db.stagedAssignment.findMany({
        where: {
          importBatchId,
          ...(status !== undefined ? { status } : {}),
          ...(resourceExternalId !== undefined ? { resourceExternalId } : {}),
        },
        orderBy: { createdAt: "asc" },
        take: limit + 1,
        ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      });

      let nextCursor: string | undefined;
      if (items.length > limit) {
        const next = items.pop();
        nextCursor = next?.id;
      }

      return { items, nextCursor };
    }),

  // ── 9. listStagedVacations ───────────────────────────────────────────────

  listStagedVacations: adminProcedure
    .input(
      paginationSchema.extend({
        importBatchId: z.string(),
        resourceExternalId: z.string().optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { cursor, importBatchId, limit, resourceExternalId } = input;

      const items = await ctx.db.stagedVacation.findMany({
        where: {
          importBatchId,
          ...(resourceExternalId !== undefined ? { resourceExternalId } : {}),
        },
        orderBy: { startDate: "asc" },
        take: limit + 1,
        ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      });

      let nextCursor: string | undefined;
      if (items.length > limit) {
        const next = items.pop();
        nextCursor = next?.id;
      }

      return { items, nextCursor };
    }),

  // ── 10. listStagedUnresolvedRecords ──────────────────────────────────────

  listStagedUnresolvedRecords: adminProcedure
    .input(
      paginationSchema.extend({
        importBatchId: z.string(),
        recordType: stagedRecordTypeSchema.optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { cursor, importBatchId, limit, recordType } = input;

      const items = await ctx.db.stagedUnresolvedRecord.findMany({
        where: {
          importBatchId,
          ...(recordType !== undefined ? { recordType } : {}),
        },
        orderBy: { createdAt: "asc" },
        take: limit + 1,
        ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      });

      let nextCursor: string | undefined;
      if (items.length > limit) {
        const next = items.pop();
        nextCursor = next?.id;
      }

      return { items, nextCursor };
    }),

  // ── 11. resolveStagedRecord ──────────────────────────────────────────────

  resolveStagedRecord: adminProcedure
    .input(
      z.object({
        action: z.enum(["APPROVE", "REJECT", "SKIP"]),
        id: z.string(),
        recordType: stagedRecordTypeSchema,
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const statusMap: Record<string, StagedRecordStatus> = {
        APPROVE: StagedRecordStatus.APPROVED,
        REJECT: StagedRecordStatus.REJECTED,
        SKIP: StagedRecordStatus.REJECTED,
      };
      const nextStatus = statusMap[input.action]!;

      // Delegate table lookup based on record type
      switch (input.recordType) {
        case DispoStagedRecordType.RESOURCE:
          return ctx.db.stagedResource.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        case DispoStagedRecordType.CLIENT:
          return ctx.db.stagedClient.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        case DispoStagedRecordType.PROJECT:
          return ctx.db.stagedProject.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        case DispoStagedRecordType.ASSIGNMENT:
          return ctx.db.stagedAssignment.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        case DispoStagedRecordType.VACATION:
          return ctx.db.stagedVacation.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        case DispoStagedRecordType.AVAILABILITY_RULE:
          return ctx.db.stagedAvailabilityRule.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        case DispoStagedRecordType.UNRESOLVED:
          return ctx.db.stagedUnresolvedRecord.update({
            where: { id: input.id },
            data: { status: nextStatus },
          });
        default:
          throw new TRPCError({
            code: "BAD_REQUEST",
            message: `Unknown record type: ${input.recordType as string}`,
          });
      }
    }),

  // ── 12. commitImportBatch ────────────────────────────────────────────────

  commitImportBatch: adminProcedure
    .input(
      z.object({
        allowTbdUnresolved: z.boolean().optional(),
        importBatchId: z.string(),
        importTbdProjects: z.boolean().optional(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const result = await commitDispoImportBatch(ctx.db, {
        importBatchId: input.importBatchId,
        ...(input.allowTbdUnresolved !== undefined ? { allowTbdUnresolved: input.allowTbdUnresolved } : {}),
        ...(input.importTbdProjects !== undefined ? { importTbdProjects: input.importTbdProjects } : {}),
      });

      const counts = result as unknown as Record<string, unknown>;
      void createAuditEntry({
        db: ctx.db,
        entityType: "ImportBatch",
        entityId: input.importBatchId,
        entityName: input.importBatchId,
        action: "IMPORT",
        userId: ctx.dbUser?.id,
        summary: `Committed import batch (${JSON.stringify(counts)})`,
        after: counts,
        source: "ui",
      });

      return result;
    }),
});
