import type { FastifyInstance } from "fastify";
import { z } from "zod";
import {
  analyzeCaseGaps,
  assembleCasePacket,
  consolidateCaseFacts,
  createReview,
  fillCaseForms,
  generateCaseProcess,
  listCaseFacts,
  reviewCaseFact,
} from "../../lib/intelligence.js";
import { requireSession } from "../../lib/auth.js";
import { prisma } from "../../lib/prisma.js";
import { getSessionProfile } from "../../lib/session.js";

const questionnaireSchema = z.object({
  caseId: z.string().uuid(),
});

const caseIdParamSchema = z.object({
  caseId: z.string().uuid(),
});

const factReviewParamSchema = z.object({
  caseId: z.string().uuid(),
  factId: z.string().uuid(),
});

const factReviewSchema = z.object({
  action: z.enum(["confirm", "correct", "reject", "research_hint"]),
  correctedValue: z.string().max(1000).optional().nullable(),
  feedbackNote: z.string().max(4000).optional().nullable(),
  searchHint: z.string().max(2000).optional().nullable(),
  sourceHint: z.string().max(2000).optional().nullable(),
});

const reviewSchema = z.object({
  caseId: z.string().uuid().optional().nullable(),
  entityType: z.string().min(2).max(50),
  entityId: z.string().uuid().optional().nullable(),
  reviewType: z.string().min(2).max(50),
  notes: z.string().max(2000).optional().or(z.literal("")),
});

const generateProcessSchema = z.object({
  clientId: z.string().uuid(),
  caseId: z.string().uuid(),
  workflowTemplateId: z.string().uuid(),
});

function parseQuestionnaireRunSummary(
  value: unknown,
  runReason: string,
  fallbackQuestionCount: number,
) {
  let parsedValue = value;

  if (typeof parsedValue === "string" && parsedValue.trim()) {
    try {
      parsedValue = JSON.parse(parsedValue);
    } catch {
      parsedValue = null;
    }
  }

  const sourceType =
    parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) ?
      String((parsedValue as { sourceType?: unknown }).sourceType ?? "").trim() :
      "";
  const workflowTemplateIds =
    parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) &&
      Array.isArray((parsedValue as { workflowTemplateIds?: unknown[] }).workflowTemplateIds) ?
        (parsedValue as { workflowTemplateIds: unknown[] }).workflowTemplateIds
          .map((item) => String(item ?? "").trim())
          .filter(Boolean) :
        [];
  const portalToken =
    parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) ?
      String((parsedValue as { portalToken?: unknown }).portalToken ?? "").trim() :
      "";
  const summaryQuestionCount =
    parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) ?
      Number((parsedValue as { questionCount?: unknown }).questionCount ?? fallbackQuestionCount) :
      fallbackQuestionCount;
  const summaryDocumentCount =
    parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) ?
      Number((parsedValue as { documentCount?: unknown }).documentCount ?? 0) :
      0;
  const normalizedRunReason = runReason.trim().toLowerCase();
  const runKind =
    sourceType === "workflow_portal" || normalizedRunReason === "workflow portal selection".toLowerCase() ?
      "workflow_portal" :
    sourceType === "gap_analysis" || normalizedRunReason === "dynamic gap analysis".toLowerCase() ?
      "gap_analysis" :
      "other";

  return {
    runKind,
    portalToken: portalToken || null,
    portalLink: portalToken ? `http://localhost:5173/questionnaire/${portalToken}` : null,
    questionCount:
      Number.isFinite(summaryQuestionCount) && summaryQuestionCount >= 0 ?
        Math.trunc(summaryQuestionCount) :
        fallbackQuestionCount,
    documentCount:
      Number.isFinite(summaryDocumentCount) && summaryDocumentCount >= 0 ?
        Math.trunc(summaryDocumentCount) :
        0,
    workflowCount: workflowTemplateIds.length,
  };
}

