/**
 * Authentication E2E Tests
 *
 * Tests the authentication flows for the Charon application including:
 * - Login with valid credentials
 * - Login with invalid credentials
 * - Login with non-existent user
 * - Logout functionality
 * - Session persistence across page refreshes
 * - Session expiration handling
 *
 * These tests use per-test user fixtures to ensure isolation and avoid
 * race conditions in parallel execution.
 *
 * @see /projects/Charon/docs/plans/current_spec.md - Section 4.1.2
 */

import { test, expect, loginUser, logoutUser, TEST_PASSWORD } from '../fixtures/auth-fixtures';
import { waitForToast, waitForLoadingComplete, waitForAPIResponse } from '../utils/wait-helpers';

test.describe('Authentication Flows', () => {
  test.describe('Login with Valid Credentials', () => {
    /**
     * Test: Successful login redirects to dashboard
     * Verifies that a user with valid credentials can log in and is redirected
     * to the main dashboard.
     */
    test('should login with valid credentials and redirect to dashboard', async ({
      page,
      adminUser,
    }) => {
      await test.step('Navigate to login page', async () => {
        await page.goto('/login');
        // Verify login page is loaded by checking for the email input field
        await expect(page.locator('input[type="email"]')).toBeVisible();
      });

      await test.step('Enter valid credentials', async () => {
        await page.locator('input[type="email"]').fill(adminUser.email);
        await page.locator('input[type="password"]').fill(TEST_PASSWORD);
      });

      await test.step('Submit login form', async () => {
        const responsePromise = waitForAPIResponse(page, '/api/v1/auth/login', { status: 200 });
        await page.getByRole('button', { name: /sign in/i }).click();
        await responsePromise;
      });

      await test.step('Verify redirect to dashboard', async () => {
        await page.waitForURL('/');
        await waitForLoadingComplete(page);
        // Dashboard should show main content area
        await expect(page.getByRole('main')).toBeVisible();
      });
    });

    /**
     * Test: Login form shows loading state during authentication
     */
    test('should show loading state during authentication', async ({ page, adminUser }) => {
      await page.goto('/login');

      await page.locator('input[type="email"]').fill(adminUser.email);
      await page.locator('input[type="password"]').fill(TEST_PASSWORD);

      await test.step('Verify loading state on submit', async () => {
        const loginButton = page.getByRole('button', { name: /sign in/i });
        await loginButton.click();

        // Button should be disabled or show loading indicator during request
        await expect(loginButton).toBeDisabled().catch(() => {
          // Some implementations use loading spinner instead
        });
      });

      await page.waitForURL('/');
    });
  });

  test.describe('Login with Invalid Credentials', () => {
    /**
     * Test: Wrong password shows error message
     * Verifies that entering an incorrect password displays an appropriate error.
     */
    test('should show error message for wrong password', async ({ page, adminUser }) => {
      await test.step('Navigate to login page', async () => {
        await page.goto('/login');
      });

      await test.step('Enter valid email with wrong password', async () => {
        await page.locator('input[type="email"]').fill(adminUser.email);
        await page.locator('input[type="password"]').fill('WrongPassword123!');
      });

      await test.step('Submit and verify error', async () => {
        await page.getByRole('button', { name: /sign in/i }).click();

        // Wait for error toast to appear (use specific test ID to avoid strict mode violation)
        const errorMessage = page.getByTestId('toast-error');
        await expect(errorMessage).toBeVisible({ timeout: 10000 });
      });

      await test.step('Verify user stays on login page', async () => {
        await expect(page).toHaveURL(/login/);
      });
    });

    /**
     * Test: Empty password shows validation error
     */
    test('should show validation error for empty password', async ({ page, adminUser }) => {
      await page.goto('/login');

      await page.locator('input[type="email"]').fill(adminUser.email);
      // Leave password empty

      await test.step('Submit and verify validation error', async () => {
        await page.getByRole('button', { name: /sign in/i }).click();

        // Check for HTML5 validation state, aria-invalid, or visible error text
        const passwordInput = page.locator('input[type="password"]');
        const isInvalid =
          (await passwordInput.getAttribute('aria-invalid')) === 'true' ||
          (await passwordInput.evaluate((el: HTMLInputElement) => !el.validity.valid)) ||
          (await page.getByText(/password.*required|required.*password/i).isVisible().catch(() => false));

        expect(isInvalid).toBeTruthy();
      });
    });
  });

  test.describe('Login with Non-existent User', () => {
    /**
     * Test: Unknown email shows error message
     * Verifies that a non-existent user email displays an appropriate error.
     */
    test('should show error message for non-existent user', async ({ page }) => {
      await test.step('Navigate to login page', async () => {
        await page.goto('/login');
      });

      await test.step('Enter non-existent email', async () => {
        const nonExistentEmail = `nonexistent-${Date.now()}@test.local`;
        await page.locator('input[type="email"]').fill(nonExistentEmail);
        await page.locator('input[type="password"]').fill('SomePassword123!');
      });

      await test.step('Submit and verify error', async () => {
        await page.getByRole('button', { name: /sign in/i }).click();

        // Wait for error toast to appear (use specific test ID to avoid strict mode violation)
        const errorMessage = page.getByTestId('toast-error');
        await expect(errorMessage).toBeVisible({ timeout: 10000 });
      });

      await test.step('Verify user stays on login page', async () => {
        await expect(page).toHaveURL(/login/);
      });
    });

    /**
     * Test: Invalid email format shows validation error
     */
    test('should show validation error for invalid email format', async ({ page }) => {
      await page.goto('/login');

      await page.locator('input[type="email"]').fill('not-an-email');
      await page.locator('input[type="password"]').fill('SomePassword123!');

      await test.step('Verify email validation error', async () => {
        await page.getByRole('button', { name: /sign in/i }).click();

        const emailInput = page.locator('input[type="email"]');

        // Check for HTML5 validation state or aria-invalid or visible error text
        const isInvalid =
          (await emailInput.getAttribute('aria-invalid')) === 'true' ||
          (await emailInput.evaluate((el: HTMLInputElement) => !el.validity.valid)) ||
          (await page.getByText(/valid email|invalid email/i).isVisible().catch(() => false));

        expect(isInvalid).toBeTruthy();
      });
    });
  });

  test.describe('Logout Functionality', () => {
    /**
     * Test: Logout redirects to login page and clears session
     * Verifies that clicking logout properly ends the session.
     */
    test('should logout and redirect to login page', async ({ page, adminUser }) => {
      await test.step('Login first', async () => {
        await loginUser(page, adminUser);
        await expect(page).toHaveURL('/');
      });

      await test.step('Perform logout', async () => {
        await logoutUser(page);
      });

      await test.step('Verify redirect to login page', async () => {
        await expect(page).toHaveURL(/login/);
      });

      await test.step('Verify session is cleared - cannot access protected route', async () => {
        await page.goto('/');
        // Should redirect to login since session is cleared
        await expect(page).toHaveURL(/login/);
      });
    });

    /**
     * Test: Logout clears authentication cookies
     */
    test('should clear authentication cookies on logout', async ({ page, adminUser }) => {
      await loginUser(page, adminUser);

      await test.step('Verify auth cookie exists before logout', async () => {
        const cookies = await page.context().cookies();
        const hasAuthCookie = cookies.some(
          (c) => c.name.includes('token') || c.name.includes('auth') || c.name.includes('session')
        );
        // Some implementations use localStorage instead of cookies
        if (!hasAuthCookie) {
          const localStorageToken = await page.evaluate(() =>
            localStorage.getItem('token') || localStorage.getItem('authToken')
          );
          expect(localStorageToken || hasAuthCookie).toBeTruthy();
        }
      });

      await logoutUser(page);

      await test.step('Verify auth data cleared after logout', async () => {
        const cookies = await page.context().cookies();
        const hasAuthCookie = cookies.some(
          (c) =>
            (c.name.includes('token') || c.name.includes('auth') || c.name.includes('session')) &&
            c.value !== ''
        );

        const localStorageToken = await page.evaluate(() =>
          localStorage.getItem('token') || localStorage.getItem('authToken')
        );

        expect(hasAuthCookie && localStorageToken).toBeFalsy();
      });
    });
  });

  test.describe('Session Persistence', () => {
    /**
     * Test: Session persists across page refresh
     * Verifies that refreshing the page maintains the logged-in state.
     */
    test('should maintain session after page refresh', async ({ page, adminUser }) => {
      await test.step('Login', async () => {
        await loginUser(page, adminUser);
        await expect(page).toHaveURL('/');
      });

      await test.step('Refresh page', async () => {
        await page.reload();
        await waitForLoadingComplete(page);
      });

      await test.step('Verify still logged in', async () => {
        // Should still be on dashboard, not redirected to login
        await expect(page).toHaveURL('/');
        await expect(page.getByRole('main')).toBeVisible();
      });
    });

    /**
     * Test: Session persists when navigating between pages
     */
    test('should maintain session when navigating between pages', async ({ page, adminUser }) => {
      await loginUser(page, adminUser);

      await test.step('Navigate to different pages', async () => {
        // Navigate to proxy hosts
        const proxyHostsLink = page.getByRole('link', { name: /proxy.*hosts?/i });
        if (await proxyHostsLink.isVisible()) {
          await proxyHostsLink.click();
          await waitForLoadingComplete(page);
          await expect(page.getByRole('main')).toBeVisible();
        }

        // Navigate back to dashboard
        await page.goto('/');
        await waitForLoadingComplete(page);
      });

      await test.step('Verify still logged in', async () => {
        await expect(page).toHaveURL('/');
        await expect(page.getByRole('main')).toBeVisible();
      });
    });
  });

  test.describe('Session Expiration Handling', () => {
    /**
     * Test: Expired token redirects to login
     * Simulates an expired session and verifies proper redirect.
     */
    test('should redirect to login when session expires', async ({ page, adminUser }) => {
      await loginUser(page, adminUser);

      await test.step('Simulate session expiration by clearing auth data', async () => {
        // Clear cookies and storage to simulate expiration
        await page.context().clearCookies();
        await page.evaluate(() => {
          localStorage.removeItem('token');
          localStorage.removeItem('authToken');
          localStorage.removeItem('charon_auth_token');
          sessionStorage.clear();
        });
      });

      await test.step('Attempt to access protected resource', async () => {
        await page.reload();
      });

      await test.step('Verify redirect to login', async () => {
        await expect(page).toHaveURL(/login/, { timeout: 10000 });
      });
    });

    /**
     * Test: API returns 401 on expired token, UI handles gracefully
     */
    test('should handle 401 response gracefully', async ({ page, adminUser }) => {
      await loginUser(page, adminUser);

      await test.step('Intercept API calls to return 401', async () => {
        await page.route('**/api/v1/**', async (route) => {
          // Let health check through, block others with 401
          if (route.request().url().includes('/health')) {
            await route.continue();
          } else {
            await route.fulfill({
              status: 401,
              contentType: 'application/json',
              body: JSON.stringify({ message: 'Token expired' }),
            });
          }
        });
      });

      await test.step('Trigger an API call by navigating', async () => {
        await page.goto('/proxy-hosts');
        // Wait for the 401 response to be processed and UI to react
        await page.waitForTimeout(2000);
      });

      await test.step('Verify redirect to login or error message', async () => {
        // Wait for potential redirect to login page
        try {
          await page.waitForURL(/\/login/, { timeout: 5000 });
        } catch {
          // If no redirect, check for session expired message
        }

        // Should either redirect to login or show session expired message
        const isLoginPage = page.url().includes('/login');
        const hasSessionExpiredMessage = await page
          .getByText(/session.*expired|please.*login|unauthorized/i)
          .isVisible()
          .catch(() => false);

        expect(isLoginPage || hasSessionExpiredMessage).toBeTruthy();
      });
    });
  });

  test.describe('Authentication Accessibility', () => {
    /**
     * Test: Login form is keyboard accessible
     */
    test('should be fully keyboard navigable', async ({ page }) => {
      await page.goto('/login');

      await test.step('Tab through form elements and verify focus order', async () => {
        const emailInput = page.locator('input[type="email"]');
        const passwordInput = page.locator('input[type="password"]');
        const submitButton = page.getByRole('button', { name: /sign in/i });

        // Focus the email input first
        await emailInput.focus();
        await expect(emailInput).toBeFocused();

        // Tab to password field
        await page.keyboard.press('Tab');
        await expect(passwordInput).toBeFocused();

        // Tab to submit button (may go through "Forgot Password" link first)
        await page.keyboard.press('Tab');
        // If there's a "Forgot Password" link, tab again
        if (!(await submitButton.evaluate((el) => el === document.activeElement))) {
          await page.keyboard.press('Tab');
        }
        await expect(submitButton).toBeFocused();
      });
    });

    /**
     * Test: Login form has proper ARIA labels
     */
    test('should have accessible form labels', async ({ page }) => {
      await page.goto('/login');

      await test.step('Verify email input is visible', async () => {
        const emailInput = page.locator('input[type="email"]');
        await expect(emailInput).toBeVisible();
      });

      await test.step('Verify password input is visible', async () => {
        const passwordInput = page.locator('input[type="password"]');
        await expect(passwordInput).toBeVisible();
      });

      await test.step('Verify submit button has accessible name', async () => {
        const submitButton = page.getByRole('button', { name: /sign in/i });
        await expect(submitButton).toBeVisible();
      });
    });

    /**
     * Test: Error messages are announced to screen readers
     */
    test('should announce errors to screen readers', async ({ page }) => {
      await page.goto('/login');

      await page.locator('input[type="email"]').fill('test@example.com');
      await page.locator('input[type="password"]').fill('wrongpassword');
      await page.getByRole('button', { name: /sign in/i }).click();

      await test.step('Verify error has proper ARIA role', async () => {
        const errorAlert = page.locator('[role="alert"], [aria-live="polite"], [aria-live="assertive"]');
        // Wait for error to appear
        await expect(errorAlert).toBeVisible({ timeout: 10000 }).catch(() => {
          // Some implementations may not use live regions
        });
      });
    });
  });
});
