import { test, expect, type Page } from '../../fixtures.js';
import {
  DIALOG,
  unique,
  searchAndFind,
  expectGoneFromSearch,
  openCreateDialog,
  openRowEditor,
  saveDialog,
  deleteViaDialog,
} from '../../helpers/settings.js';

// Countries carry a 2-letter ISO-style code; cross-run collisions on the
// shared staging DB are handled by createCountryWithFreeCode below, while
// display names stay collision-free via the shared unique() suffix.
const uniqueCode = () => {
  const a = String.fromCharCode(65 + Math.floor(Math.random() * 26));
  const b = String.fromCharCode(65 + Math.floor(Math.random() * 26));
  return `${a}${b}`;
};

/**
 * Fill and submit the create dialog once. Returns false when the dialog
 * stays open, which here is the EXPECTED duplicate-code signal consumed by
 * `createCountryWithFreeCode` (that is why this does not use the shared
 * `saveDialog`, which hard-asserts closing). Callers must never treat a
 * `false` as a reason to skip — the retry wrapper turns persistent failure
 * into a thrown error.
 */
async function createCountryViaUI(page: Page, code: string, nameDe: string, nameEn: string): Promise<boolean> {
  const dialog = await openCreateDialog(page);

  const inputs = dialog.locator('input');
  await inputs.nth(0).fill(code);
  await inputs.nth(1).fill(nameDe);
  await inputs.nth(2).fill(nameEn);

  const saveButton = dialog.locator('[data-testid="save-button"]').first();
  await expect(saveButton).toBeEnabled({ timeout: 3_000 });
  await saveButton.click();

  const closed = await dialog.waitFor({ state: 'hidden', timeout: 10_000 }).then(() => true).catch(() => false);
  if (!closed) {
    await dialog.locator('[data-testid="cancel-button"]').first().click();
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
    return false;
  }
  return true;
}

/**
 * Create a country with a free 2-letter code. ~250 of the 676 codes are
 * taken by ISO countries (~37% collision per attempt), so a single random
 * code is unreliable; loop with fresh codes until one lands. Returns the
 * code actually used. Throws (loud failure, not a silent skip) if the code
 * space appears exhausted after maxAttempts.
 */
async function createCountryWithFreeCode(
  page: Page,
  nameDe: string,
  nameEn: string,
  maxAttempts = 15
): Promise<string> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const code = uniqueCode();
    const created = await createCountryViaUI(page, code, nameDe, nameEn);
    if (created) return code;
  }
  throw new Error(
    `Could not create a country with a free code after ${maxAttempts} attempts (code space may be exhausted)`
  );
}

