/**
 * Route Health Check Suite
 *
 * Visits every publicly accessible URL in the application and verifies:
 *   - HTTP status is not 4xx / 5xx  (or the expected status for auth-guarded pages)
 *   - No Laravel error page is rendered ("Whoops", "Server Error", stack traces)
 *   - No uncaught JavaScript exceptions (window.onerror / unhandledrejection)
 *   - No browser console errors
 *   - Page has a non-empty <title> and a visible <body>
 *
 * Auth-guarded routes are tested to confirm they redirect cleanly to /login
 * rather than throwing an error.
 *
 * Run:
 *   npx playwright test tests/e2e/routes.spec.ts
 *   npx playwright test tests/e2e/routes.spec.ts --reporter=html
 */

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

// ─────────────────────────────────────────────────────────────────────────────
// Route registry
// ─────────────────────────────────────────────────────────────────────────────

interface RouteFixture {
  /** URL path (relative to baseURL) */
  url: string;
  /** Human-readable label shown in test output */
  label: string;
  /**
   * When set, assert page.url() contains this string after navigation.
   * When absent, no URL assertion is made (redirects are tolerated by default).
   */
  expectUrlContains?: string;
  /** When true, expect the browser to land on the login page */
  requiresAuth?: boolean;
  /** Skip entirely — use for routes that need real DB fixtures not guaranteed in CI */
  skip?: boolean;
  /** Additional text that MUST be present somewhere on the page */
  bodyContains?: string;
}

// Public routes — must return 200 with no errors
const PUBLIC_ROUTES: RouteFixture[] = [
  // ── Core ──────────────────────────────────────────────────────────────────
  { url: '/',                    label: 'Home page' },
  { url: '/home',                label: 'Home (alias)' },
  { url: '/blank',               label: 'Blank template' },

  // ── Browse / gallery ──────────────────────────────────────────────────────
  { url: '/browse',              label: 'Browse' },
  { url: '/browse-categories',   label: 'Browse Categories' },
  { url: '/categories',          label: 'Categories' },
  { url: '/sections',            label: 'Sections' },
  { url: '/featured',            label: 'Featured artworks' },
  { url: '/featured-artworks',   label: 'Featured artworks (alias)' },

  // ── Uploads ──────────────────────────────────────────────────────────────
  { url: '/uploads/latest',      label: 'Latest uploads (new)' },
  { url: '/uploads/daily',       label: 'Daily uploads (new)' },
  { url: '/daily-uploads',       label: 'Daily uploads (legacy)' },
  { url: '/latest',              label: 'Latest (legacy)' },

  // ── Community ────────────────────────────────────────────────────────────
  { url: '/members/photos',      label: 'Member photos (new)' },
  { url: '/authors/top',         label: 'Top authors (new)' },
  { url: '/comments/latest',     label: 'Latest comments (new)' },
  { url: '/comments/monthly',    label: 'Monthly commentators (new)' },
  { url: '/downloads/today',     label: 'Today downloads (new)' },
  { url: '/top-authors',         label: 'Top authors (legacy)',     expectUrlContains: '/creators/top' },
  { url: '/top-favourites',      label: 'Top favourites (legacy)', expectUrlContains: '/discover/top-rated' },
  { url: '/today-downloads',     label: 'Today downloads (legacy)' },
  { url: '/today-in-history',    label: 'Today in history', expectUrlContains: '/discover/on-this-day' },
  { url: '/monthly-commentators',label: 'Monthly commentators (legacy)' },
  { url: '/latest-comments',     label: 'Latest comments (legacy)' },
  { url: '/interviews',          label: 'Interviews',               expectUrlContains: '/stories' },
  { url: '/chat',                label: 'Chat' },

  // ── Forum ────────────────────────────────────────────────────────────────
  { url: '/forum',               label: 'Forum index' },

  // ── Content type roots ───────────────────────────────────────────────────
  { url: '/photography',         label: 'Photography root' },
  { url: '/wallpapers',          label: 'Wallpapers root' },
  { url: '/skins',               label: 'Skins root' },

  // ── Discover ──────────────────────────────────────────────────────────────
  { url: '/discover/trending',        label: 'Discover: Trending' },
  { url: '/discover/fresh',           label: 'Discover: Fresh' },
  { url: '/discover/top-rated',       label: 'Discover: Top Rated' },
  { url: '/discover/most-downloaded', label: 'Discover: Most Downloaded' },
  { url: '/discover/on-this-day',     label: 'Discover: On This Day' },

  // ── Creators ──────────────────────────────────────────────────────────────
  { url: '/creators/top',             label: 'Creators: Top' },
  { url: '/creators/rising',          label: 'Creators: Rising' },
  { url: '/stories',                  label: 'Creator Stories' },

  // ── Tags ──────────────────────────────────────────────────────────────────
  { url: '/tags',                     label: 'Tags index' },

  // ── Auth pages (guest-only, publicly accessible) ─────────────────────────
  { url: '/login',               label: 'Login page' },
  { url: '/register',            label: 'Register page' },
  { url: '/forgot-password',     label: 'Forgot password' },
];

