import type { FastifyInstance } from "fastify";
import { z } from "zod";
import { requireSession } from "../../lib/auth.js";
import { writeAuditLog } from "../../lib/audit.js";
import { createId } from "../../lib/id.js";
import { prisma } from "../../lib/prisma.js";
import { getSessionProfile } from "../../lib/session.js";

const caseCreateSchema = z.object({
  clientId: z.string().uuid(),
  title: z.string().min(2).max(255),
  caseTypeCode: z.string().min(2).max(50),
  caseSubtypeCode: z.string().max(50).optional().or(z.literal("")),
  statusCode: z.string().min(2).max(50).default("intake"),
  priorityCode: z.string().min(2).max(30).default("normal"),
});

async function nextCaseNumber(lawFirmId: string) {
  const count = await prisma.caseRecord.count({
    where: { law_firm_id: lawFirmId },
  });

  return `CASE-${String(count + 1).padStart(6, "0")}`;
}

export async function registerCaseRoutes(app: FastifyInstance) {
  app.get("/", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const [cases, clients] = await prisma.$transaction([
      prisma.caseRecord.findMany({
        where: {
          law_firm_id: profile.lawFirm.id,
          deleted_at: null,
        },
        orderBy: {
          created_at: "desc",
        },
      }),
      prisma.client.findMany({
        where: {
          law_firm_id: profile.lawFirm.id,
          deleted_at: null,
        },
        select: {
          id: true,
          first_name: true,
          last_name: true,
        },
      }),
    ]);

    const clientsMap = new Map(
      clients.map((client) => [
        client.id,
        `${client.first_name} ${client.last_name}`.trim(),
      ]),
    );

    return cases.map((caseRecord) => ({
      id: caseRecord.id,
      caseNumber: caseRecord.case_number,
      title: caseRecord.title,
      caseTypeCode: caseRecord.case_type_code,
      caseSubtypeCode: caseRecord.case_subtype_code,
      statusCode: caseRecord.status_code,
      priorityCode: caseRecord.priority_code,
      clientId: caseRecord.client_id,
      clientName: clientsMap.get(caseRecord.client_id) ?? "Unknown client",
      openedAt: caseRecord.opened_at,
      archivedAt: caseRecord.archived_at,
    }));
  });

  app.post("/", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const payload = caseCreateSchema.parse(request.body);

    const client = await prisma.client.findFirst({
      where: {
        id: payload.clientId,
        law_firm_id: profile.lawFirm.id,
        deleted_at: null,
      },
    });

    if (!client) {
      throw reply.notFound("Client not found");
    }

    const caseRecord = await prisma.caseRecord.create({
      data: {
        id: createId(),
        law_firm_id: profile.lawFirm.id,
        office_id: profile.user.primaryOfficeId ?? null,
        client_id: payload.clientId,
        case_number: await nextCaseNumber(profile.lawFirm.id),
        title: payload.title,
        case_type_code: payload.caseTypeCode,
        case_subtype_code: payload.caseSubtypeCode || null,
        status_code: payload.statusCode,
        priority_code: payload.priorityCode,
        responsible_attorney_id: profile.user.roleCodes.some((code) =>
          ["admin", "partner", "attorney"].includes(code),
        )
          ? profile.user.id
          : null,
        created_by_user_id: profile.user.id,
      },
    });

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "case",
      entityId: caseRecord.id,
      action: "case.create",
      afterJson: {
        caseNumber: caseRecord.case_number,
        title: caseRecord.title,
        clientId: caseRecord.client_id,
        statusCode: caseRecord.status_code,
      },
      request,
    });

    return reply.code(201).send({
      id: caseRecord.id,
      caseNumber: caseRecord.case_number,
      title: caseRecord.title,
      caseTypeCode: caseRecord.case_type_code,
      caseSubtypeCode: caseRecord.case_subtype_code,
      statusCode: caseRecord.status_code,
      priorityCode: caseRecord.priority_code,
      clientId: caseRecord.client_id,
      clientName: `${client.first_name} ${client.last_name}`.trim(),
      openedAt: caseRecord.opened_at,
      archivedAt: caseRecord.archived_at,
    });
  });

  app.post("/:caseId/archive", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = request.params as { caseId: string };

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const caseRecord = await prisma.caseRecord.findFirst({
      where: {
        id: caseId,
        law_firm_id: profile.lawFirm.id,
        deleted_at: null,
      },
    });

    if (!caseRecord) {
      throw reply.notFound("Case not found");
    }

    if (caseRecord.archived_at) {
      throw reply.badRequest("Case is already archived");
    }

    const updatedCase = await prisma.caseRecord.update({
      where: { id: caseRecord.id },
      data: {
        archived_at: new Date(),
        archived_by_user_id: profile.user.id,
      },
    });

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "case",
      entityId: caseRecord.id,
      action: "case.archive",
      beforeJson: {
        archivedAt: caseRecord.archived_at,
      },
      afterJson: {
        archivedAt: updatedCase.archived_at,
      },
      request,
    });

    return {
      id: updatedCase.id,
      archivedAt: updatedCase.archived_at,
    };
  });

  app.post("/:caseId/unarchive", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = request.params as { caseId: string };

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const caseRecord = await prisma.caseRecord.findFirst({
      where: {
        id: caseId,
        law_firm_id: profile.lawFirm.id,
        deleted_at: null,
      },
    });

    if (!caseRecord) {
      throw reply.notFound("Case not found");
    }

    if (!caseRecord.archived_at) {
      throw reply.badRequest("Case is not archived");
    }

    const updatedCase = await prisma.caseRecord.update({
      where: { id: caseRecord.id },
      data: {
        archived_at: null,
        archived_by_user_id: null,
      },
    });

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "case",
      entityId: caseRecord.id,
      action: "case.unarchive",
      beforeJson: {
        archivedAt: caseRecord.archived_at,
      },
      afterJson: {
        archivedAt: updatedCase.archived_at,
      },
      request,
    });

    return {
      id: updatedCase.id,
      archivedAt: updatedCase.archived_at,
    };
  });
}
