import { randomUUID } from "node:crypto";
import assert from "node:assert/strict";
import { rm } from "node:fs/promises";
import { resolve } from "node:path";
import test from "node:test";
import { createDocumentReviewService } from "./document-reviews.js";
import { prisma } from "./prisma.js";
import { saveBinaryFile } from "./storage.js";
import { writeAuditLog } from "./audit.js";

type Fixture = {
  lawFirmId: string;
  officeId: string;
  userId: string;
  clientId: string;
  caseId: string;
};

type MockAnalysisPayload = {
  summary: string;
  verdict?: "valid" | "invalid" | "needs_review";
  verdictReason?: string;
  strengths: string[];
  weaknesses: string[];
  suggestions: string[];
  risks: Array<{
    title: string;
    severity: "low" | "medium" | "high" | "critical";
    description: string;
    recommendation: string | null;
  }>;
  possibleRejectionReasons: string[];
  confidence: number;
  riskLevel: "low" | "medium" | "high" | "critical";
  alertPoints: string[];
};

function createMockPayload(input: Partial<MockAnalysisPayload> = {}): MockAnalysisPayload {
  return {
    summary: input.summary ?? "Resumo automático de teste.",
    verdict: input.verdict,
    verdictReason: input.verdictReason,
    strengths: input.strengths ?? ["Estrutura consistente."],
    weaknesses: input.weaknesses ?? [],
    suggestions: input.suggestions ?? ["Seguir revisão humana final."],
    risks: input.risks ?? [],
    possibleRejectionReasons: input.possibleRejectionReasons ?? [],
    confidence: input.confidence ?? 0.91,
    riskLevel: input.riskLevel ?? "low",
    alertPoints: input.alertPoints ?? [],
  };
}

function createMockService(queue: Array<Record<string, unknown>>) {
  return createDocumentReviewService({
    prisma,
    saveBinaryFile,
    writeAuditLog,
    createAiRun: async () =>
      ({
        id: null,
        ai_provider: "mock",
        ai_model: "mock",
        api_key_reference_id: "mock",
      }) as never,
    finishAiRun: async () => undefined,
    runJsonChatCompletion: async () => {
      const next = queue.shift() ?? createMockPayload();
      return {
        json: next as unknown as Record<string, unknown>,
        usage: {
          inputTokens: 0,
          outputTokens: 0,
          totalTokens: 0,
        },
        model: "mock",
      };
    },
  });
}

function createCountingMockService(queue: Array<Record<string, unknown>>) {
  let aiCallCount = 0;

  const service = createDocumentReviewService({
    prisma,
    saveBinaryFile,
    writeAuditLog,
    createAiRun: async () =>
      ({
        id: null,
        ai_provider: "mock",
        ai_model: "mock",
        api_key_reference_id: "mock",
      }) as never,
    finishAiRun: async () => undefined,
    runJsonChatCompletion: async () => {
      aiCallCount += 1;
      const next = queue.shift() ?? createMockPayload();
      return {
        json: next as unknown as Record<string, unknown>,
        usage: {
          inputTokens: 0,
          outputTokens: 0,
          totalTokens: 0,
        },
        model: "mock",
      };
    },
  });

  return {
    service,
    getAiCallCount: () => aiCallCount,
  };
}