// Auth-guarded routes — unauthenticated visitors must land on /login
const AUTH_ROUTES: RouteFixture[] = [
  { url: '/dashboard',           label: 'Dashboard', requiresAuth: true },
  { url: '/dashboard/profile',   label: 'Dashboard profile', requiresAuth: true },
  { url: '/studio/artworks',     label: 'Studio artworks', requiresAuth: true },
  { url: '/dashboard/gallery',   label: 'Dashboard gallery', requiresAuth: true },
  { url: '/dashboard/favorites', label: 'Dashboard favorites', requiresAuth: true },
  { url: '/upload',              label: 'Upload page', requiresAuth: true },
  { url: '/statistics',          label: 'Statistics', requiresAuth: true },
  { url: '/recieved-comments',   label: 'Received comments', requiresAuth: true },
  { url: '/mybuddies',           label: 'My buddies', requiresAuth: true },
  { url: '/buddies',             label: 'Buddies', requiresAuth: true },
  { url: '/manage',              label: 'Manage', requiresAuth: true },
  { url: '/dashboard/awards',    label: 'Dashboard awards', requiresAuth: true },
];

// Routes that should 404 (to ensure 404 handling is clean and doesn't 500)
const NOT_FOUND_ROUTES: RouteFixture[] = [
  { url: '/this-page-does-not-exist-xyz-9999', label: '404 — unknown path' },
  { url: '/art/999999999/no-such-artwork',      label: '404 — unknown artwork' },
];

// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────

/** Console message origins we choose to tolerate (CSP reports, hot-reload, etc.) */
const IGNORED_CONSOLE_PATTERNS: RegExp[] = [
  /\[vite\]/i,
  /\[HMR\]/i,
  /favicon\.ico/i,
  /Failed to load resource.*hot/i,
  /content security policy/i,
  /sourcemappingurl/i,
  // Missing image/asset files in dev environment (thumbnails not present locally)
  /Failed to load resource: the server responded with a status of 404/i,
];

/**
 * Text fragments whose presence in page HTML indicates Laravel rendered
 * an error page (debug mode or a production error view).
 */
const ERROR_PAGE_SIGNALS: string[] = [
  'Whoops!',
  'Server Error',
  'Symfony\\Component\\',
  'ErrorException',
  'QueryException',
  'ParseError',
];

interface PageProbe {
  jsErrors: string[];
  consoleErrors: string[];
}

/** Wire up collectors for JS errors before navigating. */
function attachProbes(page: Page): PageProbe {
  const probe: PageProbe = { jsErrors: [], consoleErrors: [] };

  page.on('pageerror', (err) => {
    probe.jsErrors.push(err.message);
  });

  page.on('console', (msg) => {
    if (msg.type() !== 'error') return;
    const text = msg.text();
    if (IGNORED_CONSOLE_PATTERNS.some((re) => re.test(text))) return;
    probe.consoleErrors.push(text);
  });

  return probe;
}

