/**
 * POST /api/x402/email/verify
 *
 * x402-Email Spec v0.1 verifier.
 *
 * Accepts a JSON body containing email headers and returns a verdict on
 * sender identity, bond status, x402 payment settlement, and a non-binding
 * routing recommendation.
 *
 * Body shape (preferred):
 *   { "headers": { "x-vaultfire-sender": "0x...", "x-vaultfire-bond": "...", ... } }
 *
 * Or flat top-level keys:
 *   { "x-vaultfire-sender": "0x...", "x-402-payment": "base:0x...", ... }
 *
 * Both shapes are normalized to lowercase keys.
 *
 * GET on this same path returns the spec descriptor (no payment, no work) —
 * useful for liveness checks and for clients sniffing the endpoint.
 *
 * This route never throws. Any internal failure is reported as
 *   { specVersion, verified: false, error: "...", recommendation: "review" }
 * with HTTP 200, so a recipient mail server never sees a 500.
 */
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const revalidate = 0;

const SPEC_VERSION = '0.1.0';
const ORIGIN = 'https://theloopbreaker.com';
const SPEC_URL = `${ORIGIN}/x402-email`;
const WELL_KNOWN_URL = `${ORIGIN}/.well-known/x402-email.json`;
const CLAIM_URL = `${ORIGIN}/api/x402/email/claim`; // reserved for v0.2

const SUPPORTED_CHAINS = ['base'] as const;

type Recommendation = 'inbox' | 'review' | 'spam';

interface VerifyResponse {
  specVersion: string;
  verified: boolean;
  sender: {
    address: string | null;
    trustScore: number | null;
    registered: boolean | null;
    vnsName: string | null;
  };
  bond: {
    id: string | null;
    active: boolean | null;
    stakedEth: string | null;
    chain: string | null;
    explorer: string | null;
  };
  payment: {
    settled: boolean | null;
    amount: string | null;
    asset: string | null;
    tx: string | null;
  };
  recommendation: Recommendation;
  reasons: string[];
  claimUrl: string;
  specUrl: string;
  tookMs: number;
  error?: string;
}

function emptyResponse(): VerifyResponse {
  return {
    specVersion: SPEC_VERSION,
    verified: false,
    sender: { address: null, trustScore: null, registered: null, vnsName: null },
    bond: { id: null, active: null, stakedEth: null, chain: null, explorer: null },
    payment: { settled: null, amount: null, asset: null, tx: null },
    recommendation: 'review',
    reasons: [],
    claimUrl: CLAIM_URL,
    specUrl: SPEC_URL,
    tookMs: 0,
  };
}

function normalizeHeaders(input: unknown): Record<string, string> {
  const out: Record<string, string> = {};
  if (!input || typeof input !== 'object') return out;
  const obj = input as Record<string, unknown>;
  // If body has a "headers" sub-object, use that; otherwise use top-level.
  const source =
    obj.headers && typeof obj.headers === 'object'
      ? (obj.headers as Record<string, unknown>)
      : obj;
  for (const [k, v] of Object.entries(source)) {
    if (v == null) continue;
    if (typeof v === 'string') out[k.toLowerCase()] = v;
    else if (typeof v === 'number' || typeof v === 'boolean') out[k.toLowerCase()] = String(v);
  }
  return out;
}

function isHexAddress(s: string): boolean {
  return /^0x[a-fA-F0-9]{40}$/.test(s);
}

function parsePayment(raw: string | undefined): { chain: string; tx: string } | null {
  if (!raw) return null;
  // Accept "base:0x..." or just "0x..." (assume base)
  const m = raw.match(/^([a-zA-Z0-9-]+):(0x[a-fA-F0-9]{64})$/);
  if (m) return { chain: m[1].toLowerCase(), tx: m[2] };
  if (/^0x[a-fA-F0-9]{64}$/.test(raw)) return { chain: 'base', tx: raw };
  return null;
}

async function lookupSender(
  origin: string,
  address: string,
): Promise<{ trustScore: number | null; registered: boolean | null; vnsName: string | null }> {
  try {
    const r = await fetch(
      `${origin}/api/agent/status?address=${encodeURIComponent(address)}&chain=base`,
      { cache: 'no-store', signal: AbortSignal.timeout(8000) },
    );
    if (!r.ok) return { trustScore: null, registered: null, vnsName: null };
    const j: any = await r.json();
    const trustScore =
      typeof j?.trustScore === 'number'
        ? j.trustScore
        : typeof j?.combinedScore === 'number'
          ? j.combinedScore
          : null;
    const registered =
      typeof j?.registered === 'boolean'
        ? j.registered
        : typeof j?.isRegistered === 'boolean'
          ? j.isRegistered
          : null;
    const vnsName =
      (typeof j?.vnsName === 'string' && j.vnsName) ||
      (typeof j?.name === 'string' && j.name) ||
      null;
    return { trustScore, registered, vnsName };
  } catch {
    return { trustScore: null, registered: null, vnsName: null };
  }
}

