// E2E test for the in-app messaging flow built on the Notes page:
//   - Sender (user A) creates a note via the composer at /notes
//   - Sender clicks "Send" on the card and picks user B as recipient
//   - Recipient (user B) signs in, opens /notes → "Inbox" tab
//   - Sees the note from A with an "Unread" badge → opens it (which marks read)
//   - Replies inline; reply persists in the thread dialog
//   - Sender re-opens /notes → "Sent" tab and sees the recipient with the
//     "Replied" status pill.
import { test, expect } from "@playwright/test";
import pg from "pg";

const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
  throw new Error("DATABASE_URL must be set to run this UI test");
}

const TEST_PASSWORD = "TestPass123!";
const TEST_PASSWORD_HASH =
  "$2b$10$Bs636ukPMyz01nKrsi.5m.JlDXSN22AVCvn8cgPWWDbo5yJRQX2vu";

const pool = new pg.Pool({ connectionString: DATABASE_URL });

const createdUserIds = [];
const createdNoteIds = [];

function uniq() {
  return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}

async function createUser(prefix, displayEn) {
  const username = `${prefix}_${uniq()}`;
  const { rows } = await pool.query(
    `INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
     VALUES ($1, $2, $3, $4, 'en', true) RETURNING id`,
    [username, `${username}@example.com`, TEST_PASSWORD_HASH, displayEn],
  );
  const id = rows[0].id;
  createdUserIds.push(id);
  await pool.query(
    `INSERT INTO user_roles (user_id, role_id)
       SELECT $1, id FROM roles WHERE name = 'user'`,
    [id],
  );
  return { id, username, displayEn };
}

test.afterAll(async () => {
  if (createdNoteIds.length > 0) {
    await pool.query(
      `DELETE FROM note_replies WHERE note_id = ANY($1::int[])`,
      [createdNoteIds],
    );
    await pool.query(
      `DELETE FROM note_recipients WHERE note_id = ANY($1::int[])`,
      [createdNoteIds],
    );
    await pool.query(`DELETE FROM notes WHERE id = ANY($1::int[])`, [
      createdNoteIds,
    ]);
  }
  if (createdUserIds.length > 0) {
    await pool.query(
      `DELETE FROM notes WHERE user_id = ANY($1::int[])`,
      [createdUserIds],
    );
    await pool.query(
      `DELETE FROM notifications WHERE user_id = ANY($1::int[])`,
      [createdUserIds],
    );
    await pool.query(
      `DELETE FROM user_roles WHERE user_id = ANY($1::int[])`,
      [createdUserIds],
    );
    await pool.query(`DELETE FROM users WHERE id = ANY($1::int[])`, [
      createdUserIds,
    ]);
  }
  await pool.end();
});

async function loginViaUi(page, username) {
  await page.goto("/login");
  await page.locator("#username").fill(username);
  await page.locator("#password").fill(TEST_PASSWORD);
  await Promise.all([
    page.waitForURL((url) => !url.pathname.endsWith("/login"), {
      timeout: 15_000,
    }),
    page.locator('form button[type="submit"]').click(),
  ]);
}

async function logout(page) {
  await page.context().clearCookies();
}

test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
  page,
}) => {
  const sender = await createUser("notes_e2e_sender", "Sender Person");
  const recipient = await createUser("notes_e2e_recipient", "Recipient Person");

  // ---- Sender composes + sends a note ----
  await loginViaUi(page, sender.username);
  await page.goto("/notes");
  await expect(page.getByTestId("notes-page")).toBeVisible();

  const noteContent = `Please review attached. ${uniq()}`;

  await page.getByTestId("notes-composer-open").click();
  await page.getByTestId("notes-composer-content").fill(noteContent);

  const createPromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === "/api/notes" &&
      r.request().method() === "POST" &&
      r.status() >= 200 &&
      r.status() < 300,
    { timeout: 15_000 },
  );
  await page.getByTestId("notes-composer-save").click();
  const createResp = await createPromise;
  const createdNote = await createResp.json();
  createdNoteIds.push(createdNote.id);

  // The new card should appear with our content; click its Send button.
  const card = page.getByTestId(`note-card-${createdNote.id}`);
  await expect(card).toBeVisible();
  await card.hover();
  await page.getByTestId(`note-card-send-${createdNote.id}`).click();

  const sendDialog = page.getByTestId("send-note-dialog");
  await expect(sendDialog).toBeVisible();
  await page.getByTestId("send-note-search").fill(recipient.displayEn);
  await page
    .getByTestId(`send-recipient-option-${recipient.id}`)
    .click();

  const sendPromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === `/api/notes/${createdNote.id}/send` &&
      r.request().method() === "POST" &&
      r.status() >= 200 &&
      r.status() < 300,
    { timeout: 15_000 },
  );
  await page.getByTestId("send-note-submit").click();
  await sendPromise;
  await expect(page.getByTestId("send-note-confirm")).toBeHidden();
  await expect(sendDialog).toBeHidden();

  // ---- Switch to recipient session ----
  await logout(page);
  await loginViaUi(page, recipient.username);
  await page.goto("/notes");

  await page.getByTestId("notes-overflow-menu-trigger").click();
  await page.getByTestId("notes-overflow-received").click();
  const inboxCard = page.getByTestId(`received-note-card-${createdNote.id}`);
  await expect(inboxCard).toBeVisible();
  await expect(inboxCard).toContainText(noteContent);
  await expect(inboxCard).toContainText(sender.displayEn);
  await expect(
    inboxCard.getByTestId("status-badge-unread"),
  ).toBeVisible();

  // Open the thread (this auto-marks read).
  const readPromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === `/api/notes/${createdNote.id}/read` &&
      r.request().method() === "POST" &&
      r.status() >= 200 &&
      r.status() < 300,
    { timeout: 15_000 },
  );
  await inboxCard.click();
  const threadDialog = page.getByTestId("note-thread-dialog");
  await expect(threadDialog).toBeVisible();
  await readPromise;

  // Reply inline.
  const replyText = "Acknowledged, thanks.";
  await page.getByTestId("thread-reply-input").fill(replyText);
  const replyPromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === `/api/notes/${createdNote.id}/reply` &&
      r.request().method() === "POST" &&
      r.status() >= 200 &&
      r.status() < 300,
    { timeout: 15_000 },
  );
  await page.getByTestId("thread-reply-submit").click();
  await replyPromise;

  // Reply now appears in the thread.
  await expect(threadDialog).toContainText(replyText);
  await page.keyboard.press("Escape");

  // ---- Back to sender → Sent tab shows replied status ----
  await logout(page);
  await loginViaUi(page, sender.username);
  await page.goto("/notes");
  await page.getByTestId("notes-overflow-menu-trigger").click();
  await page.getByTestId("notes-overflow-sent").click();

  const sentCard = page.getByTestId(`sent-note-card-${createdNote.id}`);
  await expect(sentCard).toBeVisible();
  await expect(sentCard).toContainText(noteContent);
  await expect(sentCard).toContainText(recipient.displayEn);
  // Per-recipient status pill flipped to "replied".
  const recipientPill = page.getByTestId(
    `sent-recipient-status-${createdNote.id}-${recipient.id}`,
  );
  await expect(recipientPill).toBeVisible();

  // Open the thread from the sender side and confirm the reply is visible.
  await sentCard.click();
  const senderThread = page.getByTestId("note-thread-dialog");
  await expect(senderThread).toBeVisible();
  await expect(senderThread).toContainText(replyText);
});
