// Admin user-edit Review-and-confirm dialog.
// Verifies:
//  - The editor's Save button is disabled while the form is unchanged.
//  - Editing a single field opens the Review dialog with exactly one
//    diff row, and Confirm persists the change.
//  - Editing multiple fields + group memberships shows them all.
//  - "Back to edit" preserves the in-progress form state.
// Runs in both `ar` and `en` to catch RTL layout regressions and to
// confirm the new locale keys resolve in both languages.

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 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 createdGroupNames = [];

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

async function createUser(prefix, opts = {}) {
  const username = `${prefix}_${uniqueSuffix()}`;
  const { rows } = await pool.query(
    `INSERT INTO users (username, email, password_hash, display_name_ar, display_name_en, preferred_language, is_active)
     VALUES ($1, $2, $3, $4, $5, $6, true) RETURNING id`,
    [
      username,
      `${username}@example.com`,
      TEST_PASSWORD_HASH,
      opts.displayNameAr ?? prefix,
      opts.displayNameEn ?? prefix,
      opts.preferredLanguage ?? "en",
    ],
  );
  const id = rows[0].id;
  createdUserIds.push(id);
  const roleName = opts.admin ? "admin" : "user";
  await pool.query(
    `INSERT INTO user_roles (user_id, role_id) SELECT $1, id FROM roles WHERE name = $2`,
    [id, roleName],
  );
  return { id, username };
}

async function createGroup(prefix) {
  const name = `${prefix}_${uniqueSuffix()}`;
  const { rows } = await pool.query(
    `INSERT INTO groups (name, description_en) VALUES ($1, 'edit-review test group') RETURNING id`,
    [name],
  );
  createdGroupNames.push(name);
  return { id: rows[0].id, name };
}

async function addUserToGroup(userId, groupId) {
  await pool.query(
    `INSERT INTO user_groups (user_id, group_id) VALUES ($1, $2)`,
    [userId, groupId],
  );
}

test.afterAll(async () => {
  if (createdGroupNames.length > 0) {
    await pool.query(
      `DELETE FROM user_groups WHERE group_id IN (SELECT id FROM groups WHERE name = ANY($1::text[]))`,
      [createdGroupNames],
    );
    await pool.query(
      `DELETE FROM groups WHERE name = ANY($1::text[])`,
      [createdGroupNames],
    );
  }
  if (createdUserIds.length > 0) {
    await pool.query(
      `DELETE FROM user_roles WHERE user_id = ANY($1::int[])`,
      [createdUserIds],
    );
    await pool.query(
      `DELETE FROM user_groups WHERE user_id = ANY($1::int[])`,
      [createdUserIds],
    );
    await pool.query(`DELETE FROM users WHERE id = ANY($1::int[])`, [
      createdUserIds,
    ]);
  }
  await pool.end();
});

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

async function setLanguage(page, lang) {
  // The app persists language via localStorage key `tx-lang` (see
  // artifacts/tx-os/src/i18n.ts). Set it before any page mount so the
  // very first render is in the chosen language.
  await page.addInitScript((l) => {
    try {
      window.localStorage.setItem("tx-lang", l);
    } catch {}
  }, lang);
}

const LABELS = {
  en: {
    title: "Review changes",
    back: "Back to edit",
    confirm: "Confirm & save",
    groupsAdded: "Groups added",
    groupsRemoved: "Groups removed",
  },
  ar: {
    title: "مراجعة التغييرات",
    back: "رجوع للتعديل",
    confirm: "تأكيد وحفظ",
    groupsAdded: "مجموعات مُضافة",
    groupsRemoved: "مجموعات مُزالة",
  },
};

