import { test, expect, type Page } from '../../fixtures.js';

const DIALOG = '.p-dialog-mask .p-dialog';
const unique = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 6);

/**
 * Search for text in the data table and wait for the row to settle so
 * downstream `[data-testid="edit-button"].first()` reliably hits the
 * filtered row instead of whatever was at the top of the unfiltered
 * list. The unscoped `getByText` previously matched the search input
 * itself, leaking past the debounce.
 */
async function searchAndFind(page: Page, text: string): Promise<void> {
  const searchInput = page.locator('masterev-search-input input').first();
  await expect(searchInput).toBeVisible({ timeout: 5_000 });
  await searchInput.fill(text);
  await expect(
    page
      .locator('[data-testid="data-table"] [class*="border-b"]')
      .filter({ hasText: text })
      .first()
  ).toBeVisible({ timeout: 10_000 });
}

/** Create a user via the UI dialog, returning true on success */
async function createUserViaUI(page: Page, name: string, username: string): Promise<boolean> {
  const createButton = page.locator('[data-testid="create-button"]').first();
  await expect(createButton).toBeVisible({ timeout: 10_000 });
  await createButton.click();

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

  // Fill name and username
  await dialog.locator('input').first().fill(name);
  await dialog.locator('input').nth(1).fill(username);

  // Select role: click p-select dropdown, pick first option
  const roleSelect = dialog.locator('p-select').first();
  await roleSelect.click();
  const roleOption = page.locator('.p-select-overlay .p-select-option').first();
  const roleVisible = await roleOption.waitFor({ state: 'visible', timeout: 3_000 }).then(() => true).catch(() => false);
  if (roleVisible) await roleOption.click();

  // Select team: try p-multiselect first (multi-team feature flag), fallback to second p-select
  const multiSelect = dialog.locator('p-multiselect').first();
  const hasMultiSelect = await multiSelect.waitFor({ state: 'visible', timeout: 2_000 }).then(() => true).catch(() => false);
  if (hasMultiSelect) {
    await multiSelect.click();
    const teamOption = page.locator('.p-multiselect-overlay .p-multiselect-option').first();
    const teamVisible = await teamOption.waitFor({ state: 'visible', timeout: 3_000 }).then(() => true).catch(() => false);
    if (teamVisible) await teamOption.click();
    // Close overlay by pressing Escape
    await page.keyboard.press('Escape');
  } else {
    const teamSelect = dialog.locator('p-select').nth(1);
    const hasTeamSelect = await teamSelect.waitFor({ state: 'visible', timeout: 2_000 }).then(() => true).catch(() => false);
    if (hasTeamSelect) {
      await teamSelect.click();
      const teamOption = page.locator('.p-select-overlay .p-select-option').first();
      const teamVisible = await teamOption.waitFor({ state: 'visible', timeout: 3_000 }).then(() => true).catch(() => false);
      if (teamVisible) await teamOption.click();
    }
  }

  // Select at least one process
  const processListbox = dialog.locator('p-listbox').first();
  const processVisible = await processListbox.waitFor({ state: 'visible', timeout: 2_000 }).then(() => true).catch(() => false);
  if (processVisible) {
    const firstProcess = processListbox.locator('.p-listbox-option').first();
    await firstProcess.click();
  }

  // Try to save
  const saveButton = dialog.locator('[data-testid="save-button"]').first();
  const canSave = await expect(saveButton).toBeEnabled({ timeout: 3_000 }).then(() => true).catch(() => false);
  if (!canSave) {
    await dialog.locator('[data-testid="cancel-button"]').first().click();
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
    return false;
  }

  await saveButton.click();
  const closed = await expect(dialog).not.toBeVisible({ timeout: 15_000 }).then(() => true).catch(() => false);
  if (!closed) {
    await page.keyboard.press('Escape');
    await expect(dialog).not.toBeVisible({ timeout: 5_000 }).catch(() => {});
    return false;
  }
  return true;
}

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

  test('create user dialog opens and shows required fields', async ({ adminPage }) => {
    await adminPage.goto('/admin/settings/users');

    const createButton = adminPage.locator('[data-testid="create-button"]').first();
    await expect(createButton).toBeVisible({ timeout: 10_000 });
    await createButton.click();

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

    // Verify all expected form fields are present
    await expect(dialog.locator('input').first()).toBeVisible(); // name
    await expect(dialog.locator('input').nth(1)).toBeVisible();  // username

    // Role select should be present
    await expect(dialog.locator('p-select').first()).toBeVisible();

    // Save and cancel buttons
    await expect(dialog.locator('[data-testid="save-button"]').first()).toBeVisible();
    await expect(dialog.locator('[data-testid="cancel-button"]').first()).toBeVisible();

    // Fill basic fields
    await dialog.locator('input').first().fill(`E2E User ${unique()}`);
    await dialog.locator('input').nth(1).fill(`e2e-user-${unique()}`);

    // Select role: click dropdown and pick first option (ADMIN)
    const roleSelect = dialog.locator('p-select').first();
    await roleSelect.click();
    const roleOption = adminPage.locator('.p-select-overlay .p-select-option, .p-select-list li').first();
    if (await roleOption.isVisible({ timeout: 3_000 }).catch(() => false)) {
      await roleOption.click();
    }

    // Select processes: check first item in the processes listbox
    const processListbox = dialog.locator('p-listbox').first();
    if (await processListbox.isVisible({ timeout: 2_000 }).catch(() => false)) {
      const firstProcess = processListbox.locator('.p-listbox-option, li').first();
      await firstProcess.click();
    }

    // The user form has many required fields (team, processes, role).
    // Verify the save button exists, then close via cancel button.
    await expect(dialog.locator('[data-testid="save-button"]').first()).toBeVisible();
    await dialog.locator('[data-testid="cancel-button"]').first().click();
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
  });

  test('edit an existing user via dialog', async ({ adminPage }) => {
    await adminPage.goto('/admin/settings/users');

    const table = adminPage.locator('masterev-infinite-data-table').first();
    await expect(table).toBeVisible({ timeout: 10_000 });

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

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

    // Verify form is populated
    const nameInput = dialog.locator('input').first();
    await expect(nameInput).not.toHaveValue('');

    // Toggle isActive
    const activeToggle = dialog.locator('p-toggleswitch').first();
    if (await activeToggle.isVisible({ timeout: 2_000 }).catch(() => false)) {
      await activeToggle.click();
      // Toggle back
      await activeToggle.click();
    }

    // Close without saving
    await adminPage.keyboard.press('Escape');
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
  });

  test('create a new user and verify via search', async ({ adminPage }) => {
    const id = unique();
    const userName = `E2E Create ${id}`;
    const loginName = `e2e-create-${id}`;
    await adminPage.goto('/admin/settings/users');

    const created = await createUserViaUI(adminPage, userName, loginName);
    if (!created) {
      test.skip(true, 'Could not create user (required fields could not be filled)');
      return;
    }

    // Verify via search
    await searchAndFind(adminPage, userName);
  });

  test('create and edit a user name', async ({ adminPage }) => {
    const id = unique();
    const userName = `E2E Edit ${id}`;
    const editedName = `E2E Edited ${id}`;
    const loginName = `e2e-edit-${id}`;
    await adminPage.goto('/admin/settings/users');

    // Step 1: Create
    const created = await createUserViaUI(adminPage, userName, loginName);
    if (!created) {
      test.skip(true, 'Could not create user (required fields could not be filled)');
      return;
    }

    // Step 2: Search for the created user and click edit
    await searchAndFind(adminPage, userName);
    const editButton = adminPage.locator('[data-testid="edit-button"]').first();
    await editButton.click();

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

    // Step 3: Verify username input is disabled on edit
    const usernameInput = dialog.locator('input').nth(1);
    await expect(usernameInput).toBeDisabled({ timeout: 3_000 });

    // Step 4: Modify the name
    const nameInput = dialog.locator('input').first();
    await nameInput.fill(editedName);

    const saveButton = dialog.locator('[data-testid="save-button"]').first();
    await expect(saveButton).toBeEnabled({ timeout: 3_000 });
    await saveButton.click();
    const editClosed = await expect(dialog).not.toBeVisible({ timeout: 15_000 }).then(() => true).catch(() => false);
    if (!editClosed) {
      await adminPage.keyboard.press('Escape');
      test.skip(true, 'Save dialog did not close — server may have rejected the mutation');
      return;
    }

    // Step 5: Verify the updated name via search
    await searchAndFind(adminPage, editedName);
  });

  test('authMethods persistence', async ({ adminPage }) => {
    const id = unique();
    const userName = `E2E Auth ${id}`;
    const loginName = `e2e-auth-${id}`;
    await adminPage.goto('/admin/settings/users');

    const created = await createUserViaUI(adminPage, userName, loginName);
    if (!created) {
      test.skip(true, 'Could not create user');
      return;
    }

    // Open user and check authMethods listbox
    await searchAndFind(adminPage, userName);
    await adminPage.locator('[data-testid="edit-button"]').first().click();

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

    // The authMethods listbox is the second p-listbox
    const authListbox = dialog.locator('p-listbox').nth(1);
    await expect(authListbox).toBeVisible({ timeout: 3_000 });

    // Wait for accounts query to settle before interacting
    const authOptions = authListbox.locator('.p-listbox-option');
    await expect(async () => {
      expect(await authOptions.count()).toBeGreaterThan(0);
    }).toPass({ timeout: 5_000 });

    // Find a non-password auth method to toggle (password auth requires a password
    // to be set, so toggling it without a password won't persist the account)
    const optionCount = await authOptions.count();
    let targetOption = authOptions.first();
    let targetIndex = 0;
    for (let i = 0; i < optionCount; i++) {
      const optionText = await authOptions.nth(i).textContent();
      if (optionText && !/passwort|password/i.test(optionText)) {
        targetOption = authOptions.nth(i);
        targetIndex = i;
        break;
      }
    }

    // If all options are password-type, fill the password field before toggling
    const targetText = await targetOption.textContent();
    const isPasswordOption = /passwort|password/i.test(targetText || '');
    if (isPasswordOption) {
      const passwordInput = dialog.locator('input[type="password"], input[formcontrolname="password"]').first();
      const hasPassword = await passwordInput.isVisible({ timeout: 2_000 }).catch(() => false);
      if (hasPassword) {
        await passwordInput.fill('E2E-Test-Pw-123!');
      }
    }

    await adminPage.waitForLoadState('networkidle');
    const wasSelected = await targetOption.getAttribute('aria-selected') === 'true';
    await targetOption.click();

    // Verify selection changed
    await expect(async () => {
      const isNowSelected = await targetOption.getAttribute('aria-selected') === 'true';
      expect(isNowSelected).not.toBe(wasSelected);
    }).toPass({ timeout: 3_000 });
    const isNowSelected = await targetOption.getAttribute('aria-selected') === 'true';

    const saveButton = dialog.locator('[data-testid="save-button"]').first();
    const canSave = await expect(saveButton).toBeEnabled({ timeout: 3_000 }).then(() => true).catch(() => false);
    if (!canSave) {
      await dialog.locator('[data-testid="cancel-button"]').first().click();
      await expect(dialog).not.toBeVisible({ timeout: 5_000 });
      test.skip(true, 'Form invalid after toggling auth method');
      return;
    }
    await saveButton.click();
    await expect(dialog).not.toBeVisible({ timeout: 10_000 });

    // Re-open and verify the toggled auth method persisted
    await searchAndFind(adminPage, userName);
    await adminPage.locator('[data-testid="edit-button"]').first().click();

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

    const savedAuthListbox = editDialog.locator('p-listbox').nth(1);
    await expect(savedAuthListbox).toBeVisible({ timeout: 3_000 });
    const savedAuthOptions = savedAuthListbox.locator('.p-listbox-option');
    await expect(async () => {
      expect(await savedAuthOptions.count()).toBeGreaterThan(0);
    }).toPass({ timeout: 5_000 });

    const savedOption = savedAuthOptions.nth(targetIndex);
    await expect(async () => {
      const persistedSelected = await savedOption.getAttribute('aria-selected') === 'true';
      expect(persistedSelected).toBe(isNowSelected);
    }).toPass({ timeout: 5_000 });

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

  test('isActive toggle persistence', async ({ adminPage }) => {
    const id = unique();
    const userName = `E2E Active ${id}`;
    const loginName = `e2e-active-${id}`;
    await adminPage.goto('/admin/settings/users');

    const created = await createUserViaUI(adminPage, userName, loginName);
    if (!created) {
      test.skip(true, 'Could not create user');
      return;
    }

    // Open user and toggle isActive
    await searchAndFind(adminPage, userName);
    await adminPage.locator('[data-testid="edit-button"]').first().click();

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

    const toggleHost = dialog.locator('p-toggleswitch').first();
    await expect(toggleHost).toBeVisible({ timeout: 3_000 });
    const toggle = dialog.getByRole('switch').first();
    const wasChecked = await toggle.isChecked();

    // Use Playwright click (not evaluate) so Angular change detection picks up the change
    await toggleHost.click();

    // Verify the toggle state changed
    await expect(async () => {
      const isNowChecked = await toggle.isChecked();
      expect(isNowChecked).not.toBe(wasChecked);
    }).toPass({ timeout: 3_000 });

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

    // Re-open (reload first in case inactive users are filtered)
    await adminPage.goto('/admin/settings/users');
    await expect(adminPage.locator('masterev-infinite-data-table').first()).toBeVisible({ timeout: 10_000 });
    const searchInput = adminPage.locator('masterev-search-input input').first();
    await searchInput.fill(userName);
    const visible = await adminPage.getByText(userName, { exact: false }).isVisible({ timeout: 10_000 }).catch(() => false);
    if (!visible) {
      test.skip(true, 'User not visible after toggle — may be filtered by active status');
      return;
    }
    await adminPage.locator('[data-testid="edit-button"]').first().click();

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

    const savedToggle = editDialog.getByRole('switch').first();
    await expect(savedToggle).toBeVisible({ timeout: 3_000 });
    if (wasChecked) {
      await expect(savedToggle).not.toBeChecked();
    } else {
      await expect(savedToggle).toBeChecked();
    }

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

  test('processes persistence after create', async ({ adminPage }) => {
    const id = unique();
    const userName = `E2E Process ${id}`;
    const loginName = `e2e-process-${id}`;
    await adminPage.goto('/admin/settings/users');

    const created = await createUserViaUI(adminPage, userName, loginName);
    if (!created) {
      test.skip(true, 'Could not create user');
      return;
    }

    // Re-open and verify at least one process is selected
    await searchAndFind(adminPage, userName);
    await adminPage.locator('[data-testid="edit-button"]').first().click();

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

    const processListbox = dialog.locator('p-listbox').first();
    await expect(processListbox).toBeVisible({ timeout: 3_000 });

    // At least one process should be selected (we selected one during creation)
    const selectedProcesses = processListbox.locator('.p-listbox-option[aria-selected="true"]');
    await expect(async () => {
      expect(await selectedProcesses.count()).toBeGreaterThan(0);
    }).toPass({ timeout: 5_000 });

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

  test('role info dialog opens', async ({ adminPage }) => {
    await adminPage.goto('/admin/settings/users');

    const createButton = adminPage.locator('[data-testid="create-button"]').first();
    await expect(createButton).toBeVisible({ timeout: 10_000 });
    await createButton.click();

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

    // Click the info icon next to "Role" label
    const roleInfoIcon = dialog.locator('.pi-info-circle.clickable').first();
    const hasInfoIcon = await roleInfoIcon.isVisible({ timeout: 3_000 }).catch(() => false);
    if (!hasInfoIcon) {
      await dialog.locator('[data-testid="cancel-button"]').first().click();
      await expect(dialog).not.toBeVisible({ timeout: 5_000 });
      test.skip(true, 'Role info icon not available');
      return;
    }
    await roleInfoIcon.click();

    // A second dialog should open with role descriptions
    const infoDialog = adminPage.locator(DIALOG).nth(1);
    await expect(infoDialog).toBeVisible({ timeout: 5_000 });

    // Close info dialog
    await adminPage.keyboard.press('Escape');
    await adminPage.waitForTimeout(500);

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

  test('create and delete a user', async ({ adminPage }) => {
    const id = unique();
    const userName = `E2E Delete ${id}`;
    const loginName = `e2e-delete-${id}`;
    await adminPage.goto('/admin/settings/users');

    // Step 1: Create
    const created = await createUserViaUI(adminPage, userName, loginName);
    if (!created) {
      test.skip(true, 'Could not create user (required fields could not be filled)');
      return;
    }

    // Step 2: Search and click edit
    await searchAndFind(adminPage, userName);
    const editButton = adminPage.locator('[data-testid="edit-button"]').first();
    await editButton.click();

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

    // Step 3: Click delete
    const deleteButton = dialog.locator('[data-testid="delete-button"]').first();
    if (!await deleteButton.isVisible({ timeout: 3_000 }).catch(() => false)) {
      await dialog.locator('[data-testid="cancel-button"]').first().click();
      test.skip(true, 'Delete button not available');
      return;
    }
    await deleteButton.click();

    // Step 4: Confirm deletion in the ConfirmDialog
    const confirmDialog = adminPage.locator('[role="alertdialog"]:visible').first();
    await expect(confirmDialog).toBeVisible({ timeout: 5_000 });
    await confirmDialog.locator('.p-confirmdialog-accept-button, .p-confirm-dialog-accept').first().click();

    // Wait for dialogs to close and Convex mutation to complete
    await expect(dialog).not.toBeVisible({ timeout: 10_000 });

    // Step 5: Reload and verify user is gone from search results
    await adminPage.goto('/admin/settings/users');
    await expect(adminPage.locator('masterev-infinite-data-table').first()).toBeVisible({ timeout: 10_000 });
    const searchInput = adminPage.locator('masterev-search-input input').first();
    await searchInput.fill(userName);
    await expect(adminPage.getByText(userName)).not.toBeVisible({ timeout: 10_000 });
  });
});
