#!/usr/bin/env node
// Health check every x402 endpoint:
//   - GETs (trust/*): hit with no payment, expect 402 with Payment-Required header
//     containing solana + eip155:8453 (Base) accepts.
//   - POSTs (actions/*): hit with no payment, expect 402 (or 405/400 if missing
//     body validation runs first \u2014 still acceptable as long as not 5xx).
//
// Exits non-zero if any endpoint is unhealthy.

import { readdir, stat } from 'node:fs/promises';
import { join } from 'node:path';

const BASE = process.env.BASE || 'https://theloopbreaker.com';
const ROOT = 'app/api/x402';

async function listRoutes(dir, prefix = '') {
  const out = [];
  for (const ent of await readdir(dir)) {
    const full = join(dir, ent);
    const s = await stat(full);
    if (s.isDirectory()) {
      out.push(...(await listRoutes(full, prefix + '/' + ent)));
    } else if (ent === 'route.ts') {
      out.push(prefix);
    }
  }
  return out;
}

function categorize(path) {
  // Free, no-payment GETs (return 200).
  if (path === '/trust/health') return 'FREE';
  if (path === '/oracle/chainlink-status') return 'FREE';
  if (path.startsWith('/trust/')) return 'GET';
  if (path === '/trust' || path === '/health') return 'GET';
  if (path.startsWith('/actions/')) return 'POST';
  if (path === '/contract-call') return 'POST';
  if (path === '/verify' || path === '/settle' || path === '/submit-signed') return 'INFRA';
  return 'GET';
}

async function checkEndpoint(path) {
  const method = categorize(path);
  const url = `${BASE}/api/x402${path}`;
  const opts = method === 'POST'
    ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }
    : {};
  let res;
  try {
    res = await fetch(url, opts);
  } catch (e) {
    return { path, method, ok: false, status: 0, reason: 'network: ' + (e?.message || 'fail') };
  }
  const status = res.status;
  const body = await res.text();
  // For GETs we expect 402 + Payment-Required header.
  if (method === 'GET') {
    if (status !== 402) {
      return { path, method, ok: false, status, reason: `expected 402, got ${status}: ${body.slice(0, 200)}` };
    }
    const header = res.headers.get('Payment-Required') || res.headers.get('payment-required') || '';
    if (!header) {
      return { path, method, ok: false, status, reason: 'missing Payment-Required header' };
    }
    let parsed;
    try {
      parsed = JSON.parse(Buffer.from(header, 'base64').toString('utf8'));
    } catch {
      return { path, method, ok: false, status, reason: 'Payment-Required not base64 JSON' };
    }
    const accepts = parsed?.accepts || [];
    const networks = new Set(accepts.map(a => a.network));
    const hasBase = networks.has('eip155:8453') || networks.has('base');
    const hasSolana = networks.has('solana');
    if (!hasBase) return { path, method, ok: false, status, reason: `header missing Base/eip155:8453. Networks: ${[...networks].join(',')}` };
    if (!hasSolana) return { path, method, ok: false, status, reason: `header missing Solana. Networks: ${[...networks].join(',')}` };
    return { path, method, ok: true, status, networks: [...networks] };
  }
  if (method === 'POST') {
    // POSTs may return 402 (typical) or 400/405 if validation rejects body first.
    // Anything that's not 5xx is acceptable for now.
    if (status >= 500) {
      return { path, method, ok: false, status, reason: `5xx: ${body.slice(0, 200)}` };
    }
    return { path, method, ok: true, status };
  }
  if (method === 'FREE') {
    // Free endpoint: must return 200 with valid JSON.
    if (status !== 200) return { path, method, ok: false, status, reason: `expected 200, got ${status}` };
    try {
      JSON.parse(body);
    } catch {
      return { path, method, ok: false, status, reason: 'response not valid JSON' };
    }
    return { path, method, ok: true, status };
  }
  // INFRA
  return { path, method, ok: status < 500, status };
}

async function main() {
  const paths = await listRoutes(ROOT);
  paths.sort();
  console.log(`Found ${paths.length} routes under ${ROOT}\n`);

  const concurrency = 8;
  const results = [];
  for (let i = 0; i < paths.length; i += concurrency) {
    const batch = paths.slice(i, i + concurrency);
    const r = await Promise.all(batch.map(checkEndpoint));
    results.push(...r);
    process.stdout.write('.');
  }
  console.log('\n');

  const failed = results.filter(r => !r.ok);
  const passed = results.filter(r => r.ok);

  console.log(`PASS: ${passed.length} / ${results.length}`);
  if (failed.length) {
    console.log(`\nFAILURES (${failed.length}):`);
    for (const f of failed) {
      console.log(`  [${f.method}] ${f.path}  status=${f.status}  ${f.reason}`);
    }
  }

  // Sanity counts
  const getPaths = results.filter(r => r.method === 'GET');
  const postPaths = results.filter(r => r.method === 'POST');
  console.log(`\nBy category: GET=${getPaths.length}, POST=${postPaths.length}, FREE=${results.filter(r => r.method === 'FREE').length}, INFRA=${results.filter(r => r.method === 'INFRA').length}`);

  // GET network coverage
  const getsWithSolana = getPaths.filter(r => r.ok && r.networks?.includes('solana'));
  console.log(`GETs advertising Solana: ${getsWithSolana.length}/${getPaths.filter(r => r.ok).length}`);

  process.exit(failed.length > 0 ? 1 : 0);
}

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