// ========================================
// i18n Helper Utilities for E2E Tests
// ========================================
// Reusable utilities for internationalization validation

import { Page, Locator } from '@playwright/test';

/**
 * Switch language and verify complete state update
 * @param page - Playwright Page object
 * @param locale - Target locale ('en' or 'zh')
 * @param languageSwitcher - Optional pre-located language switcher locator
 */
export async function switchLanguageAndVerify(
  page: Page,
  locale: 'en' | 'zh',
  languageSwitcher?: Locator
): Promise<void> {
  const switcher = languageSwitcher || page.getByRole('combobox', { name: /select language/i }).first();

  // Click to open dropdown
  await switcher.click();

  // Select the target language option
  const targetOption = locale === 'zh'
    ? page.getByText('中文')
    : page.getByText('English');

  await expectToBeVisible(targetOption);
  await targetOption.click();

  // Wait for HTML lang attribute update (explicit wait instead of timeout)
  await page.waitForFunction(
    (expectedLocale) => document.documentElement.lang === expectedLocale,
    locale,
    { timeout: 5000 }
  );

  // Verify the switcher text content is updated
  const expectedText = locale === 'zh' ? '中文' : 'English';
  const switcherText = await switcher.textContent();
  if (!switcherText?.includes(expectedText)) {
    throw new Error(`Expected language switcher to show "${expectedText}" but got "${switcherText}"`);
  }
}

/**
 * Verify complete i18n state after language switch
 * @param page - Playwright Page object
 * @param locale - Expected locale ('en' or 'zh')
 * @returns Object containing verification results
 */
export async function verifyI18nState(
  page: Page,
  locale: 'en' | 'zh'
): Promise<{
  langAttribute: string;
  localStorage: string | null;
  storageVerified: boolean;
}> {
  // Verify lang attribute
  const langAttribute = await page.evaluate(() => document.documentElement.lang);
  if (langAttribute !== locale) {
    throw new Error(`Expected lang="${locale}" but got lang="${langAttribute}"`);
  }

  // Verify localStorage
  const storage = await page.evaluate(() => localStorage.getItem('ccw-app-store'));
  const storageVerified = storage ? storage.includes(`"locale":"${locale}"`) : false;

  if (!storageVerified) {
    throw new Error(`localStorage does not contain locale="${locale}"`);
  }

  return { langAttribute, localStorage: storage, storageVerified };
}

/**
 * Verify language persists after page reload
 * @param page - Playwright Page object
 * @param locale - Expected locale ('en' or 'zh')
 */
export async function verifyPersistenceAfterReload(
  page: Page,
  locale: 'en' | 'zh'
): Promise<void> {
  // Reload the page
  await page.reload();
  await page.waitForLoadState('networkidle');

  // Verify language is maintained
  const lang = await page.evaluate(() => document.documentElement.lang);
  if (lang !== locale) {
    throw new Error(`Language not persisted after reload. Expected "${locale}" but got "${lang}"`);
  }

  // Verify language switcher still shows correct locale (check text content)
  const switcher = page.getByRole('combobox', { name: /select language/i }).first();
  const expectedText = locale === 'zh' ? '中文' : 'English';
  const switcherText = await switcher.textContent();
  if (!switcherText?.includes(expectedText)) {
    throw new Error(`Language switcher does not show "${expectedText}" after reload. Got "${switcherText}"`);
  }
}

/**
 * Check if translated content is visible on the page
 * @param page - Playwright Page object
 * @param locale - Expected locale ('en' or 'zh')
 * @param expectedText - Expected text content to verify
 */
export async function verifyTranslationCompleteness(
  page: Page,
  locale: 'en' | 'zh',
  expectedText?: string
): Promise<boolean> {
  if (expectedText) {
    const element = page.getByText(expectedText);
    const isVisible = await element.isVisible().catch(() => false);
    if (!isVisible) {
      throw new Error(`Expected translated text "${expectedText}" not found for locale "${locale}"`);
    }
    return true;
  }

  // If no specific text provided, check for general locale content
  const pageContent = await page.content();
  if (locale === 'zh') {
    // Check for Chinese characters
    return /[\u4e00-\u9fa5]/.test(pageContent);
  }

  // For English, check that we have English content
  return pageContent.length > 0;
}

/**
 * Verify aria-label is updated on language change
 * @param page - Playwright Page object
 * @param locator - Element locator to check
 * @param expectedLabel - Expected aria-label (partial match supported)
 */
export async function verifyAriaLabelUpdated(
  page: Page,
  locator: Locator,
  expectedLabel: string
): Promise<void> {
  const ariaLabel = await locator.getAttribute('aria-label');
  if (!ariaLabel || !ariaLabel.includes(expectedLabel)) {
    throw new Error(`Expected aria-label to include "${expectedLabel}" but got "${ariaLabel}"`);
  }
}