/** Assert the probe found no problems. */
function expectCleanProbe(probe: PageProbe) {
  expect(
    probe.jsErrors,
    `Uncaught JS exceptions: ${probe.jsErrors.join(' | ')}`
  ).toHaveLength(0);

  expect(
    probe.consoleErrors,
    `Browser console errors: ${probe.consoleErrors.join(' | ')}`
  ).toHaveLength(0);
}

/** Check the rendered HTML for Laravel / server-side error signals. */
async function expectNoErrorPage(page: Page) {
  const html = await page.content();
  for (const signal of ERROR_PAGE_SIGNALS) {
    expect(
      html,
      `Error page signal found in HTML: "${signal}"`
    ).not.toContain(signal);
  }
}

/** Check the page has a <title> and visible <body>. */
async function expectMeaningfulPage(page: Page) {
  const title = await page.title();
  expect(title.trim(), 'Page <title> must not be empty').not.toBe('');

  await expect(page.locator('body'), '<body> must be visible').toBeVisible();
}

// ─────────────────────────────────────────────────────────────────────────────
// Test suites
// ─────────────────────────────────────────────────────────────────────────────

// ── 1. Public routes ──────────────────────────────────────────────────────────
test.describe('Public routes — 200, no errors', () => {
  for (const route of PUBLIC_ROUTES) {
    const testFn = route.skip ? test.skip : test;

    testFn(route.label, async ({ page }) => {
      const probe = attachProbes(page);

      const response = await page.goto(route.url, { waitUntil: 'domcontentloaded' });

      // ── Status check ──────────────────────────────────────────────────────
      const status = response?.status() ?? 0;
      expect(
        status,
        `${route.url} returned HTTP ${status} — expected 200`
      ).toBe(200);

      // ── Optional URL assertion ─────────────────────────────────────────────
      if (route.expectUrlContains) {
        expect(page.url()).toContain(route.expectUrlContains);
      }

      // ── Page content checks ───────────────────────────────────────────────
      await expectNoErrorPage(page);
      await expectMeaningfulPage(page);

      if (route.bodyContains) {
        await expect(page.locator('body')).toContainText(route.bodyContains);
      }

      // ── JS probe results ──────────────────────────────────────────────────
      expectCleanProbe(probe);
    });
  }
});

// ── 2. Auth-guarded routes ────────────────────────────────────────────────────
test.describe('Auth-guarded routes — redirect to /login cleanly', () => {
  for (const route of AUTH_ROUTES) {
    test(route.label, async ({ page }) => {
      const probe = attachProbes(page);

      // Follow redirects; we expect to land on the login page
      const response = await page.goto(route.url, { waitUntil: 'domcontentloaded' });

      // Final page must not be an error page
      const status = response?.status() ?? 0;
      expect(
        status,
        `${route.url} auth redirect resulted in HTTP ${status} — expected 200 (login page)`
      ).toBe(200);

      // Must have redirected to login
      expect(
        page.url(),
        `${route.url} did not redirect to /login`
      ).toContain('/login');

      await expectNoErrorPage(page);
      await expectMeaningfulPage(page);
      expectCleanProbe(probe);
    });
  }
});

// ── 3. 404 handling ───────────────────────────────────────────────────────────
test.describe('404 routes — clean error page, not a 500', () => {
  for (const route of NOT_FOUND_ROUTES) {
    test(route.label, async ({ page }) => {
      const probe = attachProbes(page);

      const response = await page.goto(route.url, { waitUntil: 'domcontentloaded' });
      const status = response?.status() ?? 0;

      // Must be 404 — never 500
      expect(
        status,
        `${route.url} returned HTTP ${status} — expected 404, not 500`
      ).toBe(404);

      // 404 pages are fine, but they must not be a 500 crash
      const html = await page.content();
      const crashSignals = [
        'Whoops!',
        'Symfony\\Component\\',
        'ErrorException',
        'QueryException',
      ];
      for (const signal of crashSignals) {
        expect(
          html,
          `500-level signal "${signal}" found on a ${status} page`
        ).not.toContain(signal);
      }

      expectCleanProbe(probe);
    });
  }
});

