/**
 * GET /api/x402/trust/agentproof-evidence?reportUrl=https://...&expectedSha256=...
 *
 * x402 payment-gated AgentProof bridge — $0.02 USDC per query.
 * Payments routed through CDP facilitator for Bazaar discovery.
 *
 * Fetches a public AgentProof report (https://github.com/dicnunz/agentproof),
 * SHA-256-verifies the results.json against the agent-supplied digest, and
 * returns a normalized evidence envelope. Read-only, no on-chain writes,
 * no re-scoring — AgentProof's deterministic score and category breakdown
 * pass through unchanged.
 *
 * Bridge spec (draft RFC): see /docs in the Vaultfire repo.
 *
 * Security:
 *  - Only https:// URLs accepted.
 *  - RFC1918, loopback, and link-local hosts refused (SSRF guard).
 *  - results.json fetch capped at 256 KB.
 *  - expectedSha256 required; mismatch returns success:false.
 *  - Never accepts private keys (rejects body-shaped key material).
 *  - No telemetry beyond standard x402 billing logs.
 */

import { NextRequest, NextResponse } from 'next/server';
import { createHash } from 'crypto';
import { withX402MultiNet } from '../_lib/with-x402-multinet';

const MAX_BYTES = 256 * 1024; // 256 KB cap on results.json
const FETCH_TIMEOUT_MS = 8000;

const isSha256Hex = (s: string) =>
  /^(0x)?[a-fA-F0-9]{64}$/.test(s || '');

/**
 * Refuse anything that isn't a public https URL. Blocks SSRF surface:
 * RFC1918 (10/8, 172.16/12, 192.168/16), loopback (127/8, ::1), and
 * link-local (169.254/16, fe80::/10). Hostname-based check; we let
 * the resolver do its job for the rest.
 */
function isSafePublicUrl(raw: string): { ok: true; url: URL } | { ok: false; reason: string } {
  let url: URL;
  try {
    url = new URL(raw);
  } catch {
    return { ok: false, reason: 'reportUrl is not a valid URL' };
  }
  if (url.protocol !== 'https:') {
    return { ok: false, reason: 'reportUrl must use https://' };
  }
  const host = url.hostname.toLowerCase();
  if (
    host === 'localhost' ||
    host === '0.0.0.0' ||
    host === '::1' ||
    host.startsWith('127.') ||
    host.startsWith('10.') ||
    host.startsWith('192.168.') ||
    host.startsWith('169.254.') ||
    host.startsWith('fe80::') ||
    host.startsWith('fc00::') ||
    /^172\.(1[6-9]|2[0-9]|3[01])\./.test(host)
  ) {
    return { ok: false, reason: 'private/loopback/link-local hosts refused' };
  }
  return { ok: true, url };
}

/**
 * Reject any body or query value that looks like a private key — matches
 * the Vaultfire-wide policy. Caller signs; we never accept key material.
 */
function rejectsPrivateKeyShape(s: string | null): string | null {
  if (!s) return null;
  if (/^0x[a-fA-F0-9]{64}$/.test(s)) return 'Private-key-shaped value refused. Vaultfire NEVER accepts private keys.';
  if (/^[a-fA-F0-9]{64}$/.test(s) && s.length === 64) return 'Hex-key-shaped value refused. Vaultfire NEVER accepts private keys.';
  return null;
}

interface AgentProofResults {
  agentproofVersion?: string;
  generatedAt?: string;
  project?: { framework?: string; root?: string };
  score?: {
    overall?: number;
    categories?: Array<{ name: string; score: number; max: number }>;
    deductions?: Array<{ category: string; points: number; reason: string; fix: string }>;
    blockingIssues?: string[];
  };
  browser?: {
    enabled?: boolean;
    routes?: Array<{
      route: string;
      ok: boolean;
      status: number | null;
      console?: Array<{ level: string }>;
      visibleErrorText?: string[];
      brokenLinks?: unknown[];
      accessibility?: Array<{ count: number }>;
    }>;
  };
  commandResults?: Array<{ id: string; passed: boolean }>;
}

