import { test, expect, request as apiRequest, type APIRequestContext } from '@playwright/test';
import { Client } from 'pg';
import fs from 'node:fs/promises';
import path from 'node:path';
import { API_URL, PORTALS, USERS } from '../../fixtures/users';

interface AuthSession {
  accessToken: string;
  refreshToken: string;
  userId: string;
}

interface SiteConfig {
  siteName: string;
  tagline: string;
  footerTagline: string | null;
  ctaLoginUrl: string;
  ctaRegisterUrl: string;
}

interface PublicDoctor {
  id: string;
  fullName: string;
  primarySpecialty?: string | null;
}

interface AdminDoctor {
  id: string;
  firstName?: string;
  lastName?: string;
  fullName?: string;
  user?: { firstName?: string; lastName?: string };
}

type ListResponse<T> = T[] | { data?: T[]; items?: T[] };

interface PackageRow {
  slug: string;
  name: string;
  priceMinor: number;
  currency: string;
  isActive?: boolean;
}

interface FaqGroup {
  category: string;
  items: Array<{ id: string; question: string; answer: string; isPublished?: boolean }>;
}

const EVIDENCE_DIR = path.resolve(
  process.cwd(),
  '..',
  '..',
  'run-logs',
  '2026-05-26-cross-portal-qa',
  'phase-8',
);

const PUBLIC_ROUTES = [
  '/',
  '/about',
  '/doctors',
  '/specialties',
  '/packages',
  '/faq',
  '/contact',
  '/privacy',
  '/terms',
] as const;

async function login(ctx: APIRequestContext): Promise<AuthSession> {
  const res = await ctx.post(`${API_URL}/api/v2/auth/login`, {
    data: { email: USERS.admin.email, password: USERS.admin.password },
  });
  const body = await res.json();
  expect(res.ok(), `admin login should succeed: ${JSON.stringify(body)}`).toBeTruthy();
  return { accessToken: body.accessToken, refreshToken: body.refreshToken, userId: body.user.id };
}

async function logout(ctx: APIRequestContext, session: AuthSession | undefined) {
  if (!session) return;
  await ctx.post(`${API_URL}/api/v2/auth/logout`, {
    headers: auth(session),
    data: { refreshToken: session.refreshToken },
  }).catch(() => null);
}

function auth(session: AuthSession) {
  return {
    Authorization: `Bearer ${session.accessToken}`,
    'Content-Type': 'application/json',
  };
}

async function clearAdminSession() {
  const client = new Client({
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT),
    user: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE,
  });
  await client.connect();
  try {
    await client.query(
      `DELETE FROM refresh_tokens
       WHERE user_id IN (
         SELECT id FROM users WHERE email = $1
       )`,
      [USERS.admin.email],
    );
  } finally {
    await client.end();
  }
}

async function json<T>(res: Awaited<ReturnType<APIRequestContext['get']>>, label: string) {
  const body = await res.json().catch(async () => ({ raw: await res.text() }));
  expect(res.ok(), `${label}: ${JSON.stringify(body)}`).toBeTruthy();
  return body as T;
}

function doctorName(row: AdminDoctor) {
  return (
    row.fullName ??
    `${row.user?.firstName ?? row.firstName ?? ''} ${row.user?.lastName ?? row.lastName ?? ''}`.trim()
  );
}

function rows<T>(value: ListResponse<T>) {
  return Array.isArray(value) ? value : (value.data ?? value.items ?? []);
}

test.beforeEach(async () => {
  await clearAdminSession();
  await fs.mkdir(EVIDENCE_DIR, { recursive: true });
});

test.afterEach(async () => {
  await clearAdminSession();
});

