import { randomUUID } from "node:crypto";
import assert from "node:assert/strict";
import test from "node:test";
import { PDFDocument, StandardFonts } from "pdf-lib";
import { buildApp } from "../../app.js";
import { env } from "../../env.js";
import { prisma } from "../../lib/prisma.js";

type Fixture = {
  lawFirmId: string;
  lawFirmSlug: string;
  officeId: string;
  clientId: string;
  caseId: string;
  teamId: string;
  firstUserId: string;
  firstUserEmail: string;
  secondUserId: string;
  secondUserEmail: string;
  firstAgentId: string;
  secondAgentId: string;
};

async function buildSamplePdfBytes(text: string) {
  const document = await PDFDocument.create();
  const page = document.addPage([595.28, 841.89]);
  const font = await document.embedFont(StandardFonts.Helvetica);

  page.drawText(text, {
    x: 48,
    y: 760,
    size: 13,
    font,
  });

  return Buffer.from(await document.save());
}

async function createFixture() {
  const suffix = randomUUID().slice(0, 8);
  const fixture: Fixture = {
    lawFirmId: randomUUID(),
    lawFirmSlug: `team-msg-${suffix}`,
    officeId: randomUUID(),
    clientId: randomUUID(),
    caseId: randomUUID(),
    teamId: randomUUID(),
    firstUserId: randomUUID(),
    firstUserEmail: `ana-${suffix}@example.com`,
    secondUserId: randomUUID(),
    secondUserEmail: `bruno-${suffix}@example.com`,
    firstAgentId: randomUUID(),
    secondAgentId: randomUUID(),
  };

  await prisma.$executeRaw`
    INSERT INTO law_firms (
      id, name, legal_name, slug, timezone, default_locale, status, created_at, updated_at
    ) VALUES (
      ${fixture.lawFirmId},
      ${`Team Messages ${suffix}`},
      ${`Team Messages ${suffix}`},
      ${fixture.lawFirmSlug},
      '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',
      ${`TM${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.firstUserId},
      ${fixture.lawFirmId},
      ${fixture.officeId},
      'Ana',
      'Alpha',
      'Ana Alpha',
      ${fixture.firstUserEmail},
      'local',
      'pt-BR',
      'America/New_York',
      1,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    ),
    (
      ${fixture.secondUserId},
      ${fixture.lawFirmId},
      ${fixture.officeId},
      'Bruno',
      'Beta',
      'Bruno Beta',
      ${fixture.secondUserEmail},
      'local',
      'pt-BR',
      'America/New_York',
      1,
      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',
      'Mensagens',
      ${`cliente-${suffix}@example.com`},
      'pt-BR',
      ${fixture.firstUserId},
      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 do team',
      'employment_based',
      'eb3',
      'intake',
      'normal',
      NOW(),
      ${fixture.firstUserId},
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO workspace_memberships (
      id, law_firm_id, user_id, membership_role, is_default, created_by_user_id, joined_at
    ) VALUES
    (
      ${randomUUID()},
      ${fixture.lawFirmId},
      ${fixture.firstUserId},
      'admin',
      1,
      ${fixture.firstUserId},
      CURRENT_TIMESTAMP
    ),
    (
      ${randomUUID()},
      ${fixture.lawFirmId},
      ${fixture.secondUserId},
      'member',
      0,
      ${fixture.firstUserId},
      CURRENT_TIMESTAMP
    )
  `;

  await prisma.$executeRaw`
    INSERT INTO teams (
      id, law_firm_id, name, description, created_by_user_id, created_at, updated_at
    ) VALUES (
      ${fixture.teamId},
      ${fixture.lawFirmId},
      'Time jurídico',
      'Team usado pelos testes de mensagens.',
      ${fixture.firstUserId},
      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
    (
      ${randomUUID()},
      ${fixture.lawFirmId},
      ${fixture.teamId},
      ${fixture.firstUserId},
      'owner',
      ${fixture.firstUserId},
      CURRENT_TIMESTAMP
    ),
    (
      ${randomUUID()},
      ${fixture.lawFirmId},
      ${fixture.teamId},
      ${fixture.secondUserId},
      'member',
      ${fixture.firstUserId},
      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
    (
      ${fixture.firstAgentId},
      ${fixture.lawFirmId},
      ${fixture.teamId},
      ${fixture.firstUserId},
      'Agente de Ana',
      'Responder seguindo o estilo jurídico de Ana.',
      NULL,
      0,
      NULL,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    ),
    (
      ${fixture.secondAgentId},
      ${fixture.lawFirmId},
      ${fixture.teamId},
      ${fixture.secondUserId},
      'Agente de Bruno',
      'Responder seguindo o estilo jurídico de Bruno.',
      NULL,
      0,
      NULL,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  return fixture;
}

async function cleanupFixture(fixture: Fixture) {
  await prisma.$executeRaw`DELETE FROM team_message_attachment_comments WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_messages WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_message_thread_reads WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_message_thread_participants WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_message_threads 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}`;
  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 team_agents WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM team_memberships WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM workspace_memberships WHERE law_firm_id = ${fixture.lawFirmId}`;
  await prisma.$executeRaw`DELETE FROM teams WHERE law_firm_id = ${fixture.lawFirmId}`;
  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 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}`;
}

function buildMultipartRequest(input: {
  fields: Record<string, string>;
  file?: {
    fieldName: string;
    fileName: string;
    contentType: string;
    content: Buffer;
  };
}) {
  const boundary = `----neurav2-${randomUUID()}`;
  const chunks: Buffer[] = [];

  for (const [key, value] of Object.entries(input.fields)) {
    chunks.push(
      Buffer.from(
        `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`,
      ),
    );
  }

  if (input.file) {
    chunks.push(
      Buffer.from(
        `--${boundary}\r\nContent-Disposition: form-data; name="${input.file.fieldName}"; filename="${input.file.fileName}"\r\nContent-Type: ${input.file.contentType}\r\n\r\n`,
      ),
    );
    chunks.push(input.file.content);
    chunks.push(Buffer.from("\r\n"));
  }

  chunks.push(Buffer.from(`--${boundary}--\r\n`));

  return {
    payload: Buffer.concat(chunks),
    contentType: `multipart/form-data; boundary=${boundary}`,
  };
}

test("legal doubt team messages add memory to the sender agent", async () => {
  const fixture = await createFixture();
  const app = buildApp();

  try {
    await app.ready();

    const token = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });

    const response = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
      payload: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Dúvida sobre cláusula de rescisão",
        topicType: "legal_doubt",
        bodyText: "Tenho dúvida sobre como limitar a multa contratual neste caso.",
      },
    });

    assert.equal(response.statusCode, 201, response.body);

    const payload = response.json() as {
      thread: {
        id: string;
        topicType: string;
      };
      messages: Array<{
        bodyText: string;
      }>;
    };

    assert.equal(payload.thread.topicType, "legal_doubt");
    assert.equal(payload.messages.length, 1);
    assert.equal(
      payload.messages[0]?.bodyText,
      "Tenho dúvida sobre como limitar a multa contratual neste caso.",
    );

    const memoryRows = await prisma.$queryRaw<
      Array<{
        agent_id: string;
        memory_text: string;
      }>
    >`
      SELECT agent_id, memory_text
      FROM team_agent_memories
      WHERE law_firm_id = ${fixture.lawFirmId}
        AND team_id = ${fixture.teamId}
        AND source_type = 'team_message_legal_doubt'
      ORDER BY created_at ASC
    `;

    assert.equal(memoryRows.length, 1);
    assert.equal(memoryRows[0]?.agent_id, fixture.firstAgentId);
    assert.ok(
      memoryRows.every((row) => row.memory_text.includes("cláusula de rescisão")),
      "expected the legal subject to be captured in agent memory",
    );

    const updatedAgents = await prisma.$queryRaw<
      Array<{
        id: string;
        memory_count: number;
      }>
    >`
      SELECT id, memory_count
      FROM team_agents
      WHERE id IN (${fixture.firstAgentId}, ${fixture.secondAgentId})
      ORDER BY id ASC
    `;

    assert.equal(updatedAgents.length, 2);
    assert.equal(
      Number(updatedAgents.find((row) => row.id === fixture.firstAgentId)?.memory_count ?? 0),
      1,
    );
    assert.equal(
      Number(updatedAgents.find((row) => row.id === fixture.secondAgentId)?.memory_count ?? 0),
      0,
    );
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});

