import type { FastifyInstance } from "fastify";
import type { MultipartFile } from "@fastify/multipart";
import { Prisma } from "@prisma/client";
import { z } from "zod";
import { requireSession } from "../../lib/auth.js";
import { writeAuditLog } from "../../lib/audit.js";
import { createId } from "../../lib/id.js";
import { isImageKitConfigured, uploadBinaryFileToImageKit } from "../../lib/imagekit.js";
import { prisma } from "../../lib/prisma.js";
import { createRepositoryItem } from "../../lib/repository.js";
import { getSessionProfile } from "../../lib/session.js";
import {
  createSpecializedFormGenerationJob,
  getSpecializedFormGenerationJobDetail,
  pauseSpecializedFormGenerationJob,
  resumePendingSpecializedFormGenerationJobs,
  resumeSpecializedFormGenerationJob,
  startSpecializedFormGenerationJob,
} from "../../lib/specialized-form-generation-jobs.js";
import {
  generateSpecializedFormForClient,
  listSpecializedFormFieldGuidance,
  listSpecializedForms,
  updateSpecializedFormFieldGuidance,
  updateSpecializedFormPrompt,
} from "../../lib/specialized-forms.js";
import { saveBinaryFile } from "../../lib/storage.js";

const formTemplateSchema = z.object({
  code: z.string().min(2).max(50),
  name: z.string().min(2).max(255),
  versionLabel: z.string().min(1).max(50).default("1"),
  description: z.string().max(1000).optional().or(z.literal("")),
  basePdfFileId: z.string().uuid().optional().nullable(),
});

const formFieldSchema = z.object({
  fieldKey: z.string().min(2).max(100),
  label: z.string().min(2).max(255),
  pdfFieldName: z.string().min(2).max(255),
  dataType: z.string().min(2).max(50),
  isRequired: z.boolean().default(true),
  sectionName: z.string().max(100).optional().or(z.literal("")),
});

const specializedFormPromptSchema = z.object({
  prompt: z.string().max(50000),
});

const specializedFormFieldGuidanceSchema = z.object({
  instructions: z.string().max(4000),
});

const specializedFormGenerationSchema = z.object({
  clientId: z.string().uuid(),
});

