// ============================================================
// SHARED PLAYWRIGHT FIXTURES
// ============================================================
// Provides:
//   - `test` — augmented @playwright/test with auto-applied uncaught-error
//     guards on every page (pageerror + console.error → test fail)
//   - `authedPage` fixture — a logged-in page, ready to drive
//   - `mockAI(page, overrides)` — installs page.route() handlers that
//     intercept AI endpoints and return canned JSON. Pass `{ real: true }`
//     or set E2E_USE_REAL_AI=1 to bypass mocking and call real backend.
// ============================================================

const base = require('@playwright/test');

// ── Environment ──────────────────────────────────────────────
const E2E_BASE_INTERNAL = 'http://pediatric-ai-scribe-e2e:3000';
const E2E_BASE_EXTERNAL = 'http://host.docker.internal:3553';
const E2E_BASE = process.env.E2E_AUTH_BASE_URL || E2E_BASE_INTERNAL;

const TEST_EMAIL = process.env.E2E_TEST_EMAIL || 'e2e-user@ped-ai.test';
const TEST_PASSWORD = process.env.E2E_TEST_PASSWORD || 'E2E-testPassword123!';

const USE_REAL_AI = process.env.E2E_USE_REAL_AI === '1' || process.env.E2E_USE_REAL_AI === 'true';

// ── Console-error allowlist ─────────────────────────────────
// Some console messages are expected / noise (e.g. favicon 404). If a
// message matches one of these patterns it does NOT fail the test.
const CONSOLE_ERROR_ALLOWLIST = [
  /favicon/i,
  /\/api\/models/i,                                  // When no AI provider configured yet
  /Cross-Origin-Opener-Policy/i,                     // Chrome warning on non-HTTPS e2e server
  /Failed to load resource.*(400|401|403|404|500|502|503)/i,  // Any HTTP error on subsidiary fetches — smoke tests only verify UI renders, deeper integration tests validate endpoint contracts separately
  /net::ERR_BLOCKED_BY_CLIENT/i,                     // Adblocker etc.
  /Cloudflare Turnstile.*110200/i,                   // Expected on e2e: site key hard-coded in index.html but e2e uses different host → domain mismatch error
  /challenges\.cloudflare\.com\/turnstile/i,         // Turnstile script errors from same root cause
];
function isAllowedConsoleNoise(text) {
  return CONSOLE_ERROR_ALLOWLIST.some(re => re.test(text));
}

// ── Auth — module-scoped token cache ────────────────────────
// Keeps one login per worker to avoid the 10/15-min login rate-limiter.
let _tokenCache = null;
async function getAuthToken(request) {
  if (_tokenCache) return _tokenCache;
  const r = await request.post(E2E_BASE + '/api/auth/login', {
    data: { email: TEST_EMAIL, password: TEST_PASSWORD },
  });
  if (!r.ok()) {
    const text = await r.text();
    throw new Error(`E2E login failed (status ${r.status()}): ${text}`);
  }
  const body = await r.json();
  if (!body.token) throw new Error('Login response missing token: ' + JSON.stringify(body));
  _tokenCache = body.token;
  return _tokenCache;
}

async function loginAs(context, request) {
  const token = await getAuthToken(request);
  const url = new URL(E2E_BASE);
  await context.addCookies([{
    name: 'ped_auth',
    value: token,
    domain: url.hostname,
    path: '/',
    httpOnly: true,
    secure: false,
    sameSite: 'Lax',
  }]);
}

