// E2E regression for the styled in-app delete confirmation on notes.
// Previously the trash icon called the browser's native confirm() which
// shows the raw Repl hostname and clashes with the rest of the app's
// design. This test guards the styled AlertDialog: cancelling keeps the
// note, confirming deletes it. We deliberately do NOT register a
// `dialog` event handler — if a native confirm fires the test will hang
// (and fail), proving the native dialog is gone.
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 = [];

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 };
}

test.afterAll(async () => {
  if (createdUserIds.length > 0) {
    await pool.query(`DELETE FROM notes 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 createNoteViaComposer(page, content) {
  await page.getByTestId("notes-composer").click();
  await page.getByTestId("notes-composer-content").fill(content);
  const savePromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === "/api/notes" &&
      r.request().method() === "POST" &&
      r.status() >= 200 &&
      r.status() < 300,
  );
  await page.mouse.click(10, 10);
  const resp = await savePromise;
  return resp.json();
}

test("delete note uses styled confirmation dialog (cancel keeps it, confirm deletes)", async ({
  page,
}) => {
  const user = await createUser("notes_delete_confirm", "Delete Tester");
  await loginViaUi(page, user.username);

  await page.goto("/notes");
  await expect(page.getByTestId("notes-page")).toBeVisible();

  const title = `delete-test ${uniq()}`;
  const note = await createNoteViaComposer(page, title);

  const card = page.getByTestId(`note-card-${note.id}`);
  await expect(card).toBeVisible();

  // Open the styled confirmation. Hover first so the action row appears.
  await card.hover();
  await page.getByTestId(`note-card-delete-${note.id}`).click();

  // Styled dialog appears (no native confirm).
  const dialog = page.getByTestId(`note-delete-dialog-${note.id}`);
  await expect(dialog).toBeVisible();

  // Cancel — note must still be there.
  await dialog.getByRole("button", { name: "Cancel" }).click();
  await expect(dialog).toBeHidden();
  await expect(card).toBeVisible();

  // Re-open and confirm.
  await card.hover();
  await page.getByTestId(`note-card-delete-${note.id}`).click();
  await expect(page.getByTestId(`note-delete-dialog-${note.id}`)).toBeVisible();

  const deletePromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === `/api/notes/${note.id}` &&
      r.request().method() === "DELETE" &&
      r.status() >= 200 &&
      r.status() < 300,
  );
  await page.getByTestId(`note-delete-confirm-${note.id}`).click();
  await deletePromise;

  await expect(card).toHaveCount(0);
});

test("delete label uses styled confirmation dialog (cancel keeps it, confirm deletes)", async ({
  page,
}) => {
  const user = await createUser("notes_label_delete", "Label Tester");
  await loginViaUi(page, user.username);

  await page.goto("/notes");
  await expect(page.getByTestId("notes-page")).toBeVisible();

  // Open the manage-labels dialog and create a label through the UI so
  // React Query has the row in its cache by the time we go to delete it.
  await page.getByTestId("notes-manage-labels-open").click();

  const labelName = `lbl-${uniq()}`;
  const createPromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === "/api/note-labels" &&
      r.request().method() === "POST" &&
      r.status() >= 200 &&
      r.status() < 300,
  );
  await page.getByPlaceholder(/new label|تصنيف جديد/i).fill(labelName);
  await page.getByPlaceholder(/new label|تصنيف جديد/i).press("Enter");
  const label = await (await createPromise).json();

  // The trash button uses opacity-0 + group-hover; force the click since
  // hovering a per-row group on portaled dialogs is flaky in headless.
  const labelDeleteBtn = page.getByTestId(`label-delete-${label.id}`);
  await labelDeleteBtn.click({ force: true });

  const dialog = page.getByTestId(`label-delete-dialog-${label.id}`);
  await expect(dialog).toBeVisible();

  const deletePromise = page.waitForResponse(
    (r) =>
      new URL(r.url()).pathname === `/api/note-labels/${label.id}` &&
      r.request().method() === "DELETE" &&
      r.status() >= 200 &&
      r.status() < 300,
  );
  await page.getByTestId(`label-delete-confirm-${label.id}`).click();
  await deletePromise;

  await expect(labelDeleteBtn).toHaveCount(0);
});