export async function registerIntelligenceRoutes(app: FastifyInstance) {
  app.get("/data-fields", async (_request, _reply) => {
    return prisma.$queryRaw<
      Array<{
        id: string;
        field_key: string;
        label: string;
        entity_scope: string;
        data_type: string;
      }>
    >`
      SELECT id, field_key, label, entity_scope, data_type
      FROM data_fields
      ORDER BY label ASC
    `;
  });

  app.get("/cases/:caseId/facts", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = caseIdParamSchema.parse(request.params);

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

    return listCaseFacts({
      lawFirmId: profile.lawFirm.id,
      caseId,
    });
  });

  app.post("/cases/:caseId/facts/consolidate", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = caseIdParamSchema.parse(request.params);

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

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

  app.post("/cases/:caseId/facts/:factId/review", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId, factId } = factReviewParamSchema.parse(request.params);
    const payload = factReviewSchema.parse(request.body);

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

    try {
      return await reviewCaseFact({
        lawFirmId: profile.lawFirm.id,
        caseId,
        factId,
        actorUserId: profile.user.id,
        action: payload.action,
        correctedValue: payload.correctedValue ?? null,
        feedbackNote: payload.feedbackNote ?? null,
        searchHint: payload.searchHint ?? null,
        sourceHint: payload.sourceHint ?? null,
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unexpected error";

      if (message === "Fact not found") {
        throw reply.notFound(message);
      }

      if (
        message.includes("required") ||
        message.includes("Provide a note or hint") ||
        message.includes("previously rejected")
      ) {
        throw reply.badRequest(message);
      }

      throw error;
    }
  });

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

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

    return analyzeCaseGaps({
      lawFirmId: profile.lawFirm.id,
      caseId: payload.caseId,
      actorUserId: profile.user.id,
    });
  });

  app.get("/questionnaires/case/:caseId", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = caseIdParamSchema.parse(request.params);

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

    const runs = await prisma.$queryRaw<
      Array<{
        id: string;
        status_code: string;
        run_reason: string;
        coverage_summary_json: unknown;
        created_at: Date;
        question_count: bigint;
        session_id: string | null;
      }>
    >`
      SELECT qr.id, qr.status_code, qr.run_reason, qr.coverage_summary_json, qr.created_at,
             COUNT(qq.id) AS question_count,
             qs.id AS session_id
      FROM questionnaire_runs qr
      LEFT JOIN questionnaire_sessions qs ON qs.questionnaire_run_id = qr.id
      LEFT JOIN questionnaire_questions qq ON qq.questionnaire_session_id = qs.id
      WHERE qr.case_id = ${caseId}
        AND qr.law_firm_id = ${profile.lawFirm.id}
      GROUP BY qr.id, qr.status_code, qr.run_reason, qr.coverage_summary_json, qr.created_at, qs.id
      ORDER BY qr.created_at DESC
    `;

    return runs.map((run) => {
      const summary = parseQuestionnaireRunSummary(
        run.coverage_summary_json,
        run.run_reason,
        Number(run.question_count),
      );

      return {
        id: run.id,
        statusCode: run.status_code,
        runReason: run.run_reason,
        runKind: summary.runKind,
        createdAt: run.created_at,
        questionCount: summary.questionCount,
        documentCount: summary.documentCount,
        workflowCount: summary.workflowCount,
        portalToken: summary.portalToken,
        portalLink: summary.portalLink,
        sessionId: run.session_id,
      };
    });
  });

  app.post("/cases/:caseId/forms/fill", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = caseIdParamSchema.parse(request.params);

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

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

  app.post("/cases/:caseId/packets/assemble", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { caseId } = caseIdParamSchema.parse(request.params);

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

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

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

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

    return generateCaseProcess({
      lawFirmId: profile.lawFirm.id,
      clientId: payload.clientId,
      caseId: payload.caseId,
      workflowTemplateId: payload.workflowTemplateId,
      actorUserId: profile.user.id,
    });
  });

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

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

    const reviewId = await createReview({
      lawFirmId: profile.lawFirm.id,
      caseId: payload.caseId ?? null,
      entityType: payload.entityType,
      entityId: payload.entityId ?? null,
      reviewType: payload.reviewType,
      reviewerUserId: profile.user.id,
      notes: payload.notes || null,
      aiRunId: null,
    });

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

  app.get("/reviews", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const query = z.object({ caseId: z.string().uuid().optional() }).parse(request.query);

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

    const reviews = await prisma.$queryRaw<
      Array<{
        id: string;
        entity_type: string;
        review_type: string;
        review_status: string;
        notes: string | null;
        created_at: Date;
      }>
    >`
      SELECT id, entity_type, review_type, review_status, notes, created_at
      FROM reviews
      WHERE law_firm_id = ${profile.lawFirm.id}
        AND (${query.caseId ?? null} IS NULL OR case_id = ${query.caseId ?? null})
      ORDER BY created_at DESC
    `;

    return reviews;
  });
}