for (const lang of ["en", "ar"]) {
  test.describe(`Admin user edit review (${lang})`, () => {
    test("Save is disabled when nothing changed; review opens on edit; back preserves state; confirm saves", async ({
      page,
    }) => {
      const labels = LABELS[lang];
      const admin = await createUser("rev_admin", {
        admin: true,
        displayNameAr: "مراجع",
        displayNameEn: "Reviewer",
        preferredLanguage: lang,
      });
      const target = await createUser("rev_target", {
        displayNameAr: "هدف قديم",
        displayNameEn: "Old Target",
      });
      const groupA = await createGroup("revA");
      const groupB = await createGroup("revB");
      // Pre-attach groupA so the editor opens with one selected group.
      await addUserToGroup(target.id, groupA.id);

      await setLanguage(page, lang);
      await login(page, admin.username, TEST_PASSWORD);
      await page.goto("/admin#section=users");

      // Find the target user's row and open the editor.
      const editBtn = page.locator(`[data-testid="user-edit-${target.id}"]`);
      await expect(editBtn).toBeVisible({ timeout: 10_000 });
      await editBtn.click();

      const saveBtn = page.locator('[data-testid="edit-user-save"]');
      await expect(saveBtn).toBeVisible();

      // 1) Pristine state → Save is disabled, no review.
      await expect(saveBtn).toBeDisabled();

      // 2) Edit only the Arabic display name → review shows exactly one row.
      const arInput = page.locator('[data-testid="edit-user-display-ar"]');
      await arInput.fill("هدف جديد");
      await expect(saveBtn).toBeEnabled();
      await saveBtn.click();

      const reviewDialog = page.locator(
        '[data-testid="edit-user-review-dialog"]',
      );
      await expect(reviewDialog).toBeVisible();
      await expect(reviewDialog).toContainText(labels.title);
      await expect(
        reviewDialog.locator(
          '[data-testid="edit-user-review-change-displayNameAr"]',
        ),
      ).toBeVisible();
      // Other field rows should NOT appear.
      await expect(
        reviewDialog.locator(
          '[data-testid="edit-user-review-change-displayNameEn"]',
        ),
      ).toHaveCount(0);

      // 3) Back to edit preserves form state.
      await page.locator('[data-testid="edit-user-review-back"]').click();
      await expect(reviewDialog).toBeHidden();
      await expect(arInput).toHaveValue("هدف جديد");

      // 4) Make additional changes: toggle a group on, toggle the existing
      //    one off, and change the language. Then re-open review.
      await page
        .locator('[data-testid="edit-user-group-search"]')
        .fill(groupB.name);
      await page
        .locator("label")
        .filter({ hasText: groupB.name })
        .locator('input[type="checkbox"]')
        .check();
      await page
        .locator('[data-testid="edit-user-group-search"]')
        .fill(groupA.name);
      await page
        .locator("label")
        .filter({ hasText: groupA.name })
        .locator('input[type="checkbox"]')
        .uncheck();
      await page
        .locator('[data-testid="edit-user-language"]')
        .selectOption("ar");

      await saveBtn.click();
      await expect(reviewDialog).toBeVisible();
      await expect(
        reviewDialog.locator(
          '[data-testid="edit-user-review-change-displayNameAr"]',
        ),
      ).toBeVisible();
      await expect(
        reviewDialog.locator(
          '[data-testid="edit-user-review-change-preferredLanguage"]',
        ),
      ).toBeVisible();
      const added = reviewDialog.locator(
        '[data-testid="edit-user-review-group-added"]',
      );
      const removed = reviewDialog.locator(
        '[data-testid="edit-user-review-group-removed"]',
      );
      await expect(added).toBeVisible();
      await expect(added).toContainText(groupB.name);
      await expect(added).toContainText(labels.groupsAdded);
      await expect(removed).toBeVisible();
      await expect(removed).toContainText(groupA.name);
      await expect(removed).toContainText(labels.groupsRemoved);

      // 5) Confirm saves and closes both modals.
      await page.locator('[data-testid="edit-user-review-confirm"]').click();
      await expect(reviewDialog).toBeHidden({ timeout: 10_000 });
      await expect(saveBtn).toBeHidden();

      // 6) Verify persistence in DB.
      const { rows: userRows } = await pool.query(
        `SELECT display_name_ar, preferred_language FROM users WHERE id = $1`,
        [target.id],
      );
      expect(userRows[0].display_name_ar).toBe("هدف جديد");
      expect(userRows[0].preferred_language).toBe("ar");
      const { rows: groupRows } = await pool.query(
        `SELECT group_id FROM user_groups WHERE user_id = $1 ORDER BY group_id`,
        [target.id],
      );
      const ids = groupRows.map((r) => r.group_id);
      expect(ids).toContain(groupB.id);
      expect(ids).not.toContain(groupA.id);
    });
  });
}