/**
 * Navigate to a route and verify language is maintained
 * @param page - Playwright Page object
 * @param route - Route path to navigate to
 * @param locale - Expected locale ('en' or 'zh')
 */
export async function navigateAndVerifyLanguage(
  page: Page,
  route: string,
  locale: 'en' | 'zh'
): Promise<void> {
  await page.goto(route, { waitUntil: 'networkidle' as const });

  // Verify language is maintained after navigation
  const lang = await page.evaluate(() => document.documentElement.lang);
  if (lang !== locale) {
    throw new Error(`Language not maintained after navigation to ${route}. Expected "${locale}" but got "${lang}"`);
  }
}

/**
 * Get localStorage state as parsed object
 * @param page - Playwright Page object
 * @returns Parsed localStorage object or null
 */
export async function getLocalStorageState(page: Page): Promise<any> {
  const storage = await page.evaluate(() => {
    const item = localStorage.getItem('ccw-app-store');
    return item ? JSON.parse(item) : null;
  });
  return storage;
}

/**
 * Set localStorage state (useful for pre-test setup)
 * @param page - Playwright Page object
 * @param state - State object to set in localStorage
 */
export async function setLocalStorageState(page: Page, state: any): Promise<void> {
  await page.evaluate((s) => {
    localStorage.setItem('ccw-app-store', JSON.stringify(s));
  }, state);
}

/**
 * Expect wrapper to handle errors gracefully
 */
async function expectToBeVisible(locator: Locator): Promise<void> {
  const isVisible = await locator.isVisible().catch(() => false);
  if (!isVisible) {
    throw new Error(`Expected element to be visible but it was not found`);
  }
}

/**
 * Verify language switcher shows the correct selected option
 * Note: Custom Select component uses button, not input, so we check visible text
 */
async function expectToHaveValue(locator: Locator, value: string): Promise<void> {
  // For the custom Select component, check if the target language text is visible
  // The SelectValue displays the selected option text (English or 中文)
  const expectedText = value === 'zh' ? '中文' : 'English';

  // Check if the switcher contains the expected language text
  const switcherText = await locator.textContent().catch(() => '');

  // The switcher may show flag emoji + label or just the label
  if (!switcherText.includes(expectedText)) {
    throw new Error(`Expected language switcher to show "${expectedText}" but got "${switcherText}"`);
  }
}

// ========================================
// Enhanced Error Monitoring (API Gap Fix)
// ========================================

/**
 * Console error tracker for catching proxy errors
 * Usage: Call setupConsoleErrorMonitoring() in test.beforeEach()
 */
export interface ConsoleErrorTracker {
  errors: string[];
  warnings: string[];
  start: () => void;
  stop: () => void;
  assertNoErrors: (ignorePatterns?: string[]) => void;
  getErrors: () => string[];
}

export function setupConsoleErrorMonitoring(page: Page): ConsoleErrorTracker {
  const errors: string[] = [];
  const warnings: string[] = [];

  const consoleHandler = (msg: any) => {
    const text = msg.text();
    if (msg.type() === 'error') {
      errors.push(text);
    } else if (msg.type() === 'warning') {
      warnings.push(text);
    }
  };

  return {
    errors,
    warnings,
    start: () => {
      page.on('console', consoleHandler);
    },
    stop: () => {
      page.off('console', consoleHandler);
    },
    assertNoErrors: (ignorePatterns: string[] = []) => {
      // Filter out errors matching ignore patterns
      const filteredErrors = errors.filter(
        (error) => !ignorePatterns.some((pattern) => error.includes(pattern))
      );

      if (filteredErrors.length > 0) {
        throw new Error(
          `Console errors detected:\n${filteredErrors.map((e, i) => `  ${i + 1}. ${e}`).join('\n')}`
        );
      }
    },
    getErrors: () => errors,
  };
}

/**
 * API response tracker for catching failed API calls
 * Usage: Call setupAPIResponseMonitoring(page) in test.beforeEach()
 */
export interface APIResponseTracker {
  failedRequests: Array<{ url: string; status: number; statusText: string }>;
  start: () => void;
  stop: () => void;
  assertNoFailures: (ignorePatterns?: string[]) => void;
  getFailedRequests: () => Array<{ url: string; status: number; statusText: string }>;
}

