/**
 * SET 42b / 42d — B-pipeline edge-wait.
 *
 * After wrangler pages deploy, poll until:
 *   1. /api/extension.zip serves the expected manifest version
 *   2. /app HTML has NO Cloudflare challenge bootstrap
 *   3. Every hashed JS/CSS asset referenced by /app serves the right MIME
 *   4. /assets/x-deadbeef.js is a non-cacheable 404 (not SPA HTML)
 *
 * Usage: npx tsx scripts/edge-wait.ts <version>
 * Env:   EDGE_BASE (default https://rha.software)
 */
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { randomUUID } from 'node:crypto';

const BASE = (process.env.EDGE_BASE || 'https://rha.software').replace(/\/$/, '');
const VERSION = process.argv[2];
if (!VERSION) {
  console.error('usage: npx tsx scripts/edge-wait.ts <version>');
  process.exit(2);
}

const CHALLENGE_RE = /__CF\$cv\$params|cdn-cgi\/challenge-platform/i;
const SLEEP_MS = 4000;
const MAX_MS = 10 * 60 * 1000;

function sleep(ms: number): Promise<void> {
  return new Promise(r => setTimeout(r, ms));
}

async function fetchText(url: string): Promise<{ status: number; headers: Headers; body: string }> {
  const res = await fetch(url, { redirect: 'follow', headers: { 'cache-control': 'no-cache' } });
  const body = await res.text();
  return { status: res.status, headers: res.headers, body };
}

async function checkZipVersion(): Promise<string | null> {
  const url = `${BASE}/api/extension.zip?v=${randomUUID()}`;
  const tmp = path.join(os.tmpdir(), `rha-ext-${randomUUID()}.zip`);
  try {
    const res = await fetch(url);
    if (!res.ok) return null;
    const buf = Buffer.from(await res.arrayBuffer());
    fs.writeFileSync(tmp, buf);
    const manifest = execFileSync('unzip', ['-p', tmp, 'manifest.json'], { encoding: 'utf8' });
    const v = (JSON.parse(manifest) as { version?: string }).version;
    return v ?? null;
  } catch {
    return null;
  } finally {
    try { fs.unlinkSync(tmp); } catch { /* ignore */ }
  }
}

function extractAssets(html: string): string[] {
  const out = new Set<string>();
  for (const m of html.matchAll(/(?:src|href)=["'](\/assets\/[^"']+)["']/g)) {
    out.add(m[1]!);
  }
  return [...out];
}

async function checkAppCoherence(): Promise<{ ok: boolean; detail: string }> {
  const app = await fetchText(`${BASE}/app?v=${randomUUID()}`);
  if (app.status !== 200) return { ok: false, detail: `/app status ${app.status}` };
  if (CHALLENGE_RE.test(app.body)) {
    return { ok: false, detail: '/app contains Cloudflare challenge bootstrap (__CF$cv$params / challenge-platform)' };
  }
  const assets = extractAssets(app.body);
  if (!assets.length) return { ok: false, detail: '/app HTML has no /assets/* references' };
  for (const a of assets) {
    const r = await fetch(`${BASE}${a}?v=${randomUUID()}`, { redirect: 'follow' });
    const ct = r.headers.get('content-type') || '';
    if (!r.ok) return { ok: false, detail: `${a} status ${r.status}` };
    if (/\.js(\?|$)/i.test(a) && !/javascript|ecmascript/i.test(ct)) {
      return { ok: false, detail: `${a} content-type ${ct} (want javascript)` };
    }
    if (/\.css(\?|$)/i.test(a) && !/text\/css/i.test(ct)) {
      return { ok: false, detail: `${a} content-type ${ct} (want text/css)` };
    }
    if (/text\/html/i.test(ct)) {
      return { ok: false, detail: `${a} served text/html — SPA/challenge poison` };
    }
  }
  const dead = await fetch(`${BASE}/assets/x-deadbeef.js?v=${randomUUID()}`, { redirect: 'follow' });
  const deadCt = dead.headers.get('content-type') || '';
  const deadCc = dead.headers.get('cache-control') || '';
  if (dead.status !== 404) {
    return { ok: false, detail: `dead asset status ${dead.status} (want 404), ct=${deadCt}` };
  }
  if (/text\/html/i.test(deadCt)) {
    return { ok: false, detail: 'dead asset returned HTML' };
  }
  if (!/no-store/i.test(deadCc)) {
    return { ok: false, detail: `dead asset cache-control "${deadCc}" (want no-store)` };
  }
  return { ok: true, detail: `app coherent (${assets.length} assets), no challenge, dead=404 no-store` };
}

async function main(): Promise<void> {
  const t0 = Date.now();
  // eslint-disable-next-line no-console
  console.log(`edge-wait: base=${BASE} want version=${VERSION}`);
  while (Date.now() - t0 < MAX_MS) {
    const v = await checkZipVersion();
    const coh = await checkAppCoherence();
    // eslint-disable-next-line no-console
    console.log(`  zip=${v ?? 'null'} | ${coh.detail}`);
    if (v === VERSION && coh.ok) {
      // eslint-disable-next-line no-console
      console.log('edge-wait: READY');
      return;
    }
    await sleep(SLEEP_MS);
  }
  console.error('edge-wait: TIMEOUT');
  process.exit(1);
}

main().catch(e => {
  console.error(e);
  process.exit(1);
});
