// ============================================================================
// Baseline QA E2E · smoke + brand · endpoints Sprint 1
// ----------------------------------------------------------------------------
// Livrable Sprint 1 (roadmap §Sprint 1 · GAP_ANALYSIS §7 · QA Agent).
// Data-driven sur tests/e2e/routes.json → /waf-home /crm /qa /choisir-mon-unite.
//
// Philosophie « baseline » : on vérifie la SANTÉ et le BRAND, jamais le contenu
// métier (prix/superficie/dispo) — contrainte #6 (zéro invention de chiffres).
// Exécution réelle : sur le runner Gitea VPS avec DTP_BASE_URL défini
// (voir tests/README.md). Autorable/validable en-repo, run différé côté VPS.
// ============================================================================
import { test, expect } from '@playwright/test';
import routesManifest from './routes.json';
import {
  LUXURY,
  isReachable,
  looksLikeLoginGate,
  collectStyleSurface,
  containsAny,
  type RoutesManifest,
} from './_shared/contract';

const { routes } = routesManifest as RoutesManifest;

for (const route of routes) {
  test.describe(`${route.path} · ${route.name}`, () => {
    test('répond sans erreur serveur (status < 400)', async ({ page }) => {
      const res = await page.goto(route.path, { waitUntil: 'domcontentloaded' });
      expect(res, `aucune réponse HTTP pour ${route.path}`).not.toBeNull();
      expect(
        isReachable(res),
        `${route.path} → status ${res?.status()} (attendu < 400)`,
      ).toBeTruthy();
    });

    test('sert une page HTML titrée avec attribut lang', async ({ page }) => {
      await page.goto(route.path, { waitUntil: 'domcontentloaded' });

      const title = (await page.title()).trim();
      expect(title.length, `titre vide sur ${route.path}`).toBeGreaterThan(0);

      const lang = await page.locator('html').getAttribute('lang');
      // Multilingue FR/EN/ES attendu — on vérifie la présence de l'attribut,
      // pas une valeur précise (routes servies dans plusieurs langues).
      expect(lang, `attribut <html lang> absent sur ${route.path}`).toBeTruthy();
    });

    test('respecte le brand luxury dark+doré (tokens présents)', async ({ page }) => {
      const res = await page.goto(route.path, { waitUntil: 'networkidle' });
      const bodyText = await page.locator('body').innerText().catch(() => '');

      // Route protégée non authentifiée : le brand du mur de login n'est pas
      // représentatif → on annote et on ne bloque pas (baseline tolérante).
      if (route.gated && looksLikeLoginGate(page.url(), bodyText)) {
        test.info().annotations.push({
          type: 'gate',
          description: `${route.path} sert un login (non authentifié) — brand non asserté.`,
        });
        test.skip(true, 'Mur de login — brand vérifié une fois authentifié (couverture ultérieure).');
      }
      expect(isReachable(res)).toBeTruthy();

      const surface = await collectStyleSurface(page);
      const hasColor = containsAny(surface, LUXURY.colors);
      const hasFont = containsAny(surface, LUXURY.fonts);

      expect(
        hasColor,
        `aucune couleur de marque (${LUXURY.colors.join(' / ')}) trouvée sur ${route.path}`,
      ).toBeTruthy();
      expect(
        hasFont,
        `aucune police de marque (${LUXURY.fonts.join(' / ')}) trouvée sur ${route.path}`,
      ).toBeTruthy();
    });

    test('ne produit ni erreur JS non capturée ni réponse 5xx', async ({ page }) => {
      const pageErrors: string[] = [];
      const serverErrors: string[] = [];

      page.on('pageerror', (err) => pageErrors.push(err.message));
      page.on('response', (r) => {
        if (r.status() >= 500) serverErrors.push(`${r.status()} ${r.url()}`);
      });

      await page.goto(route.path, { waitUntil: 'networkidle' });

      expect(
        pageErrors,
        `erreurs JS non capturées sur ${route.path}:\n${pageErrors.join('\n')}`,
      ).toEqual([]);
      expect(
        serverErrors,
        `réponses 5xx sur ${route.path}:\n${serverErrors.join('\n')}`,
      ).toEqual([]);
    });
  });
}
