import puppeteer, { HTTPResponse } from 'puppeteer';
import axios from 'axios';
import * as cheerio from 'cheerio';

const targetUrl =
  process.argv.find((arg) => !arg.startsWith('--') && arg.startsWith('http')) ||
  process.env.NEXT_PUBLIC_BASE_URL ||
  'http://localhost:3000';
const limit = process.env.ASSET_CHECK_LIMIT ? parseInt(process.env.ASSET_CHECK_LIMIT) : 20;
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'mbgrid';

async function main() {
  console.log(`\n🚀 Starting Strict Asset Integrity Check for: ${targetUrl}`);
  console.log(`📊 Limit: ${limit} pages\n`);

  // 1. Fetch Sitemap to discover all routes
  const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`;
  let urls: string[] = [];

  try {
    console.log(`📥 Fetching sitemap from ${sitemapUrl}...`);
    const response = await axios.get(sitemapUrl, {
      headers: { Cookie: `mb_gatekeeper_session=${gatekeeperPassword}` },
    });

    const $ = cheerio.load(response.data, { xmlMode: true });
    urls = $('url loc')
      .map((i, el) => $(el).text())
      .get();

    // Normalize to target URL instance
    const urlPattern = /https?:\/\/[^\/]+/;
    urls = [...new Set(urls)]
      .filter((u) => u.startsWith('http'))
      .map((u) => u.replace(urlPattern, targetUrl.replace(/\/$/, '')))
      .sort();

    console.log(`✅ Found ${urls.length} target URLs.`);

    if (urls.length > limit) {
      console.log(
        `⚠️  Too many pages (${urls.length}). Limiting to ${limit} representative pages.`,
      );
      // Simplify selection: home pages + a slice of the rest
      const homeDE = urls.filter((u) => u.endsWith('/de') || u === targetUrl);
      const homeEN = urls.filter((u) => u.endsWith('/en'));
      const others = urls.filter((u) => !homeEN.includes(u) && !homeDE.includes(u));
      urls = [...homeDE, ...homeEN, ...others.slice(0, limit - (homeEN.length + homeDE.length))];
    }
  } catch (err: unknown) {
    const errorBody = err instanceof Error ? err.message : String(err);
    console.error(`❌ Failed to fetch sitemap: ${errorBody}`);
    process.exit(1);
  }

  // 2. Launch Headless Browser
  console.log(`\n🕷️ Launching Puppeteer Headless Engine...`);
  const browser = await puppeteer.launch({
    headless: true,
    executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_PATH || undefined,
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
  });

  const page = await browser.newPage();

  // Inject Gatekeeper session bypassing auth screens
  await page.setCookie({
    name: 'mb_gatekeeper_session',
    value: gatekeeperPassword,
    domain: new URL(targetUrl).hostname,
    path: '/',
    httpOnly: true,
    secure: targetUrl.startsWith('https://'),
  });

  let hasBrokenAssets = false;
  let hasConsoleErrors = false;
  const brokenAssetsList: Array<{ url: string; status: number; page: string }> = [];
  const consoleErrorsList: Array<{ type: string; error: string; page: string }> = [];

  // Listen for unhandled exceptions natively in the page
  page.on('pageerror', (err: unknown) => {
    const errorMessage = err instanceof Error ? err.message : String(err);
    consoleErrorsList.push({
      type: 'PAGE_ERROR',
      error: errorMessage,
      page: page.url(),
    });
    hasConsoleErrors = true;
  });

  // Listen for console.error and console.warn messages (like Next.js Image warnings, hydration errors, CSP blocks)
  page.on('console', (msg) => {
    const type = msg.type();
    if (type === 'error' || type === 'warn') {
      const text = msg.text();

      // Exclude common browser extension noise or third party tracker warnings
      if (
        text.includes('google-analytics') ||
        text.includes('googletagmanager') ||
        text.includes('SES Removing unpermitted intrinsics') ||
        text.includes('Third-party cookie will be blocked') ||
        text.includes('Fast Refresh')
      )
        return;

      consoleErrorsList.push({
        type: type.toUpperCase(),
        error: text,
        page: page.url(),
      });
      hasConsoleErrors = true;
    }
  });

  // Listen to ALL network responses
  page.on('response', (response: HTTPResponse) => {
    const status = response.status();
    // Catch classic 404s and 500s on ANY fetch/image/script
    if (
      status >= 400 &&
      status !== 999 &&
      !response.url().includes('google-analytics') &&
      !response.url().includes('googletagmanager')
    ) {
      const type = response.request().resourceType();

      // We explicitly care about images, stylesheets, scripts, and fetch requests (API) getting 404/500s.
      if (['image', 'script', 'stylesheet', 'fetch', 'xhr'].includes(type)) {
        brokenAssetsList.push({
          url: response.url(),
          status: status,
          page: page.url(),
        });
        hasBrokenAssets = true;
      }
    }
  });

  // 3. Scan each page
  for (let i = 0; i < urls.length; i++) {
    const u = urls[i];
    console.log(`[${i + 1}/${urls.length}] Scanning: ${u}`);
    try {
      // Wait until network is idle to ensure all Next.js hydration and image lazy-loads trigger
      await page.goto(u, { waitUntil: 'networkidle0', timeout: 30000 });

      // Force scroll to bottom to trigger any IntersectionObserver lazy-loaded images
      await page.evaluate(async () => {
        await new Promise<void>((resolve) => {
          let totalHeight = 0;
          const distance = 100;
          const timer = setInterval(() => {
            const scrollHeight = document.body.scrollHeight;
            window.scrollBy(0, distance);
            totalHeight += distance;
            if (totalHeight >= scrollHeight) {
              clearInterval(timer);
              resolve();
            }
          }, 100);
        });
      });

      // Wait a tiny bit more for final lazy loads
      await new Promise((r) => setTimeout(r, 1000));
    } catch (err: unknown) {
      const errorBody = err instanceof Error ? err.message : String(err);
      console.error(`⚠️ Timeout or navigation error on ${u}: ${errorBody}`);
      // Don't fail the whole script just because one page timed out, but flag it
      hasBrokenAssets = true;
    }
  }

  await browser.close();

  // 4. Report Results
  if (hasBrokenAssets && brokenAssetsList.length > 0) {
    console.error(`\n❌ FATAL: Broken assets (404/500) detected heavily on the site!`);
    console.table(brokenAssetsList);
  }

  if (hasConsoleErrors && consoleErrorsList.length > 0) {
    console.error(`\n❌ FATAL: Console Errors/Warnings detected on the site!`);
    console.table(consoleErrorsList);
  }

  if (hasBrokenAssets || hasConsoleErrors) {
    console.error(`\n🚨 The CI build will now fail to prevent bad code from reaching production.`);
    process.exit(1);
  } else {
    console.log(
      `\n🎉 SUCCESS: All ${urls.length} pages rendered perfectly with 0 broken images or console errors!`,
    );
    process.exit(0);
  }
}

main();