function getMultipartFieldValue(
  fields: Record<string, MultipartFile["fields"][string]>,
  key: string,
) {
  const field = fields[key];

  if (!field || Array.isArray(field) || !("value" in field)) {
    return "";
  }

  return String(field.value ?? "");
}

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

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

    return listSpecializedForms({
      lawFirmId: profile.lawFirm.id,
      actorUserId: profile.user.id,
    });
  });

  app.patch("/specialized/:agentCode/prompt", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const payload = specializedFormPromptSchema.parse(request.body);
    const { agentCode } = request.params as { agentCode: string };

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

    const summary = await updateSpecializedFormPrompt({
      lawFirmId: profile.lawFirm.id,
      actorUserId: profile.user.id,
      agentCode,
      prompt: payload.prompt,
    });

    return summary;
  });

  app.get("/specialized/:agentCode/fields", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { agentCode } = request.params as { agentCode: string };

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

    return listSpecializedFormFieldGuidance({
      lawFirmId: profile.lawFirm.id,
      actorUserId: profile.user.id,
      agentCode,
    });
  });

  app.patch("/specialized/:agentCode/fields/:fieldId", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const payload = specializedFormFieldGuidanceSchema.parse(request.body);
    const { agentCode, fieldId } = request.params as { agentCode: string; fieldId: string };

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

    const field = await updateSpecializedFormFieldGuidance({
      lawFirmId: profile.lawFirm.id,
      actorUserId: profile.user.id,
      agentCode,
      fieldId,
      instructions: payload.instructions,
    });

    if (!field) {
      throw reply.notFound("Specialized form field not found");
    }

    return field;
  });

  app.post("/specialized/:agentCode/generate", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const payload = specializedFormGenerationSchema.parse(request.body);
    const { agentCode } = request.params as { agentCode: string };

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

    return generateSpecializedFormForClient({
      lawFirmId: profile.lawFirm.id,
      actorUserId: profile.user.id,
      clientId: payload.clientId,
      agentCode,
    });
  });

  app.post("/specialized/:agentCode/generate-jobs", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const payload = specializedFormGenerationSchema.parse(request.body);
    const { agentCode } = request.params as { agentCode: string };

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

    const jobId = await createSpecializedFormGenerationJob({
      lawFirmId: profile.lawFirm.id,
      actorUserId: profile.user.id,
      clientId: payload.clientId,
      agentCode,
    });

    await startSpecializedFormGenerationJob({
      jobId,
      logger: app.log,
    });

    const detail = await getSpecializedFormGenerationJobDetail({
      lawFirmId: profile.lawFirm.id,
      jobId,
      logger: app.log,
    });

    if (!detail || detail.agentCode !== agentCode) {
      throw reply.notFound("Specialized form generation job not found");
    }

    return detail;
  });

  app.get("/specialized/:agentCode/generate-jobs/:jobId", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { agentCode, jobId } = request.params as { agentCode: string; jobId: string };

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

    const detail = await getSpecializedFormGenerationJobDetail({
      lawFirmId: profile.lawFirm.id,
      jobId,
      logger: app.log,
    });

    if (!detail || detail.agentCode !== agentCode) {
      throw reply.notFound("Specialized form generation job not found");
    }

    return detail;
  });

  app.post("/specialized/:agentCode/generate-jobs/:jobId/pause", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { agentCode, jobId } = request.params as { agentCode: string; jobId: string };

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

    const detail = await pauseSpecializedFormGenerationJob({
      lawFirmId: profile.lawFirm.id,
      jobId,
    });

    if (!detail || detail.agentCode !== agentCode) {
      throw reply.notFound("Specialized form generation job not found");
    }

    return detail;
  });

  app.post("/specialized/:agentCode/generate-jobs/:jobId/resume", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { agentCode, jobId } = request.params as { agentCode: string; jobId: string };

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

    const detail = await resumeSpecializedFormGenerationJob({
      lawFirmId: profile.lawFirm.id,
      jobId,
      logger: app.log,
    });

    if (!detail || detail.agentCode !== agentCode) {
      throw reply.notFound("Specialized form generation job not found");
    }

    return detail;
  });

  app.get("/templates", 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 templates = await prisma.$queryRaw<
      Array<{
        id: string;
        law_firm_id: string | null;
        code: string;
        name: string;
        version_label: string;
        base_pdf_file_id: string | null;
        base_pdf_original_file_name: string | null;
        base_pdf_storage_provider: string | null;
        description: string | null;
        is_system_template: number;
      }>
    >`
      SELECT
        ft.id,
        ft.law_firm_id,
        ft.code,
        ft.name,
        ft.version_label,
        ft.base_pdf_file_id,
        f.original_file_name AS base_pdf_original_file_name,
        f.storage_provider AS base_pdf_storage_provider,
        ft.description,
        ft.is_system_template
      FROM form_templates ft
      LEFT JOIN files f ON f.id = ft.base_pdf_file_id
      WHERE ft.law_firm_id = ${profile.lawFirm.id} OR ft.law_firm_id IS NULL
      ORDER BY is_system_template DESC, name ASC
    `;

    const fields = templates.length
      ? await prisma.$queryRaw<
          Array<{
            id: string;
            form_template_id: string;
            field_key: string;
            label: string;
            pdf_field_name: string;
            data_type: string;
            is_required: number;
          }>
        >`
          SELECT id, form_template_id, field_key, label, pdf_field_name, data_type, is_required
          FROM form_fields
          WHERE form_template_id IN (${Prisma.join(templates.map((template) => template.id))})
          ORDER BY created_at ASC
        `
      : [];

    return templates.map((template) => ({
      id: template.id,
      code: template.code,
      name: template.name,
      versionLabel: template.version_label,
      basePdfFileId: template.base_pdf_file_id,
      basePdfOriginalFileName: template.base_pdf_original_file_name,
      basePdfStorageProvider: template.base_pdf_storage_provider,
      description: template.description,
      isSystemTemplate: Boolean(template.is_system_template),
      fields: fields
        .filter((field) => field.form_template_id === template.id)
        .map((field) => ({
          id: field.id,
          fieldKey: field.field_key,
          label: field.label,
          pdfFieldName: field.pdf_field_name,
          dataType: field.data_type,
          isRequired: Boolean(field.is_required),
        })),
    }));
  });

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

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

    const id = createId();
    await prisma.$executeRaw`
      INSERT INTO form_templates (
        id, law_firm_id, code, name, version_label, base_pdf_file_id, description, is_active,
        is_system_template, created_at, updated_at
      ) VALUES (
        ${id},
        ${profile.lawFirm.id},
        ${payload.code},
        ${payload.name},
        ${payload.versionLabel},
        ${payload.basePdfFileId ?? null},
        ${payload.description || null},
        1,
        0,
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
    `;

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "form_template",
      entityId: id,
      action: "form_template.create",
      afterJson: payload,
      request,
    });

    return reply.code(201).send({ id });
  });

  app.post("/template-assets/upload", 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 multipartRequest = request as typeof request & {
      file: () => Promise<MultipartFile | undefined>;
    };
    const file = await multipartRequest.file();

    if (!file) {
      throw reply.badRequest("A file upload is required");
    }

    if (file.mimetype !== "application/pdf" && !file.filename.toLowerCase().endsWith(".pdf")) {
      throw reply.badRequest("Only PDF uploads are supported for form templates");
    }

    const title = getMultipartFieldValue(file.fields, "title") || file.filename;
    const folder = getMultipartFieldValue(file.fields, "folder");
    const buffer = await file.toBuffer();
    const stored = isImageKitConfigured()
      ? await uploadBinaryFileToImageKit({
          bytes: buffer,
          fileName: file.filename,
          mimeType: file.mimetype,
          folder: folder || undefined,
        })
      : await (async () => {
          const localFile = await saveBinaryFile({
            lawFirmId: profile.lawFirm.id,
            caseId: null,
            fileName: file.filename,
            bytes: buffer,
            kind: "uploads",
          });

          return {
            storageProvider: "local_dev",
            storageBucket: "workspace",
            objectKey: localFile.relativeObjectKey,
            storageRegion: "local",
            storedFileName: localFile.storedFileName,
            checksumSha256: localFile.checksumSha256,
            uploadUrl: null,
          };
        })();

    const repositoryItemId = await createRepositoryItem({
      lawFirmId: profile.lawFirm.id,
      itemTypeCode: "document",
      subject: title,
      createdByUserId: profile.user.id,
    });

    const fileId = createId();
    await prisma.$executeRaw`
      INSERT INTO files (
        id, law_firm_id, client_id, case_id, repository_item_id, storage_provider, storage_bucket,
        object_key, storage_region, original_file_name, stored_file_name, mime_type, size_bytes,
        checksum_sha256, is_encrypted, uploaded_by_user_id, uploaded_at, created_at
      ) VALUES (
        ${fileId},
        ${profile.lawFirm.id},
        NULL,
        NULL,
        ${repositoryItemId},
        ${stored.storageProvider},
        ${stored.storageBucket},
        ${stored.objectKey},
        ${stored.storageRegion},
        ${file.filename},
        ${stored.storedFileName},
        ${file.mimetype},
        ${buffer.length},
        ${stored.checksumSha256},
        0,
        ${profile.user.id},
        NOW(),
        CURRENT_TIMESTAMP
      )
    `;

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "file",
      entityId: fileId,
      action: "form_template_asset.upload",
      afterJson: {
        originalFileName: file.filename,
        storageProvider: stored.storageProvider,
        storageBucket: stored.storageBucket,
      },
      request,
    });

    return reply.code(201).send({
      fileId,
      originalFileName: file.filename,
      storedFileName: stored.storedFileName,
      storageProvider: stored.storageProvider,
      uploadUrl: stored.uploadUrl,
    });
  });

  app.post("/templates/:templateId/fields", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const payload = formFieldSchema.parse(request.body);
    const { templateId } = request.params as { templateId: string };

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

    const fieldId = createId();
    await prisma.$executeRaw`
      INSERT INTO form_fields (
        id, form_template_id, section_name, page_number, field_key, label, pdf_field_name,
        data_type, is_required, is_repeatable, created_at, updated_at
      ) VALUES (
        ${fieldId},
        ${templateId},
        ${payload.sectionName || null},
        1,
        ${payload.fieldKey},
        ${payload.label},
        ${payload.pdfFieldName},
        ${payload.dataType},
        ${payload.isRequired ? 1 : 0},
        0,
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
      )
    `;

    const [dataField] = await prisma.$queryRaw<Array<{ id: string }>>`
      SELECT id FROM data_fields WHERE field_key = ${payload.fieldKey} LIMIT 1
    `;

    if (dataField) {
      await prisma.$executeRaw`
        INSERT INTO form_mappings (
          id, form_template_id, form_field_id, data_field_id, mapping_strategy,
          confidence_threshold, is_active, created_at, updated_at
        ) VALUES (
          ${createId()},
          ${templateId},
          ${fieldId},
          ${dataField.id},
          'direct',
          0.8000,
          1,
          CURRENT_TIMESTAMP,
          CURRENT_TIMESTAMP
        )
      `;
    }

    return reply.code(201).send({ id: fieldId });
  });

  app.get("/case/:caseId", 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 caseForms = await prisma.$queryRaw<
      Array<{
        id: string;
        requirement_name: string;
        status_code: string;
        form_name: string;
      }>
    >`
      SELECT cf.id, cf.requirement_name, cf.status_code, ft.name AS form_name
      FROM case_forms cf
      JOIN form_templates ft ON ft.id = cf.form_template_id
      WHERE cf.case_id = ${caseId}
      ORDER BY cf.created_at ASC
    `;

    return caseForms.map((caseForm) => ({
      id: caseForm.id,
      requirementName: caseForm.requirement_name,
      statusCode: caseForm.status_code,
      formName: caseForm.form_name,
    }));
  });

  void resumePendingSpecializedFormGenerationJobs(app.log).catch((error) => {
    app.log.error({ error }, "Unable to resume pending specialized form generation jobs");
  });
}