// ── AI mock — intercepts generation endpoints ──────────────
// Canned response shape matches what each route's frontend expects.
// Override per-test by passing {pattern: responseFn} in overrides.
async function mockAI(page, overrides = {}) {
  if (USE_REAL_AI || overrides.real) return; // opt-out to hit real backend

  const routes = [
    { pattern: '**/api/generate-soap',                  response: { success: true, soap: 'MOCK SOAP NOTE.\nSubjective: ...\nObjective: ...\nAssessment: ...\nPlan: ...', model: 'mock-gpt' } },
    { pattern: '**/api/generate-hpi-encounter',         response: { success: true, hpi: 'MOCK HPI from encounter.', model: 'mock-gpt' } },
    { pattern: '**/api/generate-hpi-dictation',         response: { success: true, hpi: 'MOCK HPI from dictation.', model: 'mock-gpt' } },
    { pattern: '**/api/sick-visit/note',                response: { success: true, note: 'MOCK sick visit note.', model: 'mock-gpt' } },
    { pattern: '**/api/well-visit/note',                response: { success: true, note: 'MOCK well visit note.', model: 'mock-gpt' } },
    { pattern: '**/api/generate-hospital-course',       response: { success: true, hospitalCourse: 'MOCK hospital course narrative.', format: 'auto', model: 'mock-gpt' } },
    { pattern: '**/api/generate-milestone-narrative',   response: { success: true, narrative: 'MOCK developmental narrative.', model: 'mock-gpt', summary: { achieved: 3, notAchieved: 0, notAssessed: 0 } } },
    { pattern: '**/api/generate-milestone-summary',     response: { success: true, summary: 'MOCK 3-sentence summary.', model: 'mock-gpt' } },
    { pattern: '**/api/generate-pe-narrative',          response: { success: true, narrative: 'Technique:\nMOCK technique.\n\nFindings:\nMOCK findings.', model: 'mock-gpt', summary: { normal: 2, abnormal: 0, notAssessed: 0 } } },
    { pattern: '**/api/generate-chart-review',          response: { success: true, review: 'MOCK chart review.', model: 'mock-gpt' } },
    { pattern: '**/api/well-visit/shadess',             response: { success: true, assessment: 'MOCK SSHADESS assessment.', model: 'mock-gpt' } },
    { pattern: '**/api/refine',                         response: { success: true, refined: 'MOCK refined content.', model: 'mock-gpt' } },
    { pattern: '**/api/suggest-billing-codes',          response: { success: true, icd10: [], cpt: [], model: 'mock-gpt' } },
    { pattern: '**/api/transcribe',                     response: { success: true, transcript: 'MOCK transcribed text.' } },
    { pattern: '**/api/tts',                            response: { success: true, audioBase64: '' } },
  ];

  for (const { pattern, response } of routes) {
    const override = overrides[pattern];
    await page.route(pattern, async route => {
      const resp = typeof override === 'function' ? await override(route.request()) : (override || response);
      await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(resp) });
    });
  }
}

// ── Error guards — auto-applied via extended test ──────────
// Any uncaught page JS error or unhandled console.error fails the test.
// This is the safety net for bugs like the SSO ReferenceError.
const test = base.test.extend({
  // Replace the default `page` with one that has listeners wired before
  // any navigation happens.
  page: async ({ page }, use) => {
    const errors = [];
    const consoleErrors = [];

    page.on('pageerror', err => {
      // Same allowlist applies to pageerror — third-party scripts (Turnstile)
      // can throw uncaught errors that are expected on the e2e host.
      const msg = err && (err.message || String(err));
      if (isAllowedConsoleNoise(msg)) return;
      errors.push(err);
    });
    page.on('console', msg => {
      if (msg.type() !== 'error') return;
      const text = msg.text();
      if (isAllowedConsoleNoise(text)) return;
      consoleErrors.push(text);
    });

    await use(page);

    // After the test finishes, fail if any uncaught errors accumulated.
    if (errors.length > 0) {
      throw new Error(
        'Uncaught page error(s) during test:\n' +
        errors.map(e => '  - ' + e.message + '\n    ' + (e.stack || '').split('\n').slice(0, 3).join('\n    ')).join('\n')
      );
    }
    if (consoleErrors.length > 0) {
      throw new Error(
        'console.error() during test:\n' +
        consoleErrors.map(t => '  - ' + t).join('\n')
      );
    }
  },

  // Pre-authed page — login before use.
  authedPage: async ({ page, context, request }, use) => {
    await loginAs(context, request);
    await use(page);
  },
});

const expect = base.expect;

module.exports = {
  test,
  expect,
  E2E_BASE,
  TEST_EMAIL,
  TEST_PASSWORD,
  loginAs,
  getAuthToken,
  mockAI,
  USE_REAL_AI,
};