function decideRecommendation(r: VerifyResponse): {
  rec: Recommendation;
  reasons: string[];
} {
  const reasons: string[] = [];
  let score = 0;

  if (r.sender.address) {
    score += 1;
    reasons.push('sender address provided');
  }
  if (r.sender.registered) {
    score += 1;
    reasons.push('sender is registered on ERC-8004');
  }
  if (typeof r.sender.trustScore === 'number') {
    if (r.sender.trustScore >= 60) {
      score += 2;
      reasons.push(`trust score ${r.sender.trustScore} ≥ 60`);
    } else if (r.sender.trustScore >= 30) {
      score += 1;
      reasons.push(`trust score ${r.sender.trustScore} (medium)`);
    } else {
      reasons.push(`trust score ${r.sender.trustScore} (low)`);
    }
  }
  if (r.bond.active) {
    score += 2;
    reasons.push('active accountability bond');
  } else if (r.bond.id) {
    reasons.push('bond referenced but not verified active');
  }
  if (r.payment.settled) {
    score += 1;
    reasons.push('x402 payment settled');
  } else if (r.payment.tx) {
    reasons.push('x402 payment provided but not yet verified');
  }

  let rec: Recommendation;
  if (score >= 4) rec = 'inbox';
  else if (score >= 2) rec = 'review';
  else rec = 'spam';

  return { rec, reasons };
}

// ───────────────────────── GET ─────────────────────────
export async function GET() {
  return NextResponse.json(
    {
      specVersion: SPEC_VERSION,
      service: 'x402-email verifier',
      method: 'POST',
      contentType: 'application/json',
      bodyShape: {
        headers: {
          'x-vaultfire-sender': '0x...',
          'x-vaultfire-bond': '<contract>:<id> or just <id>',
          'x-vaultfire-chain': 'base',
          'x-402-payment': 'base:0x<txHash>',
          'x-402-amount': '0.01 USDC',
        },
      },
      supportedChains: SUPPORTED_CHAINS,
      specUrl: SPEC_URL,
      wellKnown: WELL_KNOWN_URL,
    },
    { headers: { 'cache-control': 'public, max-age=300' } },
  );
}

// ───────────────────────── POST ─────────────────────────
export async function POST(req: NextRequest) {
  const t0 = Date.now();
  const out = emptyResponse();

  let body: unknown = null;
  try {
    body = await req.json();
  } catch {
    out.error = 'invalid JSON body';
    out.tookMs = Date.now() - t0;
    return NextResponse.json(out);
  }

  const h = normalizeHeaders(body);

  // Sender
  const senderRaw = h['x-vaultfire-sender'] || h['vaultfire-sender'] || '';
  if (senderRaw && isHexAddress(senderRaw)) {
    out.sender.address = senderRaw;
  } else if (senderRaw) {
    out.reasons.push('sender header present but not a valid 0x address');
  }

  // Bond
  const bondRaw = h['x-vaultfire-bond'] || h['vaultfire-bond'] || '';
  if (bondRaw) {
    out.bond.id = bondRaw;
    out.bond.chain = h['x-vaultfire-chain'] || 'base';
  }

  // Payment
  const paymentRaw = h['x-402-payment'] || h['402-payment'] || '';
  const parsedPay = parsePayment(paymentRaw);
  if (parsedPay) {
    out.payment.tx = parsedPay.tx;
    if (!out.bond.chain) out.bond.chain = parsedPay.chain;
    // We don't actually fetch the on-chain receipt in v0.1 — that's v0.2.
    // We mark as "not yet verified"; recommendation logic accounts for that.
    out.payment.settled = null;
  }
  const amountRaw = h['x-402-amount'] || h['402-amount'] || '';
  if (amountRaw) {
    const m = amountRaw.match(/^\s*([\d.]+)\s*([A-Za-z]+)?\s*$/);
    if (m) {
      out.payment.amount = m[1];
      out.payment.asset = m[2] ? m[2].toUpperCase() : 'USDC';
    }
  }

  // Sender lookup (best-effort, never throws)
  if (out.sender.address) {
    const origin = req.nextUrl.origin || ORIGIN;
    const info = await lookupSender(origin, out.sender.address);
    out.sender.trustScore = info.trustScore;
    out.sender.registered = info.registered;
    out.sender.vnsName = info.vnsName;
  }

  // Bond explorer link (best-effort string formatting only — no chain reads in v0.1)
  if (out.bond.id && out.bond.chain === 'base') {
    out.bond.explorer = 'https://basescan.org/address/0x6750D28865434344e04e1D0a6044394b726C3dfE';
  }

  // Decide
  const { rec, reasons } = decideRecommendation(out);
  out.recommendation = rec;
  out.reasons = [...out.reasons, ...reasons];
  out.verified = !!(out.sender.address || out.bond.id);
  out.tookMs = Date.now() - t0;

  return NextResponse.json(out, {
    headers: { 'cache-control': 'no-store' },
  });
}

// ───────────────────────── OPTIONS (CORS) ─────────────────────────
export async function OPTIONS() {
  return new NextResponse(null, {
    status: 204,
    headers: {
      'access-control-allow-origin': '*',
      'access-control-allow-methods': 'GET, POST, OPTIONS',
      'access-control-allow-headers': 'content-type',
      'access-control-max-age': '86400',
    },
  });
}