export function setupAPIResponseMonitoring(page: Page): APIResponseTracker {
  const failedRequests: Array<{ url: string; status: number; statusText: string }> = [];

  const responseHandler = (response: any) => {
    const url = response.url();
    // Only track API calls
    if (url.includes('/api/') && !response.ok()) {
      failedRequests.push({
        url,
        status: response.status(),
        statusText: response.statusText(),
      });
    }
  };

  return {
    failedRequests,
    start: () => {
      page.on('response', responseHandler);
    },
    stop: () => {
      page.off('response', responseHandler);
    },
    assertNoFailures: (ignorePatterns: string[] = []) => {
      const filtered = failedRequests.filter(
        (req) => !ignorePatterns.some((pattern) => req.url.includes(pattern))
      );

      if (filtered.length > 0) {
        throw new Error(
          `API failures detected:\n${filtered
            .map((f, i) => `  ${i + 1}. ${f.url} - ${f.status} ${f.statusText}`)
            .join('\n')}`
        );
      }
    },
    getFailedRequests: () => failedRequests,
  };
}

/**
 * Combined error monitoring setup
 * Sets up both console and API monitoring with automatic cleanup
 * Usage in test:
 *
 * test.beforeEach(async ({ page }) => {
 *   const monitoring = setupEnhancedMonitoring(page);
 *   await page.goto('/', { waitUntil: 'networkidle' });
 *   // ... test code ...
 *   monitoring.assertClean();
 * });
 *
 * Note: API errors are ignored by default for E2E tests that mock APIs.
 * Console 404 errors from API endpoints are also ignored by default.
 * Set ignoreAPIPatterns to [] to enable strict checking.
 */
export interface EnhancedMonitoring {
  console: ConsoleErrorTracker;
  api: APIResponseTracker;
  assertClean: (options?: { ignoreAPIPatterns?: string[]; allowWarnings?: boolean }) => void;
  stop: () => void;
}

export function setupEnhancedMonitoring(page: Page): EnhancedMonitoring {
  const consoleTracker = setupConsoleErrorMonitoring(page);
  const apiTracker = setupAPIResponseMonitoring(page);

  // Start monitoring immediately
  consoleTracker.start();
  apiTracker.start();

  return {
    console: consoleTracker,
    api: apiTracker,
    assertClean: (options = {}) => {
      // Default: ignore all API errors since E2E tests often mock APIs
      // Also ignore console 404 errors from API endpoints
      const { ignoreAPIPatterns = ['/api/'], allowWarnings = false } = options;

      // Check for console errors (warnings optional)
      if (!allowWarnings && consoleTracker.warnings.length > 0) {
        console.warn(
          `Console warnings detected:\n${consoleTracker.warnings.map((w, i) => `  ${i + 1}. ${w}`).join('\n')}`
        );
      }

      // Assert no console errors, ignoring common API error status codes and patterns
      // Ignore: 404 (not found), 500 (server error), 401 (unauthorized), 403 (forbidden), 400 (bad request)
      // Also ignore errors matching the provided API patterns
      const ignoreStatusCodes = ['404', '500', '401', '403', '400'];
      const ignorePatterns = ignoreAPIPatterns.map((p) => p.replace('/api/', '').replace('/**', '').replace('*', ''));
      const consoleIgnorePatterns = [
        ...ignoreStatusCodes,
        'Failed to load resource',
        'api/',
        ...ignorePatterns
      ];
      consoleTracker.assertNoErrors(consoleIgnorePatterns);

      // Assert no API failures (with optional ignore patterns)
      apiTracker.assertNoFailures(ignoreAPIPatterns);
    },
    stop: () => {
      consoleTracker.stop();
      apiTracker.stop();
    },
  };
}

/**
 * Global WebSocket mock setup for E2E tests
 * Prevents WebSocket connection errors by mocking all WebSocket routes
 * 
 * Usage in test.beforeEach:
 * ```
 * test.beforeEach(async ({ page }) => {
 *   await setupGlobalWebSocketMock(page);
 *   await page.goto('/some-page', { waitUntil: 'domcontentloaded' });
 * });
 * ```
 * 
 * @param page - Playwright Page object
 */
export async function setupGlobalWebSocketMock(page: Page): Promise<void> {
  // List of common WebSocket endpoints in the application
  const wsEndpoints = [
    '/ws/loops',
    '/ws/session',
    '/ws/activity',
    '/ws/notifications',
    '/ws/workspace',
    '/ws/workflow',
    '/ws/ticker',
  ];

  // Mock each WebSocket endpoint with proper WebSocket upgrade response
  for (const endpoint of wsEndpoints) {
    await page.route(`**${endpoint}**`, (route) => {
      route.fulfill({
        status: 101, // WebSocket Switching Protocols
        headers: {
          'Connection': 'Upgrade',
          'Upgrade': 'websocket',
          'Sec-WebSocket-Accept': 'mock-accept-token',
        },
        body: '',
      });
    });
  }

  // Also set up window.__mockWebSocket for message simulation compatibility
  await page.addInitScript(() => {
    (window as any).__mockWebSocket = {
      readyState: 1, // OPEN
      onmessage: null,
      send: (data: string) => {
        // Mock send - do nothing
      },
      close: () => {
        // Mock close - do nothing
      },
    };
  });
}
