#!/usr/bin/env node
/**
 * GigLez UI verification — drives a real headless browser through the full
 * user journey against the deployed SPA, asserting the things the API layer
 * cannot: the page loads clean, the Leaflet map renders, and an upload done
 * *through the actual DOM* returns a device match and increments the store.
 *
 * Usage:
 *   BASE=http://localhost:8000 node scripts/verify_ui.mjs
 *
 * Requires Node Playwright (present: 1.58.x). Exits non-zero on any failure.
 * Screenshots are written to ./verify-shots/.
 */
import { chromium } from 'playwright';
import { writeFileSync, mkdirSync } from 'node:fs';

const BASE = process.env.BASE || 'http://localhost:8000';
const SHOTS = 'verify-shots';
mkdirSync(SHOTS, { recursive: true });

const fails = [];
const ok = (cond, msg) => { console.log(`${cond ? 'PASS' : 'FAIL'}  ${msg}`); if (!cond) fails.push(msg); };

// A minimal self-contained RAW .sub (no dependency on the big dataset).
const SAMPLE_SUB = [
  'Filetype: Flipper SubGhz RAW File',
  'Version: 1',
  'Frequency: 433920000',
  'Preset: FuriHalSubGhzPresetOok650Async',
  'Protocol: RAW',
  'RAW_Data: 350 -350 350 -700 700 -350 350 -350 700 -700 350 -350 350 -700 700 -350 350 -700 350 -350 700 -350 350 -700 350 -350 350 -700 700 -700 350 -350',
].join('\n');

const consoleErrors = [];
const failedRequests = [];

const browser = await chromium.launch();
const ctx = await browser.newContext();
const page = await ctx.newPage();

page.on('console', (m) => { if (m.type() === 'error') consoleErrors.push(m.text()); });
page.on('requestfailed', (r) => failedRequests.push(`${r.url()} (${r.failure()?.errorText})`));

try {
  // ---- Stage 2: load ----
  const resp = await page.goto(BASE, { waitUntil: 'networkidle', timeout: 30000 });
  ok(resp && resp.ok(), `GET / returned ${resp && resp.status()}`);
  await page.screenshot({ path: `${SHOTS}/1-loaded.png`, fullPage: true });

  // ---- Stage 3: map rendered ----
  await page.waitForSelector('#map', { timeout: 10000 });
  const leafletUp = await page.locator('#map .leaflet-container').count();
  ok(leafletUp > 0, 'Leaflet map initialised (.leaflet-container present)');

  const baselineTxt = await page.locator('#total-captures').innerText().catch(() => '0');
  const baseline = parseInt(baselineTxt, 10) || 0;
  console.log(`      baseline #total-captures = ${baseline}`);

  // ---- Stage 4: switch to Upload, fill the real form ----
  await page.click('a[href="#upload"]').catch(() => {});
  await page.waitForSelector('#upload-section.section.active, #upload-section', { timeout: 5000 });

  await page.setInputFiles('#file-input', {
    name: 'sample.sub',
    mimeType: 'application/octet-stream',
    buffer: Buffer.from(SAMPLE_SUB),
  });
  await page.fill('#default-lat', '47.6062');
  await page.fill('#default-lon', '-122.3321');
  await page.screenshot({ path: `${SHOTS}/2-upload-ready.png`, fullPage: true });

  // The upload button is a global onclick="uploadFiles()".
  await page.click('button[onclick="uploadFiles()"]');

  // ---- Stage 5: results panel shows a match ----
  await page.waitForSelector('#upload-results', { state: 'visible', timeout: 20000 });
  const resultsTxt = (await page.locator('#upload-results').innerText()).trim();
  ok(resultsTxt.length > 0, 'upload results panel is populated');
  ok(/success|processed|device|match|categ/i.test(resultsTxt), 'results mention a processed/device match');
  await page.screenshot({ path: `${SHOTS}/3-results.png`, fullPage: true });

  // ---- Stage 6/8: store incremented (checked via the API the UI uses) ----
  const total = await page.evaluate(async () => {
    const r = await fetch('/api/v1/query/captures?limit=1000');
    return (await r.json()).total;
  });
  ok(total >= baseline + 1, `capture total incremented (${baseline} -> ${total})`);

  // ---- clean-page assertions ----
  ok(consoleErrors.length === 0, `no console errors (saw ${consoleErrors.length})`);
  if (consoleErrors.length) consoleErrors.slice(0, 5).forEach((e) => console.log('        console:', e));
  // CDN/tile requests may legitimately fail if offline — report but treat local assets strictly.
  const localFailed = failedRequests.filter((u) => u.includes('/static/') || u.includes(BASE));
  ok(localFailed.length === 0, `no failed LOCAL asset requests (saw ${localFailed.length})`);
  if (failedRequests.length) failedRequests.slice(0, 8).forEach((u) => console.log('        reqfail:', u));
} catch (err) {
  ok(false, `unhandled error: ${err.message}`);
  await page.screenshot({ path: `${SHOTS}/error.png`, fullPage: true }).catch(() => {});
} finally {
  await browser.close();
}

writeFileSync(`${SHOTS}/summary.json`, JSON.stringify({ fails, consoleErrors, failedRequests }, null, 2));
console.log('\n' + (fails.length ? `FAILED (${fails.length}): ${fails.join(' | ')}` : 'ALL CHECKS PASSED'));
process.exit(fails.length ? 1 : 0);
