import { Page, Request, Response, expect } from '@playwright/test';
import { RoleAuthHelper, RoleName } from './helpers';

export const E2E_HOSPITAL_NAME = 'E2E Test Hospital';
export const E2E_PASSWORD = process.env.E2E_PASSWORD || 'Dev@123456';
export const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8000';

export type ObsStatus = 'PASS' | 'FAIL' | 'WARN' | 'INFO' | 'SKIP';
export interface Observation {
  module: string;
  step: string;
  role?: string;
  status: ObsStatus;
  detail: string;
  url?: string;
  http?: number;
  ts: string;
}

export const OBS: Observation[] = [];

export function observe(
  module: string,
  step: string,
  status: ObsStatus,
  detail: string,
  opts: { role?: string; url?: string; http?: number } = {}
) {
  const o: Observation = {
    module,
    step,
    status,
    detail,
    role: opts.role,
    url: opts.url,
    http: opts.http,
    ts: new Date().toISOString(),
  };
  OBS.push(o);
  const tag = `[${status}] ${module}/${step}${opts.role ? ` (${opts.role})` : ''}: ${detail}`;
  if (status === 'FAIL') console.log('\x1b[31m' + tag + '\x1b[0m');
  else if (status === 'WARN') console.log('\x1b[33m' + tag + '\x1b[0m');
  else if (status === 'PASS') console.log('\x1b[32m' + tag + '\x1b[0m');
  else console.log(tag);
}

/**
 * Attach console / pageerror / response observers to a page.
 * Captures JS errors, console errors, and HTTP 4xx/5xx + Django tracebacks.
 */
export function attachObservers(page: Page, module: string, role?: string) {
  // auto-dismiss any unexpected JS dialog so it can't block the run
  page.on('dialog', (d) => { observe(module, 'dialog', 'WARN', `${d.type()}: ${d.message().slice(0, 120)}`, { role }); d.dismiss().catch(() => {}); });
  page.on('console', (msg) => {
    if (msg.type() === 'error') {
      observe(module, 'console-error', 'WARN', msg.text(), { role });
    }
  });
  page.on('pageerror', (err) => {
    observe(module, 'page-error', 'FAIL', `${err.name}: ${err.message}`, { role });
  });
  page.on('requestfailed', (req: Request) => {
    observe(module, 'request-failed', 'WARN', `${req.method()} ${req.url()} - ${req.failure()?.errorText}`, { role });
  });
  page.on('response', async (resp: Response) => {
    const status = resp.status();
    const url = resp.url();
    if (status >= 400) {
      let bodySnippet = '';
      try {
        const ct = resp.headers()['content-type'] || '';
        if (ct.includes('text') || ct.includes('html') || ct.includes('json')) {
          const body = await resp.text();
          const tbMatch = body.match(/(?:Traceback[\s\S]{0,400}|Server Error \(500\)|OperationalError|DoesNotExist|TemplateSyntaxError)/);
          bodySnippet = tbMatch ? ` | ${tbMatch[0].slice(0, 200).replace(/\s+/g, ' ')}` : body.slice(0, 160).replace(/\s+/g, ' ');
        }
      } catch {
        /* ignore */
      }
      observe(module, 'http-error', status >= 500 ? 'FAIL' : 'WARN', `${resp.request().method()} ${status} ${url}${bodySnippet}`, {
        role,
        url,
        http: status,
      });
    }
  });
}

/** Detect a Django error/traceback page in the current body. */
export async function bodyHasTraceback(page: Page): Promise<string | null> {
  const text = await page.textContent('body').catch(() => '');
  if (!text) return null;
  const patterns = [
    /Server Error \(500\)/,
    /Traceback \(most recent call last\)/,
    /Exception Type:[\s\S]{0,80}/,
    /DoesNotExist/,
    /OperationalError/,
    /TemplateSyntaxError/,
    /Page not found \(404\)/,
  ];
  for (const p of patterns) {
    const m = text.match(p);
    if (m) return m[0].slice(0, 120).replace(/\s+/g, ' ');
  }
  return null;
}

/**
 * Login as a role. If role is px_admin, also select E2E hospital via the
 * hospital switcher (so all scoped views target E2E Test Hospital).
 */
export async function loginAndScope(page: Page, role: RoleName, module: string) {
  const auth = new RoleAuthHelper(page);
  await auth.login(role);
  if (role === 'px_admin') {
    // px_admin users may land on dashboard or select-hospital; switch to E2E
    await page.goto('/core/select-hospital/').catch(() => {});
    await page.waitForLoadState('domcontentloaded').catch(() => {});
    const e2eLink = page.locator(`a:has-text("${E2E_HOSPITAL_NAME}"), a[href*="select-hospital"] >> text="${E2E_HOSPITAL_NAME}"`).first();
    if (await e2eLink.count().then((c) => c > 0)) {
      await e2eLink.click().catch(() => {});
      await page.waitForLoadState('domcontentloaded').catch(() => {});
    } else {
      // fallback: search any link containing the hospital name
      const any = page.locator(`text="${E2E_HOSPITAL_NAME}"`).first();
      if (await any.count().then((c) => c > 0)) await any.click().catch(() => {});
    }
    observe(module, 'login-scope', 'INFO', `px_admin scoped to ${E2E_HOSPITAL_NAME}`, { role });
  }
  return auth;
}

/** Resolve E2E hospital UUID from the public hospitals API. */
export async function getE2EHospitalId(page: Page): Promise<string> {
  const resp = await page.context().request.get(`${BASE_URL}/core/api/hospitals/`, { timeout: 10000 });
  const data = await resp.json();
  const h = (data.hospitals || []).find((x: { name: string }) => x.name === E2E_HOSPITAL_NAME);
  return h ? h.id : '';
}

/**
 * Select the E2E hospital in a <select>. Tries by visible label first, then by
 * UUID value, then by index (last resort). Avoids polluting real hospitals.
 */
export async function selectE2EHospital(page: Page, selector: string): Promise<boolean> {
  const sel = page.locator(selector).first();
  if (!(await sel.count())) return false;
  const id = await getE2EHospitalId(page);
  try { await sel.selectOption({ label: E2E_HOSPITAL_NAME }); return true; } catch { /* try next */ }
  if (id) { try { await sel.selectOption({ value: id }); return true; } catch { /* try next */ } }
  try { await sel.selectOption({ index: 1 }); return true; } catch { /* give up */ }
  return false;
}

export { expect };