async function createFixture() {
  const suffix = randomUUID().slice(0, 8);
  const teamId = randomUUID();
  const teamMembershipId = randomUUID();
  const teamAgentId = randomUUID();
  const fixture: Fixture = {
    lawFirmId: randomUUID(),
    officeId: randomUUID(),
    userId: randomUUID(),
    clientId: randomUUID(),
    caseId: randomUUID(),
  };

  await prisma.$executeRaw`
    INSERT INTO law_firms (
      id, name, legal_name, slug, timezone, default_locale, status, created_at, updated_at
    ) VALUES (
      ${fixture.lawFirmId},
      ${`Documento Review ${suffix}`},
      ${`Documento Review ${suffix}`},
      ${`doc-review-${suffix}`},
      'America/New_York',
      'pt-BR',
      'active',
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO offices (
      id, law_firm_id, name, code, timezone, is_headquarters, created_at, updated_at
    ) VALUES (
      ${fixture.officeId},
      ${fixture.lawFirmId},
      'Main office',
      ${`HQ${suffix}`},
      'America/New_York',
      1,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO users (
      id, law_firm_id, primary_office_id, first_name, last_name, display_name, email,
      auth_provider, locale, timezone, is_active, created_at, updated_at
    ) VALUES (
      ${fixture.userId},
      ${fixture.lawFirmId},
      ${fixture.officeId},
      'Teste',
      'Revisor',
      'Revisor Teste',
      ${`reviewer-${suffix}@example.com`},
      'local',
      'pt-BR',
      'America/New_York',
      1,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO teams (
      id, law_firm_id, name, description, created_by_user_id, created_at, updated_at
    ) VALUES (
      ${teamId},
      ${fixture.lawFirmId},
      'Time de revisão',
      'Team usado pelos testes de revisão documental.',
      ${fixture.userId},
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO team_memberships (
      id, law_firm_id, team_id, user_id, membership_role, invited_by_user_id, joined_at
    ) VALUES (
      ${teamMembershipId},
      ${fixture.lawFirmId},
      ${teamId},
      ${fixture.userId},
      'reviewer',
      ${fixture.userId},
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO team_agents (
      id, law_firm_id, team_id, user_id, agent_name, system_prompt, learning_summary,
      memory_count, last_learning_at, created_at, updated_at
    ) VALUES (
      ${teamAgentId},
      ${fixture.lawFirmId},
      ${teamId},
      ${fixture.userId},
      'Agente do reviewer',
      'Responder com foco em revisão documental.',
      'Memória sintética para os testes.',
      0,
      NULL,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO clients (
      id, law_firm_id, primary_office_id, client_number, first_name, last_name, email,
      preferred_language, created_by_user_id, created_at, updated_at
    ) VALUES (
      ${fixture.clientId},
      ${fixture.lawFirmId},
      ${fixture.officeId},
      ${`CL-${suffix}`},
      'Cliente',
      'Teste',
      ${`client-${suffix}@example.com`},
      'pt-BR',
      ${fixture.userId},
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO cases (
      id, law_firm_id, office_id, client_id, case_number, title, case_type_code, case_subtype_code,
      status_code, priority_code, opened_at, created_by_user_id, created_at, updated_at
    ) VALUES (
      ${fixture.caseId},
      ${fixture.lawFirmId},
      ${fixture.officeId},
      ${fixture.clientId},
      ${`CASE-${suffix}`},
      'Revisão documental teste',
      'employment_based',
      'eb3',
      'intake',
      'normal',
      NOW(),
      ${fixture.userId},
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  return fixture;
}

async function cleanupFixture(fixture: Fixture) {
  await prisma.$executeRaw`DELETE FROM audit_logs WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_agent_memories WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM document_review_assignments WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM document_review_decisions WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM document_review_comments WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM document_review_ai_analyses WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`
    UPDATE document_review_versions
    SET previous_version_id = NULL
    WHERE law_firm_id = ${fixture.lawFirmId}
  `;
  await prisma.$executeRaw`DELETE FROM document_review_versions WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM document_review_items WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`
    DELETE FROM files
    WHERE law_firm_id = ${fixture.lawFirmId}
  `;
  await prisma.$executeRaw`
    DELETE FROM repository_items
    WHERE law_firm_id = ${fixture.lawFirmId}
      AND source_entity_type IN ('document_review_version', 'document_review_comment')
  `;
  await prisma.$executeRaw`DELETE FROM cases WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM clients WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_agents WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_invitations WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_memberships WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM teams WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM users WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM offices WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM law_firms WHERE id = ${fixture.lawFirmId}`;

  await rm(resolve(process.cwd(), "../../storage/uploads", fixture.lawFirmId), {
    recursive: true,
    force: true,
  }).catch(() => undefined);
}

function actor(userId: string) {
  return {
    actorUserId: userId,
    officeId: null,
  };
}

function buildRichDocumentText(extra = "") {
  return [
    "INTRODUÇÃO",
    "",
    "Este documento foi preparado para revisão documental completa, com foco em consistência interna, clareza das seções, dados relevantes, datas, fundamentos e confirmação de que o conteúdo está suficientemente detalhado para permitir uma análise humana e automatizada sem ambiguidade material.",
    "",
    "OBJETIVO",
    "",
    "O objetivo é registrar fatos, contexto, partes envolvidas, cronologia essencial, informações de contato, critérios de revisão e explicações suficientes para reduzir o risco de omissão, rejeição ou retorno para ajustes por ausência de dados críticos.",
    "",
    "DETALHES",
    "",
    "Data principal: 2029-12-01. Contato: legal@example.com. Valor de referência: $1500. O texto inclui contexto adicional, descrição de eventos, escopo da revisão, itens já conferidos, pontos pendentes explicitamente resolvidos e uma narrativa contínua para elevar a completude total do documento antes da submissão para revisão.",
    extra,
  ].join("\n");
}

async function createApprovedDocument(
  fixture: Fixture,
  service = createMockService([createMockPayload(), createMockPayload()]),
) {
  const dueAt = new Date("2030-01-10T15:00:00.000Z");
  const submitResult = await service.submitNewDocument({
    lawFirmId: fixture.lawFirmId,
    actor: actor(fixture.userId),
    clientId: fixture.clientId,
    caseId: fixture.caseId,
    documentName: "Memorial jurídico",
        objective: "Suportar revisão documental completa.",
        dueAt,
        originalFileName: "memorial.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText(), "utf8"),
      });

  const validationResult = await service.runFinalValidation({
    lawFirmId: fixture.lawFirmId,
    actor: actor(fixture.userId),
    itemId: submitResult.itemId!,
    versionId: submitResult.versionId!,
  });

  await service.recordDecision({
    lawFirmId: fixture.lawFirmId,
    actor: actor(fixture.userId),
    itemId: submitResult.itemId!,
    versionId: submitResult.versionId!,
    decisionCode: "approved",
    justification: "Documento consistente e validado.",
    finalValidationAnalysisId: validationResult.id,
  });

  return {
    itemId: submitResult.itemId!,
    versionId: submitResult.versionId!,
  };
}

test("document review workflow suite", async (t) => {
  await t.test("creates a new document with extracted text, structure and linked analysis", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([createMockPayload()]);
      const result = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Contrato inicial",
        objective: "Validar consistência documental.",
        dueAt: new Date("2030-02-01T12:00:00.000Z"),
        originalFileName: "contrato.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Assinatura prevista em 2029-11-30."), "utf8"),
      });

      assert.equal(result.created, true);
      assert.equal(result.requiresConfirmation, false);
      assert.ok(result.itemId);
      assert.ok(result.versionId);
      assert.equal(result.analysis.verdict, "valid");
      assert.match(result.analysis.summary, /^Veredito:/);

      const detail = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: result.itemId!,
      });

      assert.equal(detail.item.status, "submitted");
      assert.equal(detail.versions.length, 1);
      assert.equal(detail.versions[0].versionNumber, 1);
      assert.ok(detail.versions[0].extractedText?.includes("INTRODUÇÃO"));
      assert.ok(
        Number(
          (detail.versions[0].structure as { wordCount?: number }).wordCount ?? 0,
        ) > 10,
      );
      assert.equal(detail.versions[0].analyses.length, 1);
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("returns AI alert before submission and allows confirm with reused analysis", async () => {
    const fixture = await createFixture();
    try {
      const highRisk = createMockPayload({
        summary: "Há risco material de recusa.",
        weaknesses: ["Campos incompletos."],
        risks: [
          {
            title: "Campos em branco",
            severity: "high",
            description: "O documento contém placeholders não resolvidos.",
            recommendation: "Preencher todos os campos obrigatórios.",
          },
        ],
        possibleRejectionReasons: ["Campos em branco."],
        confidence: 0.42,
        riskLevel: "high",
        alertPoints: ["Placeholders detectados."],
      });
      const service = createMockService([highRisk]);

      const firstAttempt = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento sensível",
        objective: "Fluxo com alerta.",
        dueAt: new Date("2030-02-02T10:00:00.000Z"),
        originalFileName: "draft.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from("TODO preencher cláusulas pendentes.", "utf8"),
      });

      assert.equal(firstAttempt.created, false);
      assert.equal(firstAttempt.requiresConfirmation, true);
      assert.ok(firstAttempt.analysis.id);
      assert.equal(firstAttempt.analysis.verdict, "invalid");
      assert.match(firstAttempt.analysis.summary, /^Veredito:/);

      const secondAttempt = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento sensível",
        objective: "Fluxo com alerta.",
        dueAt: new Date("2030-02-02T10:00:00.000Z"),
        originalFileName: "draft.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from("TODO preencher cláusulas pendentes.", "utf8"),
        forceSubmit: true,
        reuseAnalysisId: firstAttempt.analysis.id,
      });

      assert.equal(secondAttempt.created, true);
      assert.equal(secondAttempt.analysis.id, firstAttempt.analysis.id);

      const detail = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: secondAttempt.itemId!,
      });
      assert.equal(detail.item.hasAiAlert, true);
      assert.equal(detail.item.latestRiskLevel, "critical");
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("creates a new version with comparison against the previous one", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([createMockPayload(), createMockPayload()]);
      const initial = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Peça inicial",
        objective: "Versão base.",
        dueAt: new Date("2030-03-01T12:00:00.000Z"),
        originalFileName: "piece-v1.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Versão inicial registrada em 2029-11-10."), "utf8"),
      });

      const nextVersion = await service.submitNewVersion({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: initial.itemId!,
        originalFileName: "piece-v2.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(
          buildRichDocumentText(
            "SEÇÃO ADICIONAL\n\nNovo fundamento incluído nesta revisão, com contexto complementar, data confirmada em 2029-11-10 e esclarecimentos adicionais sobre o histórico do documento.",
          ),
          "utf8",
        ),
        submissionNote: "Inclui nova seção e correções.",
      });

      assert.equal(nextVersion.created, true);

      const detail = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: initial.itemId!,
      });

      assert.equal(detail.versions.length, 2);
      assert.equal(detail.versions[0].versionNumber, 2);
      assert.ok(detail.versions[0].comparison);
      assert.equal(
        Number(
          (detail.versions[0].comparison as { previousVersionNumber?: number })
            .previousVersionNumber ?? 0,
        ),
        1,
      );
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("reuses detached precheck analysis for new document submission without calling AI again", async () => {
    const fixture = await createFixture();
    try {
      const { service, getAiCallCount } = createCountingMockService([createMockPayload()]);
      const dueAt = new Date("2030-03-10T12:00:00.000Z");
      const fileBuffer = Buffer.from(
        buildRichDocumentText("Pré-check e submissão devem reutilizar a mesma análise."),
        "utf8",
      );

      const precheck = await service.runPreSubmissionAnalysis({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        reviewerUserId: fixture.userId,
        documentName: "Documento com pré-check",
        objective: "Reutilizar análise prévia.",
        dueAt,
        originalFileName: "prechecked.txt",
        mimeType: "text/plain",
        fileBuffer,
      });

      assert.equal(getAiCallCount(), 1);

      const submitted = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento com pré-check",
        objective: "Reutilizar análise prévia.",
        dueAt,
        originalFileName: "prechecked.txt",
        mimeType: "text/plain",
        fileBuffer,
        reuseAnalysisId: precheck.id,
      });

      assert.equal(submitted.created, true);
      assert.equal(submitted.analysis.id, precheck.id);
      assert.equal(getAiCallCount(), 1);
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("reuses detached precheck analysis for new version submission without calling AI again", async () => {
    const fixture = await createFixture();
    try {
      const { service, getAiCallCount } = createCountingMockService([
        createMockPayload(),
        createMockPayload(),
      ]);
      const dueAt = new Date("2030-03-18T12:00:00.000Z");

      const initial = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento versionado",
        objective: "Criar base para nova versão.",
        dueAt,
        originalFileName: "version-base.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Versão inicial."), "utf8"),
      });

      const versionBuffer = Buffer.from(
        buildRichDocumentText("Versão nova com análise reaproveitada."),
        "utf8",
      );

      const precheck = await service.runPreSubmissionAnalysis({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        itemId: initial.itemId!,
        reviewerUserId: fixture.userId,
        documentName: "Documento versionado",
        objective: "Criar base para nova versão.",
        dueAt,
        originalFileName: "version-next.txt",
        mimeType: "text/plain",
        fileBuffer: versionBuffer,
      });

      assert.equal(getAiCallCount(), 2);

      const nextVersion = await service.submitNewVersion({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: initial.itemId!,
        originalFileName: "version-next.txt",
        mimeType: "text/plain",
        fileBuffer: versionBuffer,
        submissionNote: "Mesma versão já analisada no pré-check.",
        reuseAnalysisId: precheck.id,
      });

      assert.equal(nextVersion.created, true);
      assert.equal(nextVersion.analysis.id, precheck.id);
      assert.equal(getAiCallCount(), 2);
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("learns recurring lawyer guidance from historical decisions and surfaces it to the assistant", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([createMockPayload(), createMockPayload()]);
      const documentName = "Petição inicial";

      const submitted = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName,
        objective: "Preparar a peça para revisão da advogada.",
        dueAt: new Date("2030-03-19T12:00:00.000Z"),
        originalFileName: "petition-v1.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Versão base para aprendizado histórico."), "utf8"),
      });

      await service.recordDecision({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
        decisionCode: "rejected",
        justification: "A peça ainda não está pronta para revisão.",
        rejectionReason: "Faltou cronologia completa dos fatos.",
        guidanceForNewVersion: "Adicionar linha do tempo objetiva e revisar os pedidos finais.",
      });

      const precheck = await service.runPreSubmissionAnalysis({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        reviewerUserId: fixture.userId,
        documentName,
        objective: "Preparar a peça para revisão da advogada.",
        dueAt: new Date("2030-03-22T12:00:00.000Z"),
        originalFileName: "petition-v2.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Nova versão com histórico da advogada aplicado."), "utf8"),
      });

      assert.ok(
        precheck.possibleRejectionReasons.some((item) =>
          item.includes("Faltou cronologia completa dos fatos."),
        ),
      );
      assert.ok(
        precheck.suggestions.some((item) =>
          item.includes("Adicionar linha do tempo objetiva e revisar os pedidos finais."),
        ),
      );
      assert.ok(
        precheck.alertPoints.some((item) =>
          item.includes("A advogada costuma devolver peças por"),
        ),
      );
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("stores anchored comments by excerpt and feeds them back into later AI review context", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([
        createMockPayload(),
        {
          assistantResponse:
            "O trecho precisa detalhar melhor a cronologia para reduzir risco de devolução.",
          knowledgeSummary:
            "Ao revisar esta peça, confirmar se a cronologia do trecho está completa antes da aprovação.",
        },
        createMockPayload(),
      ]);
      const sourceText = buildRichDocumentText();
      const selectedText = "Data principal: 2029-12-01.";
      const selectionStart = sourceText.indexOf(selectedText);
      const selectionEnd = selectionStart + selectedText.length;
      const selectionContextBefore = sourceText.slice(Math.max(0, selectionStart - 90), selectionStart);
      const selectionContextAfter = sourceText.slice(selectionEnd, selectionEnd + 90);

      const submitted = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Peça com comentário ancorado",
        objective: "Preparar a peça para revisão da advogada.",
        dueAt: new Date("2030-04-02T12:00:00.000Z"),
        originalFileName: "anchored-comment.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(sourceText, "utf8"),
      });

      assert.ok(submitted.itemId);
      assert.ok(submitted.versionId);

      const comment = await service.createAnchoredComment({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
        selectionStart: null,
        selectionEnd: null,
        selectedText,
        selectionContextBefore,
        selectionContextAfter,
        pageNumber: 2,
        anchorX: 0.41,
        anchorY: 0.22,
        selectionRects: [{ x: 0.31, y: 0.22, width: 0.18, height: 0.03 }],
        commentText: "Verificar se esta data está bem conectada ao restante da narrativa.",
      });

      assert.equal(comment.selectedText, selectedText);
      assert.equal(comment.pageNumber, 2);
      assert.equal(comment.selectionStart, selectionStart);
      assert.equal(comment.selectionEnd, selectionEnd);
      assert.deepEqual(comment.selectionRects, [{ x: 0.31, y: 0.22, width: 0.18, height: 0.03 }]);
      assert.match(comment.assistantResponse, /cronologia/i);
      assert.match(String(comment.knowledgeSummary), /cronologia/i);

      const detail = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: submitted.itemId!,
      });

      assert.equal(detail.versions[0]?.comments.length, 1);
      assert.equal(detail.versions[0]?.comments[0]?.selectedText, selectedText);
      assert.equal(detail.versions[0]?.comments[0]?.pageNumber, 2);

      const followUpAnalysis = await service.runReviewSupportAnalysis({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
      });

      assert.ok(
        Array.isArray(
          (followUpAnalysis.historicalContext as { anchoredReviewerComments?: unknown })
            .anchoredReviewerComments,
        ),
      );
      assert.match(
        JSON.stringify(
          (followUpAnalysis.historicalContext as { anchoredReviewerComments?: unknown[] })
            .anchoredReviewerComments ?? [],
        ),
        /cronologia/i,
      );
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("downgrades contradictory AI invalid verdict to needs_review outside final validation", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([
        createMockPayload({
          verdict: "invalid",
          verdictReason: "O histórico da advogada recomenda maior cautela.",
          summary: "Veredito: inválido. O histórico da advogada recomenda maior cautela.",
          confidence: 0.92,
          riskLevel: "low",
          risks: [],
          alertPoints: [],
        }),
      ]);

      const result = await service.runPreSubmissionAnalysis({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        reviewerUserId: fixture.userId,
        documentName: "Minuta consistente",
        objective: "Preparar a peça para revisão da advogada.",
        dueAt: new Date("2030-03-25T12:00:00.000Z"),
        originalFileName: "consistent-draft.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Peça consistente para evitar invalidação contraditória."), "utf8"),
      });

      assert.equal(result.verdict, "needs_review");
      assert.match(result.summary, /^Veredito:/);
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("supports final validation, allows cancelling approval, and only approves after explicit confirmation", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([
        createMockPayload(),
        createMockPayload({
          summary: "Validação final com alerta residual.",
          risks: [
            {
              title: "Risco residual",
              severity: "high",
              description: "Ainda há inconsistência relevante.",
              recommendation: "Ajustar antes da aprovação.",
            },
          ],
          confidence: 0.48,
          riskLevel: "high",
          alertPoints: ["Há inconsistência residual."],
        }),
        createMockPayload(),
      ]);

      const submitted = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento revisável",
        objective: "Testar validação final.",
        dueAt: new Date("2030-03-15T09:00:00.000Z"),
        originalFileName: "reviewable.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Conteúdo apto para revisão."), "utf8"),
      });

      const riskyValidation = await service.runFinalValidation({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
      });

      assert.equal(riskyValidation.riskLevel, "high");

      const stillPending = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: submitted.itemId!,
      });
      assert.equal(stillPending.item.status, "submitted");

      const safeValidation = await service.runFinalValidation({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
      });

      await service.recordDecision({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
        decisionCode: "approved",
        justification: "Ajustes conferidos e aprovação liberada.",
        finalValidationAnalysisId: safeValidation.id,
      });

      const approved = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: submitted.itemId!,
      });
      assert.equal(approved.item.status, "approved");
      assert.equal(approved.decisions[0].decisionCode, "approved");
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("rejects a document with mandatory reason and guidance", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([createMockPayload()]);
      const submitted = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento para reprovação",
        objective: "Testar reprovação.",
        dueAt: new Date("2030-03-20T09:00:00.000Z"),
        originalFileName: "reject.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Conteúdo com inconsistência material."), "utf8"),
      });

      await service.recordDecision({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: submitted.itemId!,
        versionId: submitted.versionId!,
        decisionCode: "rejected",
        justification: "Motivo formal da reprovação.",
        rejectionReason: "Dados obrigatórios ausentes.",
        guidanceForNewVersion: "Reenviar com os dados obrigatórios completos.",
      });

      const detail = await service.getItemDetail({
        lawFirmId: fixture.lawFirmId,
        itemId: submitted.itemId!,
      });

      assert.equal(detail.item.status, "rejected");
      assert.equal(detail.decisions[0].decisionCode, "rejected");
      assert.equal(detail.decisions[0].rejectionReason, "Dados obrigatórios ausentes.");
      assert.equal(
        detail.decisions[0].guidanceForNewVersion,
        "Reenviar com os dados obrigatórios completos.",
      );
    } finally {
      await cleanupFixture(fixture);
    }
  });

  await t.test("computes approval and rejection metrics for the last 12 months", async () => {
    const fixture = await createFixture();
    try {
      const service = createMockService([
        createMockPayload(),
        createMockPayload(),
        createMockPayload(),
      ]);

      await createApprovedDocument(fixture, service);

      const rejectedSubmission = await service.submitNewDocument({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        clientId: fixture.clientId,
        caseId: fixture.caseId,
        documentName: "Documento rejeitado",
        objective: "Compor métricas.",
        dueAt: new Date("2030-04-01T12:00:00.000Z"),
        originalFileName: "rejected.txt",
        mimeType: "text/plain",
        fileBuffer: Buffer.from(buildRichDocumentText("Documento para alimentar métrica de reprovação."), "utf8"),
      });

      await service.recordDecision({
        lawFirmId: fixture.lawFirmId,
        actor: actor(fixture.userId),
        itemId: rejectedSubmission.itemId!,
        versionId: rejectedSubmission.versionId!,
        decisionCode: "rejected",
        justification: "Reprovação para métricas.",
        rejectionReason: "Inconsistência detectada.",
        guidanceForNewVersion: "Ajustar a inconsistência e reenviar.",
      });

      const metrics = await service.getMetrics({
        lawFirmId: fixture.lawFirmId,
      });

      assert.equal(metrics.approvedCount, 1);
      assert.equal(metrics.rejectedCount, 1);
      assert.equal(metrics.approvalRate, 0.5);
      assert.equal(metrics.rejectionRate, 0.5);
      assert.equal(metrics.monthlySeries.length, 12);
      assert.ok(metrics.monthlySeries.some((item) => item.approved >= 1));
      assert.ok(metrics.monthlySeries.some((item) => item.rejected >= 1));
    } finally {
      await cleanupFixture(fixture);
    }
  });
});