test("team unread summary counts incoming messages until the thread is opened", async () => {
  const fixture = await createFixture();
  const app = buildApp();

  try {
    await app.ready();

    const firstUserToken = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });
    const secondUserToken = app.jwt.sign({
      userId: fixture.secondUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.secondUserEmail,
      roleCodes: [],
      displayName: "Bruno Beta",
    });

    const createResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
      payload: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Ajustes finais",
        topicType: "general",
        bodyText: "Enviei a primeira versão para revisão.",
      },
    });

    assert.equal(createResponse.statusCode, 201, createResponse.body);

    const createdThread = createResponse.json() as {
      thread: {
        id: string;
      };
    };

    const replyResponse = await app.inject({
      method: "POST",
      url: `/team/messages/threads/${createdThread.thread.id}/messages`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(secondUserToken)}`,
      },
      payload: {
        teamId: fixture.teamId,
        bodyText: "Recebi. Vou revisar e te retorno ainda hoje.",
      },
    });

    assert.equal(replyResponse.statusCode, 200, replyResponse.body);

    const unreadBeforeOpenResponse = await app.inject({
      method: "GET",
      url: "/team/messages/unread-summary",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(unreadBeforeOpenResponse.statusCode, 200, unreadBeforeOpenResponse.body);

    const unreadBeforeOpen = unreadBeforeOpenResponse.json() as Array<{
      teamId: string;
      unreadMessagesCount: number;
      unreadThreadsCount: number;
    }>;
    const firstTeamUnread = unreadBeforeOpen.find((summary) => summary.teamId === fixture.teamId);

    assert.equal(firstTeamUnread?.unreadMessagesCount, 1);
    assert.equal(firstTeamUnread?.unreadThreadsCount, 1);

    const detailResponse = await app.inject({
      method: "GET",
      url: `/team/messages/threads/${createdThread.thread.id}?teamId=${encodeURIComponent(fixture.teamId)}`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(detailResponse.statusCode, 200, detailResponse.body);

    const unreadAfterOpenResponse = await app.inject({
      method: "GET",
      url: "/team/messages/unread-summary",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(unreadAfterOpenResponse.statusCode, 200, unreadAfterOpenResponse.body);

    const unreadAfterOpen = unreadAfterOpenResponse.json() as Array<{
      teamId: string;
      unreadMessagesCount: number;
      unreadThreadsCount: number;
    }>;
    const firstTeamUnreadAfterOpen = unreadAfterOpen.find(
      (summary) => summary.teamId === fixture.teamId,
    );

    assert.equal(firstTeamUnreadAfterOpen?.unreadMessagesCount, 0);
    assert.equal(firstTeamUnreadAfterOpen?.unreadThreadsCount, 0);
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});

test("document review team conversations require a valid review file and linked case", async () => {
  const fixture = await createFixture();
  const app = buildApp();

  try {
    await app.ready();

    const token = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });

    const invalidJsonResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
      payload: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Revisão do pacote",
        topicType: "document_review",
        bodyText: "Preciso que você revise os documentos deste caso.",
      },
    });

    assert.equal(invalidJsonResponse.statusCode, 400, invalidJsonResponse.body);

    const invalidMultipart = buildMultipartRequest({
      fields: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Revisão do pacote",
        caseId: fixture.caseId,
        reviewDueDate: "2026-04-05",
        bodyText: "Preciso que você revise os documentos deste caso.",
      },
      file: {
        fieldName: "file",
        fileName: "anotacoes.txt",
        contentType: "text/plain",
        content: Buffer.from("arquivo inválido"),
      },
    });

    const invalidFileResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads/document-review",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
        "content-type": invalidMultipart.contentType,
      },
      payload: invalidMultipart.payload,
    });

    assert.equal(invalidFileResponse.statusCode, 400, invalidFileResponse.body);

    const missingDueDateMultipart = buildMultipartRequest({
      fields: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Revisão sem prazo",
        caseId: fixture.caseId,
        bodyText: "Preciso que você revise os documentos deste caso.",
      },
      file: {
        fieldName: "file",
        fileName: "peticao.pdf",
        contentType: "application/pdf",
        content: Buffer.from("%PDF-1.4\n1 0 obj\n<<>>\nendobj\ntrailer\n<<>>\n%%EOF"),
      },
    });

    const missingDueDateResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads/document-review",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
        "content-type": missingDueDateMultipart.contentType,
      },
      payload: missingDueDateMultipart.payload,
    });

    assert.equal(missingDueDateResponse.statusCode, 400, missingDueDateResponse.body);

    const createMultipart = buildMultipartRequest({
      fields: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Revisão do pacote",
        caseId: fixture.caseId,
        reviewDueDate: "2026-04-05",
        bodyText: "Preciso que você revise os documentos deste caso.",
      },
      file: {
        fieldName: "file",
        fileName: "peticao.pdf",
        contentType: "application/pdf",
        content: Buffer.from("%PDF-1.4\n1 0 obj\n<<>>\nendobj\ntrailer\n<<>>\n%%EOF"),
      },
    });

    const createResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads/document-review",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
        "content-type": createMultipart.contentType,
      },
      payload: createMultipart.payload,
    });

    assert.equal(createResponse.statusCode, 201, createResponse.body);

    const payload = createResponse.json() as {
      thread: {
        topicType: string;
        reviewDueAt: string | null;
        relatedCase: {
          id: string;
          caseNumber: string;
          title: string;
        } | null;
      };
      messages: Array<{
        bodyText: string;
        attachment: {
          fileId: string;
          fileName: string;
          mimeType: string;
        } | null;
      }>;
    };

    assert.equal(payload.thread.topicType, "document_review");
    assert.ok(payload.thread.reviewDueAt?.startsWith("2026-04-05"));
    assert.equal(payload.thread.relatedCase?.id, fixture.caseId);
    assert.ok(payload.thread.relatedCase?.caseNumber.startsWith("CASE-"));
    assert.equal(payload.thread.relatedCase?.title, "Revisão documental do team");
    assert.equal(payload.messages[0]?.bodyText, "Preciso que você revise os documentos deste caso.");
    assert.equal(payload.messages[0]?.attachment?.fileName, "peticao.pdf");
    assert.equal(payload.messages[0]?.attachment?.mimeType, "application/pdf");
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});

test("document review attachments expose preview metadata and persist anchored PDF comments", async () => {
  const fixture = await createFixture();
  const app = buildApp();

  try {
    await app.ready();
    const originalPdfBytes = await buildSamplePdfBytes("Texto base do PDF para revisar.");

    const token = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });

    const createMultipart = buildMultipartRequest({
      fields: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Revisão comentável",
        caseId: fixture.caseId,
        reviewDueDate: "2026-04-08",
        bodyText: "Abra este PDF e marque os trechos críticos.",
      },
      file: {
        fieldName: "file",
        fileName: "comentavel.pdf",
        contentType: "application/pdf",
        content: originalPdfBytes,
      },
    });

    const createResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads/document-review",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
        "content-type": createMultipart.contentType,
      },
      payload: createMultipart.payload,
    });

    assert.equal(createResponse.statusCode, 201, createResponse.body);

    const createdThread = createResponse.json() as {
      thread: { id: string };
      messages: Array<{
        attachment: {
          fileId: string;
        } | null;
      }>;
    };

    const fileId = createdThread.messages[0]?.attachment?.fileId;
    assert.ok(fileId, "expected the created document review thread to include an attachment");

    const commentResponse = await app.inject({
      method: "POST",
      url: `/team/messages/threads/${createdThread.thread.id}/attachments/${fileId}/comments`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
      payload: {
        teamId: fixture.teamId,
        pageNumber: 1,
        anchorX: 0.42,
        anchorY: 0.18,
        selectionRects: [],
        selectedText: "",
        commentText: "Revisar esta redação antes de enviar a versão final.",
      },
    });

    assert.equal(commentResponse.statusCode, 201, commentResponse.body);

    const createdComment = commentResponse.json() as {
      id: string;
      pageNumber: number | null;
      selectedText: string;
      commentText: string;
      selectionRects: Array<{ x: number; y: number; width: number; height: number }>;
    };

    assert.equal(createdComment.pageNumber, 1);
    assert.equal(createdComment.selectedText, "");
    assert.equal(createdComment.commentText, "Revisar esta redação antes de enviar a versão final.");
    assert.equal(createdComment.selectionRects.length, 0);

    const previewResponse = await app.inject({
      method: "GET",
      url: `/team/messages/threads/${createdThread.thread.id}/attachments/${fileId}/review-preview?teamId=${fixture.teamId}`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
    });

    assert.equal(previewResponse.statusCode, 200, previewResponse.body);

    const previewPayload = previewResponse.json() as {
      attachment: {
        fileId: string;
        fileName: string;
        previewMode: string;
      };
      docxHtml: string | null;
      comments: Array<{
        id: string;
        pageNumber: number | null;
        selectedText: string;
        commentText: string;
      }>;
    };

    assert.equal(previewPayload.attachment.fileId, fileId);
    assert.equal(previewPayload.attachment.fileName, "comentavel.pdf");
    assert.equal(previewPayload.attachment.previewMode, "pdf");
    assert.equal(previewPayload.docxHtml, null);
    assert.equal(previewPayload.comments.length, 1);
    assert.equal(previewPayload.comments[0]?.id, createdComment.id);
    assert.equal(previewPayload.comments[0]?.pageNumber, 1);
    assert.equal(previewPayload.comments[0]?.selectedText, "");

    const updateCommentResponse = await app.inject({
      method: "PATCH",
      url: `/team/messages/threads/${createdThread.thread.id}/attachments/${fileId}/comments/${createdComment.id}`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
      payload: {
        teamId: fixture.teamId,
        pageNumber: 1,
        anchorX: 0.58,
        anchorY: 0.34,
        commentText: "Texto revisado depois de mover a anotação.",
      },
    });

    assert.equal(updateCommentResponse.statusCode, 200, updateCommentResponse.body);

    const updatedComment = updateCommentResponse.json() as {
      id: string;
      pageNumber: number | null;
      anchorX: number | null;
      anchorY: number | null;
      commentText: string;
    };

    assert.equal(updatedComment.id, createdComment.id);
    assert.equal(updatedComment.pageNumber, 1);
    assert.equal(updatedComment.anchorX, 0.58);
    assert.equal(updatedComment.anchorY, 0.34);
    assert.equal(updatedComment.commentText, "Texto revisado depois de mover a anotação.");

    const updatedFileResponse = await app.inject({
      method: "GET",
      url: `/files/${fileId}/content`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
    });

    assert.equal(updatedFileResponse.statusCode, 200, updatedFileResponse.body);
    assert.equal(updatedFileResponse.headers["content-type"], "application/pdf");
    assert.notDeepEqual(updatedFileResponse.rawPayload, originalPdfBytes);

    const memoryRows = await prisma.$queryRaw<
      Array<{
        agent_id: string;
        source_type: string;
        memory_text: string;
      }>
    >`
      SELECT agent_id, source_type, memory_text
      FROM team_agent_memories
      WHERE law_firm_id = ${fixture.lawFirmId}
        AND team_id = ${fixture.teamId}
        AND agent_id = ${fixture.firstAgentId}
      ORDER BY created_at ASC
    `;

    assert.equal(memoryRows.length, 3);
    assert.deepEqual(
      memoryRows.map((row) => row.source_type).sort(),
      [
        "team_message_document_review",
        "team_message_document_review_comment",
        "team_message_document_review_comment",
      ].sort(),
    );
    assert.ok(
      memoryRows.some((row) =>
        row.memory_text.includes("Abra este PDF e marque os trechos críticos."),
      ),
      "expected the opening document review message to feed the agent memory",
    );
    assert.ok(
      memoryRows.some((row) =>
        row.memory_text.includes("Revisar esta redação antes de enviar a versão final."),
      ),
      "expected the created PDF annotation to feed the agent memory",
    );
    assert.ok(
      memoryRows.some((row) =>
        row.memory_text.includes("Texto revisado depois de mover a anotação."),
      ),
      "expected the updated PDF annotation to feed the agent memory",
    );
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});

test("team messages can reply to a specific earlier message", async () => {
  const fixture = await createFixture();
  const app = buildApp();

  try {
    await app.ready();

    const firstUserToken = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });
    const secondUserToken = app.jwt.sign({
      userId: fixture.secondUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.secondUserEmail,
      roleCodes: [],
      displayName: "Bruno Beta",
    });

    const createResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
      payload: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Pergunta pontual",
        topicType: "general",
        bodyText: "Qual cláusula você acha mais arriscada aqui?",
      },
    });

    assert.equal(createResponse.statusCode, 201, createResponse.body);

    const createdThread = createResponse.json() as {
      thread: { id: string };
      messages: Array<{ id: string; bodyText: string }>;
    };

    const firstMessageId = createdThread.messages[0]?.id;
    assert.ok(firstMessageId, "expected the opening message id");

    const replyResponse = await app.inject({
      method: "POST",
      url: `/team/messages/threads/${createdThread.thread.id}/messages`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(secondUserToken)}`,
      },
      payload: {
        teamId: fixture.teamId,
        bodyText: "A multa e o prazo de rescisão me preocupam mais.",
        repliedMessageId: firstMessageId,
      },
    });

    assert.equal(replyResponse.statusCode, 200, replyResponse.body);

    const replyPayload = replyResponse.json() as {
      messages: Array<{
        id: string;
        bodyText: string;
        replyTo: {
          messageId: string;
          senderDisplayName: string;
          bodyText: string;
        } | null;
      }>;
    };

    const repliedMessage = replyPayload.messages.at(-1);

    assert.equal(repliedMessage?.bodyText, "A multa e o prazo de rescisão me preocupam mais.");
    assert.deepEqual(repliedMessage?.replyTo, {
      messageId: firstMessageId,
      senderDisplayName: "Ana Alpha",
      bodyText: "Qual cláusula você acha mais arriscada aqui?",
    });

    const memoryRows = await prisma.$queryRaw<
      Array<{
        agent_id: string;
        source_type: string;
        memory_text: string;
      }>
    >`
      SELECT agent_id, source_type, memory_text
      FROM team_agent_memories
      WHERE law_firm_id = ${fixture.lawFirmId}
        AND team_id = ${fixture.teamId}
      ORDER BY created_at ASC
    `;

    assert.equal(memoryRows.length, 2);
    assert.deepEqual(
      memoryRows.map((row) => row.agent_id).sort(),
      [fixture.firstAgentId, fixture.secondAgentId].sort(),
    );
    assert.ok(
      memoryRows.every((row) => row.source_type === "team_message_general_chat"),
      "expected general chat memories to use the general source type",
    );
    assert.ok(
      memoryRows.some(
        (row) =>
          row.agent_id === fixture.firstAgentId &&
          row.memory_text.includes("Qual cláusula você acha mais arriscada aqui?"),
      ),
      "expected opening message to be stored in the first sender agent memory",
    );
    assert.ok(
      memoryRows.some(
        (row) =>
          row.agent_id === fixture.secondAgentId &&
          row.memory_text.includes("A multa e o prazo de rescisão me preocupam mais."),
      ),
      "expected reply to be stored in the replier agent memory",
    );
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});