const handler = async (req: NextRequest) => {
  const { searchParams } = req.nextUrl;
  const reportUrlRaw = searchParams.get('reportUrl');
  const expectedSha256Raw = searchParams.get('expectedSha256');

  if (!reportUrlRaw) {
    return NextResponse.json(
      { error: 'reportUrl query parameter is required (public https URL to AgentProof report folder or results.json)' },
      { status: 400 }
    );
  }
  if (!expectedSha256Raw) {
    return NextResponse.json(
      { error: 'expectedSha256 query parameter is required (hex SHA-256 of results.json — 64 hex chars, optionally 0x-prefixed)' },
      { status: 400 }
    );
  }
  // reportUrl is a URL, never a key; only run private-key-shape guard on it.
  // expectedSha256 is intentionally 64 hex chars by spec, so we validate it via
  // isSha256Hex below rather than the key-shape guard.
  const keyErr = rejectsPrivateKeyShape(reportUrlRaw);
  if (keyErr) return NextResponse.json({ error: keyErr }, { status: 400 });
  if (!isSha256Hex(expectedSha256Raw)) {
    return NextResponse.json({ error: 'expectedSha256 must be 64 hex chars (optionally 0x-prefixed)' }, { status: 400 });
  }
  const expectedSha256 = expectedSha256Raw.toLowerCase().replace(/^0x/, '');

  const safety = isSafePublicUrl(reportUrlRaw);
  if (!safety.ok) {
    return NextResponse.json({ error: (safety as { ok: false; reason: string }).reason }, { status: 400 });
  }
  const safeUrl = (safety as { ok: true; url: URL }).url;
  // Accept either /report/ folder URL (then we append results.json) or a direct results.json URL.
  let resultsUrl = safeUrl.toString();
  if (!/\.json($|\?)/i.test(safeUrl.pathname)) {
    resultsUrl = resultsUrl.replace(/\/?$/, '/') + 'results.json';
    const recheck = isSafePublicUrl(resultsUrl);
    if (!recheck.ok) return NextResponse.json({ error: (recheck as { ok: false; reason: string }).reason }, { status: 400 });
  }

  // Fetch with timeout + size cap
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
  let raw = '';
  let fetchedSha256 = '';
  try {
    const res = await fetch(resultsUrl, {
      signal: controller.signal,
      redirect: 'follow',
      headers: { Accept: 'application/json', 'User-Agent': 'vaultfire-agentproof-evidence/0.1' },
    });
    if (!res.ok) {
      return NextResponse.json(
        { success: false, reportUrl: resultsUrl, error: `Fetch failed: HTTP ${res.status}` },
        { status: 200, headers: { 'Cache-Control': 'no-store' } }
      );
    }
    // Stream with hard cap
    const reader = res.body?.getReader();
    if (!reader) {
      return NextResponse.json({ success: false, error: 'Response body unreadable' }, { status: 200 });
    }
    const chunks: Uint8Array[] = [];
    let bytes = 0;
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      bytes += value.length;
      if (bytes > MAX_BYTES) {
        return NextResponse.json(
          { success: false, reportUrl: resultsUrl, error: `results.json exceeds ${MAX_BYTES} byte cap` },
          { status: 200, headers: { 'Cache-Control': 'no-store' } }
        );
      }
      chunks.push(value);
    }
    const buf = Buffer.concat(chunks);
    raw = buf.toString('utf8');
    fetchedSha256 = createHash('sha256').update(buf).digest('hex');
  } catch (e: any) {
    return NextResponse.json(
      { success: false, reportUrl: resultsUrl, error: `Fetch error: ${e?.name === 'AbortError' ? 'timeout' : (e?.message || String(e))}` },
      { status: 200, headers: { 'Cache-Control': 'no-store' } }
    );
  } finally {
    clearTimeout(timeout);
  }

  if (fetchedSha256 !== expectedSha256) {
    return NextResponse.json(
      {
        success: false,
        reportUrl: resultsUrl,
        error: 'SHA-256 mismatch — report content does not match expectedSha256',
        fetchedSha256: '0x' + fetchedSha256,
        expectedSha256: '0x' + expectedSha256,
      },
      { status: 200, headers: { 'Cache-Control': 'no-store' } }
    );
  }

  let parsed: AgentProofResults;
  try {
    parsed = JSON.parse(raw);
  } catch (e: any) {
    return NextResponse.json(
      { success: false, reportUrl: resultsUrl, error: 'results.json is not valid JSON' },
      { status: 200, headers: { 'Cache-Control': 'no-store' } }
    );
  }

  // Normalize categories into the six AgentProof v0.1.0 names (verified from upstream source).
  const cats = parsed.score?.categories || [];
  const catLookup = (name: string) => cats.find((c) => c.name === name) || null;
  const categories = {
    reliability: catLookup('Reliability'),
    uiProof: catLookup('UI proof'),
    accessibility: catLookup('Accessibility'),
    docsQuality: catLookup('Docs/readme quality'),
    testBuildHealth: catLookup('Test/build health'),
    launchReadiness: catLookup('Launch readiness'),
  };

  // Summarize browser checks without leaking full screenshot paths
  const routes = parsed.browser?.routes || [];
  const browserSummary = {
    enabled: parsed.browser?.enabled ?? false,
    routesCrawled: routes.length,
    consoleErrors: routes.reduce(
      (sum, r) => sum + (r.console || []).filter((c) => c.level === 'error').length,
      0
    ),
    visibleErrors: routes.reduce((sum, r) => sum + (r.visibleErrorText || []).length, 0),
    brokenLinks: routes.reduce((sum, r) => sum + (r.brokenLinks || []).length, 0),
    accessibilityFindings: routes.reduce(
      (sum, r) => sum + (r.accessibility || []).reduce((a, b) => a + (b.count || 0), 0),
      0
    ),
  };

  const cmd = parsed.commandResults || [];
  const checkStatus = (id: string) => {
    const r = cmd.find((c) => c.id === id);
    if (!r) return 'skipped';
    return r.passed ? 'passed' : 'failed';
  };

  return NextResponse.json(
    {
      success: true,
      reportUrl: resultsUrl,
      resultsJsonSha256: '0x' + fetchedSha256,
      agentproofVersion: parsed.agentproofVersion || null,
      generatedAt: parsed.generatedAt || null,
      score: parsed.score?.overall ?? null,
      categories,
      checks: {
        build: checkStatus('build'),
        test: checkStatus('test'),
        lint: checkStatus('lint'),
        typecheck: checkStatus('typecheck'),
        browser: browserSummary,
      },
      deductionCount: parsed.score?.deductions?.length ?? 0,
      blockingIssues: parsed.score?.blockingIssues || [],
      verifiedAt: new Date().toISOString(),
      bridge: {
        spec: 'Vaultfire × AgentProof — draft RFC. Display-layer only. No on-chain writes. No re-scoring.',
        agentproof: 'https://github.com/dicnunz/agentproof',
        vaultfireBondsHeadline:
          'Vaultfire — accountability bonds for AI agents that reward flourishing, not fear. AgentProof evidence (when present) maps only to tasksMastered on partnership bonds, display-layer only. No bridge into accountability bonds.',
      },
      _x402: {
        paid: true,
        price: '$0.02 USDC',
        service: 'vaultfire-trust',
        facilitator: 'cdp',
        pattern: 'read (fetch + verify AgentProof report)',
      },
    },
    { headers: { 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' } }
  );
};

// Multi-network paywall: accepts USDC on Base AND Solana mainnet.
// Bazaar discovery picks up both networks from the 402 response's `accepts` array.
export const GET = withX402MultiNet(handler, {
  price: '$0.02',
  description:
    'Vaultfire × AgentProof bridge (draft RFC) — fetch a public AgentProof report (results.json), SHA-256-verify against the agent-supplied digest, and return a normalized evidence envelope. ' +
    'AgentProof (github.com/dicnunz/agentproof) is the deterministic local proof tool for AI-built apps and PRs. Vaultfire is the on-chain accountability and partnership bond layer. ' +
    'This endpoint surfaces AgentProof scores into the Vaultfire trust surface — display-layer only, never modifies on-chain values, never re-scores. ' +
    'Mapping: only tasksMastered on partnership bonds; no bridge into accountability bonds. ' +
    'Pay $0.02 USDC, pass reportUrl + expectedSha256 (64 hex), receive normalized envelope with overall score, six-category breakdown, command checks, and browser-crawl summary.',
  mimeType: 'application/json',
  outputSchema: {
    type: 'object',
    properties: {
      success: { type: 'boolean' },
      reportUrl: { type: 'string' },
      resultsJsonSha256: { type: 'string' },
      agentproofVersion: { type: 'string' },
      generatedAt: { type: 'string' },
      score: { type: 'number', description: 'AgentProof overall score 0-100' },
      categories: {
        type: 'object',
        description: 'Six AgentProof v0.1.0 score categories',
        properties: {
          reliability: { type: 'object' },
          uiProof: { type: 'object' },
          accessibility: { type: 'object' },
          docsQuality: { type: 'object' },
          testBuildHealth: { type: 'object' },
          launchReadiness: { type: 'object' },
        },
      },
      checks: {
        type: 'object',
        description: 'AgentProof command + browser check summary',
      },
      blockingIssues: { type: 'array', items: { type: 'string' } },
      verifiedAt: { type: 'string' },
      bridge: { type: 'object', description: 'Bridge meta — see spec' },
    },
  },
}) as (req: NextRequest) => Promise<Response>;