test('public doctors, packages, and CMS reads match admin-managed data', async () => {
  const ctx = await apiRequest.newContext();
  let admin: AuthSession | undefined;
  let originalGlobal: SiteConfig | undefined;
  try {
    admin = await login(ctx);
    originalGlobal = await ctx
      .get(`${API_URL}/api/v2/admin/cms/global`, { headers: auth(admin) })
      .then((res) => json<SiteConfig>(res, 'admin global CMS should load'));

    const unique = `Phase 8 QA ${Date.now()}`;
    const patchedGlobal = await ctx.patch(`${API_URL}/api/v2/admin/cms/global`, {
      headers: auth(admin),
      data: {
        tagline: `${unique} tagline`,
        footerTagline: `${unique} footer`,
      },
    }).then((res) => json<SiteConfig>(res, 'admin global CMS update should save'));
    const publicGlobal = await ctx
      .get(`${API_URL}/api/v2/public/site-config`)
      .then((res) => json<SiteConfig>(res, 'public site config should load'));
    expect(publicGlobal.tagline).toBe(patchedGlobal.tagline);
    expect(publicGlobal.footerTagline).toBe(patchedGlobal.footerTagline);

    const faq = await ctx.post(`${API_URL}/api/v2/admin/cms/faqs`, {
      headers: auth(admin),
      data: {
        question: `${unique} question?`,
        answer: `${unique} answer`,
        category: 'general',
        order: 9900,
        isPublished: true,
      },
    }).then((res) => json<{ id: string; question: string }>(res, 'admin FAQ create should save'));
    const publicFaqs = await ctx
      .get(`${API_URL}/api/v2/public/faqs?category=general`)
      .then((res) => json<FaqGroup[]>(res, 'public FAQ should load'));
    expect(publicFaqs.flatMap((group) => group.items).some((item) => item.id === faq.id)).toBeTruthy();

    const [adminDoctors, publicDoctors, adminPackages, publicPackages] = await Promise.all([
      ctx.get(`${API_URL}/api/v2/admin/doctors?status=approved`, { headers: auth(admin) })
        .then((res) => json<ListResponse<AdminDoctor>>(res, 'admin approved doctors should load')),
      ctx.get(`${API_URL}/api/v2/public/doctors`)
        .then((res) => json<PublicDoctor[]>(res, 'public doctors should load')),
      ctx.get(`${API_URL}/api/v2/admin/packages`, { headers: auth(admin) })
        .then((res) => json<ListResponse<PackageRow>>(res, 'admin packages should load')),
      ctx.get(`${API_URL}/api/v2/public/packages`)
        .then((res) => json<PackageRow[]>(res, 'public packages should load')),
    ]);

    const adminDoctorRows = rows(adminDoctors);
    const adminPackageRows = rows(adminPackages);

    expect(publicDoctors.length, 'public doctors should not be empty').toBeGreaterThan(0);
    for (const publicDoctor of publicDoctors) {
      expect(
        adminDoctorRows.some((doctor) => doctorName(doctor) === publicDoctor.fullName),
        `admin approved doctors should include ${publicDoctor.fullName}`,
      ).toBeTruthy();
    }

    const activeAdminPackages = adminPackageRows.filter((pkg) => pkg.isActive);
    expect(publicPackages.length, 'public packages should not be empty').toBeGreaterThan(0);
    for (const adminPackage of activeAdminPackages) {
      const publicPackage = publicPackages.find((pkg) => pkg.slug === adminPackage.slug);
      expect(publicPackage, `public packages should include ${adminPackage.slug}`).toBeTruthy();
      expect(publicPackage).toMatchObject({
        name: adminPackage.name,
        priceMinor: adminPackage.priceMinor,
        currency: adminPackage.currency,
      });
    }
  } finally {
    if (admin && originalGlobal) {
      await ctx.patch(`${API_URL}/api/v2/admin/cms/global`, {
        headers: auth(admin),
        data: {
          tagline: originalGlobal.tagline,
          footerTagline: originalGlobal.footerTagline,
        },
      }).catch(() => null);
    }
    await logout(ctx, admin);
    await ctx.dispose();
  }
});

test('public pages have no horizontal overflow on desktop or mobile', async ({ page }) => {
  const consoleErrors: string[] = [];
  page.on('console', (msg) => {
    if (msg.type() === 'error') consoleErrors.push(msg.text());
  });

  for (const viewport of [
    { name: 'desktop', width: 1366, height: 900 },
    { name: 'mobile', width: 390, height: 844 },
  ]) {
    await page.setViewportSize(viewport);
    for (const route of ['/', '/doctors', '/packages', '/faq'] as const) {
      await page.goto(`${PORTALS.public}${route}`);
      await expect(page.getByRole('heading').first()).toBeVisible();
      const overflow = await page.evaluate(() => ({
        innerWidth: window.innerWidth,
        scrollWidth: document.documentElement.scrollWidth,
      }));
      expect(
        overflow.scrollWidth,
        `${route} should not horizontally overflow at ${viewport.name}`,
      ).toBeLessThanOrEqual(overflow.innerWidth + 2);
    }
    await page.screenshot({
      path: path.join(EVIDENCE_DIR, `responsive-${viewport.name}.png`),
      fullPage: false,
    });
  }

  await page.setViewportSize({ width: 390, height: 844 });
  await page.goto(`${PORTALS.public}/`);
  const menu = page.getByRole('button', { name: 'Toggle menu' });
  await expect(menu).toBeVisible();
  await menu.click();
  await expect(page.getByRole('link', { name: 'Contact', exact: true })).toBeVisible();
  expect(consoleErrors, `public responsive console errors: ${consoleErrors.join('\n')}`).toEqual([]);
});

test('primary public internal links resolve successfully', async ({ page, request }) => {
  const origin = new URL(PORTALS.public).origin;
  const internalLinks = new Set<string>();

  for (const route of PUBLIC_ROUTES) {
    await page.goto(`${PORTALS.public}${route}`);
    const hrefs = await page.evaluate(() =>
      Array.from(document.querySelectorAll<HTMLAnchorElement>('a[href]'))
        .map((a) => a.href)
        .filter(Boolean),
    );
    for (const href of hrefs) {
      const url = new URL(href);
      if (url.origin !== origin) continue;
      if (url.protocol !== 'http:' && url.protocol !== 'https:') continue;
      url.hash = '';
      internalLinks.add(url.toString());
    }
  }

  expect(internalLinks.size, 'internal public links should be discovered').toBeGreaterThan(0);
  for (const link of Array.from(internalLinks).sort()) {
    const res = await request.get(link);
    expect(res.status(), `${link} should not be broken`).toBeLessThan(400);
  }
});