test("team messages can send an attachment reply with custom decision text", async () => {
  const fixture = await createFixture();
  const app = buildApp();

  try {
    await app.ready();

    const token = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });

    const createResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
      },
      payload: {
        teamId: fixture.teamId,
        recipientUserId: fixture.secondUserId,
        subject: "Resposta com PDF revisado",
        topicType: "general",
        bodyText: "Envio o arquivo base para revisão.",
      },
    });

    assert.equal(createResponse.statusCode, 201, createResponse.body);

    const createdThread = createResponse.json() as {
      thread: { id: string };
      messages: Array<{ id: string }>;
    };

    const openingMessageId = createdThread.messages[0]?.id;
    assert.ok(openingMessageId, "expected opening message id");

    const attachmentMultipart = buildMultipartRequest({
      fields: {
        teamId: fixture.teamId,
        repliedMessageId: openingMessageId,
        bodyText: "Documento revisado aprovado: peticao-revisada.pdf",
      },
      file: {
        fieldName: "file",
        fileName: "peticao-revisada.pdf",
        contentType: "application/pdf",
        content: await buildSamplePdfBytes("Versão revisada do documento."),
      },
    });

    const attachmentResponse = await app.inject({
      method: "POST",
      url: `/team/messages/threads/${createdThread.thread.id}/attachments`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(token)}`,
        "content-type": attachmentMultipart.contentType,
      },
      payload: attachmentMultipart.payload,
    });

    assert.equal(attachmentResponse.statusCode, 200, attachmentResponse.body);

    const attachmentPayload = attachmentResponse.json() as {
      messages: Array<{
        bodyText: string;
        replyTo: {
          messageId: string;
        } | null;
        attachment: {
          fileName: string;
        } | null;
      }>;
    };

    const sentAttachmentMessage = attachmentPayload.messages.at(-1);

    assert.equal(sentAttachmentMessage?.bodyText, "Documento revisado aprovado: peticao-revisada.pdf");
    assert.equal(sentAttachmentMessage?.replyTo?.messageId, openingMessageId);
    assert.equal(sentAttachmentMessage?.attachment?.fileName, "peticao-revisada.pdf");
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});

test("team unread summary keeps counts isolated across different teams", async () => {
  const fixture = await createFixture();
  const app = buildApp();
  const secondTeamId = randomUUID();

  try {
    await app.ready();

    await prisma.$executeRaw`
      INSERT INTO teams (
        id, law_firm_id, name, description, created_by_user_id, created_at, updated_at
      ) VALUES (
        ${secondTeamId},
        ${fixture.lawFirmId},
        'Time contratual',
        'Segundo team para validar contador de não lidas.',
        ${fixture.firstUserId},
        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
      (
        ${randomUUID()},
        ${fixture.lawFirmId},
        ${secondTeamId},
        ${fixture.firstUserId},
        'owner',
        ${fixture.firstUserId},
        CURRENT_TIMESTAMP
      ),
      (
        ${randomUUID()},
        ${fixture.lawFirmId},
        ${secondTeamId},
        ${fixture.secondUserId},
        'member',
        ${fixture.firstUserId},
        CURRENT_TIMESTAMP
      )
    `;

    const firstUserToken = app.jwt.sign({
      userId: fixture.firstUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.firstUserEmail,
      roleCodes: [],
      displayName: "Ana Alpha",
    });
    const secondUserToken = app.jwt.sign({
      userId: fixture.secondUserId,
      lawFirmId: fixture.lawFirmId,
      lawFirmSlug: fixture.lawFirmSlug,
      email: fixture.secondUserEmail,
      roleCodes: [],
      displayName: "Bruno Beta",
    });

    const firstTeamThreadResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(secondUserToken)}`,
      },
      payload: {
        teamId: fixture.teamId,
        recipientUserId: fixture.firstUserId,
        subject: "Pendência no team 1",
        topicType: "general",
        bodyText: "Mensagem do primeiro team.",
      },
    });

    assert.equal(firstTeamThreadResponse.statusCode, 201, firstTeamThreadResponse.body);

    const firstTeamThread = firstTeamThreadResponse.json() as {
      thread: { id: string };
    };

    const secondTeamThreadResponse = await app.inject({
      method: "POST",
      url: "/team/messages/threads",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(secondUserToken)}`,
      },
      payload: {
        teamId: secondTeamId,
        recipientUserId: fixture.firstUserId,
        subject: "Pendência no team 2",
        topicType: "general",
        bodyText: "Mensagem do segundo team.",
      },
    });

    assert.equal(secondTeamThreadResponse.statusCode, 201, secondTeamThreadResponse.body);

    const secondTeamThread = secondTeamThreadResponse.json() as {
      thread: { id: string };
    };

    const unreadBeforeOpenResponse = await app.inject({
      method: "GET",
      url: "/team/messages/unread-summary",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(unreadBeforeOpenResponse.statusCode, 200, unreadBeforeOpenResponse.body);

    const unreadBeforeOpen = unreadBeforeOpenResponse.json() as Array<{
      teamId: string;
      unreadMessagesCount: number;
      unreadThreadsCount: number;
    }>;

    assert.equal(
      unreadBeforeOpen.find((summary) => summary.teamId === fixture.teamId)?.unreadMessagesCount,
      1,
    );
    assert.equal(
      unreadBeforeOpen.find((summary) => summary.teamId === secondTeamId)?.unreadMessagesCount,
      1,
    );

    const openFirstTeamThreadResponse = await app.inject({
      method: "GET",
      url: `/team/messages/threads/${firstTeamThread.thread.id}?teamId=${encodeURIComponent(fixture.teamId)}`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(openFirstTeamThreadResponse.statusCode, 200, openFirstTeamThreadResponse.body);

    const unreadAfterFirstOpenResponse = await app.inject({
      method: "GET",
      url: "/team/messages/unread-summary",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(unreadAfterFirstOpenResponse.statusCode, 200, unreadAfterFirstOpenResponse.body);

    const unreadAfterFirstOpen = unreadAfterFirstOpenResponse.json() as Array<{
      teamId: string;
      unreadMessagesCount: number;
      unreadThreadsCount: number;
    }>;

    assert.equal(
      unreadAfterFirstOpen.find((summary) => summary.teamId === fixture.teamId)?.unreadMessagesCount,
      0,
    );
    assert.equal(
      unreadAfterFirstOpen.find((summary) => summary.teamId === secondTeamId)?.unreadMessagesCount,
      1,
    );

    const openSecondTeamThreadResponse = await app.inject({
      method: "GET",
      url: `/team/messages/threads/${secondTeamThread.thread.id}?teamId=${encodeURIComponent(secondTeamId)}`,
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(openSecondTeamThreadResponse.statusCode, 200, openSecondTeamThreadResponse.body);

    const unreadAfterSecondOpenResponse = await app.inject({
      method: "GET",
      url: "/team/messages/unread-summary",
      headers: {
        cookie: `${env.COOKIE_NAME}=${encodeURIComponent(firstUserToken)}`,
      },
    });

    assert.equal(unreadAfterSecondOpenResponse.statusCode, 200, unreadAfterSecondOpenResponse.body);

    const unreadAfterSecondOpen = unreadAfterSecondOpenResponse.json() as Array<{
      teamId: string;
      unreadMessagesCount: number;
      unreadThreadsCount: number;
    }>;

    assert.equal(
      unreadAfterSecondOpen.find((summary) => summary.teamId === fixture.teamId)?.unreadMessagesCount,
      0,
    );
    assert.equal(
      unreadAfterSecondOpen.find((summary) => summary.teamId === secondTeamId)?.unreadMessagesCount,
      0,
    );
  } finally {
    await app.close();
    await cleanupFixture(fixture);
  }
});