// ── 4. Spot-check: critical pages render expected landmarks ──────────────────
test.describe('Landmark spot-checks', () => {
  test('Home page — has gallery section', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/', { waitUntil: 'domcontentloaded' });
    await expect(page.locator('#homepage-root section').first()).toBeVisible();
    expectCleanProbe(probe);
  });

  test('Sections page — renders content-type section headings', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/sections', { waitUntil: 'domcontentloaded' });
    // Should have at least one section anchor
    const sections = page.locator('[id^="section-"]');
    await expect(sections.first()).toBeVisible();
    expectCleanProbe(probe);
  });

  test('Browse-categories page — renders category links', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/browse-categories', { waitUntil: 'domcontentloaded' });
    await expect(page.locator('a[href]').first()).toBeVisible();
    expectCleanProbe(probe);
  });

  test('Forum — renders forum index', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/forum', { waitUntil: 'domcontentloaded' });
    // Forum has either category rows or a "no threads" message
    await expect(page.locator('body')).toBeVisible();
    await expectNoErrorPage(page);
    expectCleanProbe(probe);
  });

  test('Authors top — renders leaderboard', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/authors/top', { waitUntil: 'domcontentloaded' });
    await expectNoErrorPage(page);
    await expectMeaningfulPage(page);
    expectCleanProbe(probe);
  });

  test('Daily uploads — date strip renders', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/uploads/daily', { waitUntil: 'domcontentloaded' });
    await expectNoErrorPage(page);
    // Date strip should have multiple buttons
    const strip = page.locator('#dateStrip button');
    await expect(strip.first()).toBeVisible();
    const count = await strip.count();
    expect(count, 'Daily uploads date strip should have 15 tabs').toBe(15);
    expectCleanProbe(probe);
  });

  test('Daily uploads — AJAX endpoint returns HTML fragment', async ({ page }) => {
    const probe = attachProbes(page);
    const today = new Date().toISOString().split('T')[0];

    const response = await page.goto(`/uploads/daily?ajax=1&datum=${today}`, {
      waitUntil: 'domcontentloaded',
    });

    expect(
      response?.status(),
      'Daily uploads AJAX endpoint should return 200'
    ).toBe(200);

    // Response should not be an error page
    const html = await page.content();
    expect(html).not.toContain('Whoops!');
    expect(html).not.toContain('Server Error');

    expectCleanProbe(probe);
  });

  test('Login page loads and has form', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/login', { waitUntil: 'domcontentloaded' });
    await expect(page.locator('form[method="POST"]')).toBeVisible();
    await expect(page.locator('input[type="email"], input[name="email"]')).toBeVisible();
    await expect(page.locator('input[type="password"]')).toBeVisible();
    expectCleanProbe(probe);
  });

  test('Register page loads and has form', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/register', { waitUntil: 'domcontentloaded' });
    await expect(page.locator('form[method="POST"]')).toBeVisible();
    expectCleanProbe(probe);
  });

  test('Photography root loads', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/photography', { waitUntil: 'domcontentloaded' });
    await expectNoErrorPage(page);
    await expectMeaningfulPage(page);
    expectCleanProbe(probe);
  });

  test('Wallpapers root loads', async ({ page }) => {
    const probe = attachProbes(page);
    await page.goto('/wallpapers', { waitUntil: 'domcontentloaded' });
    await expectNoErrorPage(page);
    await expectMeaningfulPage(page);
    expectCleanProbe(probe);
  });
});

// ── 5. Navigation performance — no route should hang ─────────────────────────
test.describe('Response time — no page should take over 8 s', () => {
  const SLOW_THRESHOLD_MS = 8000;
  const PERF_ROUTES = [
    '/', '/uploads/latest', '/comments/latest', '/authors/top',
    '/sections', '/forum', '/browse-categories',
  ];

  for (const url of PERF_ROUTES) {
    test(`${url} responds within ${SLOW_THRESHOLD_MS}ms`, async ({ page }) => {
      const start = Date.now();
      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: SLOW_THRESHOLD_MS + 2000 });
      const elapsed = Date.now() - start;
      expect(
        elapsed,
        `${url} took ${elapsed}ms — over the ${SLOW_THRESHOLD_MS}ms threshold`
      ).toBeLessThan(SLOW_THRESHOLD_MS);
    });
  }
});