test.describe('System: Countries', () => {
  test('countries page loads and shows list', async ({ adminPage }) => {
    await adminPage.goto('/admin/system/countries');
    await expect(adminPage).toHaveURL(/\/system\/countries/);
    await expect(adminPage.locator('masterev-infinite-data-table').first()).toBeVisible({ timeout: 10_000 });
  });

  test('create a new country', async ({ adminPage }) => {
    const nameDe = `E2E Land ${unique()}`;
    await adminPage.goto('/admin/system/countries');

    const code = await createCountryWithFreeCode(adminPage, nameDe, `E2E Country ${unique()}`);
    expect(code).toMatch(/^[A-Z]{2}$/);

    await searchAndFind(adminPage, nameDe);
  });

  test('create and edit a country', async ({ adminPage }) => {
    const id = unique();
    const nameDe = `E2E Edit ${id}`;
    const editedName = `E2E Edited ${id}`;
    await adminPage.goto('/admin/system/countries');

    const code = await createCountryWithFreeCode(adminPage, nameDe, `E2E Edit EN ${id}`);

    // Open the created row's editor. The first input is the code field, so
    // hydratedValue doubles as a right-entity check.
    const dialog = await openRowEditor(adminPage, nameDe, { hydratedValue: code });

    // Code should be disabled on edit
    await expect(dialog.locator('input').first()).toBeDisabled();

    // Modify name
    const nameDeInput = dialog.locator('input').nth(1);
    await nameDeInput.fill(editedName);

    await saveDialog(dialog);

    // Verify edited name
    await searchAndFind(adminPage, editedName);
  });

  test('create and delete a country', async ({ adminPage }) => {
    const id = unique();
    const nameDe = `E2E Delete ${id}`;
    await adminPage.goto('/admin/system/countries');

    const code = await createCountryWithFreeCode(adminPage, nameDe, `E2E Delete EN ${id}`);

    // Open editor, delete via confirm dialog (hard-asserts the dialog
    // closes — a rejected deletion fails the test) and require the row to
    // disappear from a fresh search.
    const dialog = await openRowEditor(adminPage, nameDe, { hydratedValue: code });
    await deleteViaDialog(adminPage, dialog);
    await expectGoneFromSearch(adminPage, nameDe);
  });

  test('import missing countries and verify', async ({ adminPage }) => {
    await adminPage.goto('/admin/system/countries');
    await expect(adminPage).toHaveURL(/\/system\/countries/);

    // Ensure table has loaded before proceeding (guards against page-load flakes)
    const table = adminPage.locator('masterev-infinite-data-table').first();
    await expect(table).toBeVisible({ timeout: 10_000 });

    const importButton = adminPage.locator('[data-testid="import-button"]').first();
    await expect(importButton).toBeVisible({ timeout: 5_000 });

    await importButton.click();

    const dialog = adminPage.locator(DIALOG).first();
    await expect(dialog).toBeVisible({ timeout: 5_000 });

    // Click the run-import button inside the dialog
    const runImportButton = dialog.locator('[data-testid="run-import-button"]').first();
    await expect(runImportButton).toBeVisible({ timeout: 3_000 });
    await runImportButton.click();

    // Wait for dialog to close (import mutation runs, then dialog closes)
    await expect(dialog).not.toBeVisible({ timeout: 30_000 });

    // Dialog closing confirms success (on error, dialog stays open with error toast).
    // Verify the table still shows data after import.
    const tableBody = adminPage.locator('cdk-virtual-scroll-viewport').first();
    await expect(tableBody).toBeVisible({ timeout: 5_000 });
  });

  test('import dialog opens and closes via cancel', async ({ adminPage }) => {
    await adminPage.goto('/admin/system/countries');

    const importButton = adminPage.locator('[data-testid="import-button"]').first();
    await expect(importButton).toBeVisible({ timeout: 5_000 });

    await importButton.click();

    const dialog = adminPage.locator(DIALOG).first();
    await expect(dialog).toBeVisible({ timeout: 5_000 });

    // Verify run-import button exists
    await expect(dialog.locator('[data-testid="run-import-button"]').first()).toBeVisible();

    await dialog.locator('[data-testid="cancel-button"]').first().click();
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
  });

  test('validation: code without translations shows error on save attempt', async ({ adminPage }) => {
    await adminPage.goto('/admin/system/countries');

    const dialog = await openCreateDialog(adminPage);

    // Fill only code, leave name_de and name_en empty
    const inputs = dialog.locator('input');
    await inputs.nth(0).fill(uniqueCode());

    // Click save — the translationRequired validator should prevent saving
    const saveButton = dialog.locator('[data-testid="save-button"]').first();
    await saveButton.click();

    // Dialog should remain open (save rejected by validator)
    // Check for translation required error message
    const errorMessage = dialog.locator('.text-red-500');
    await expect(errorMessage).toBeVisible({ timeout: 3_000 });

    // Close dialog
    await dialog.locator('[data-testid="cancel-button"]').first().click();
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
  });

  test('name_en persists after edit', async ({ adminPage }) => {
    const id = unique();
    const nameDe = `E2E NameEN ${id}`;
    const nameEn = `E2E NameEN EN ${id}`;
    const editedNameEn = `E2E Edited EN ${id}`;
    await adminPage.goto('/admin/system/countries');

    const code = await createCountryWithFreeCode(adminPage, nameDe, nameEn);

    // Open editor (hydratedValue = code confirms the right row was opened)
    const dialog = await openRowEditor(adminPage, nameDe, { hydratedValue: code });

    // Verify name_en is populated
    const nameEnInput = dialog.locator('input').nth(2);
    await expect(nameEnInput).toHaveValue(nameEn);

    // Edit name_en
    await nameEnInput.fill(editedNameEn);
    await saveDialog(dialog);

    // Reopen and verify name_en persisted
    const dialog2 = await openRowEditor(adminPage, nameDe, { hydratedValue: code });
    await expect(dialog2.locator('input').nth(2)).toHaveValue(editedNameEn);

    await dialog2.locator('[data-testid="cancel-button"]').first().click();
    await expect(dialog2).not.toBeVisible({ timeout: 5_000 });
  });
});
