/**
 * POST /api/x402/cdp-register
 *
 * Server-side proxy to Coinbase's CDP x402 facilitator that registers a paid
 * Vaultfire endpoint with the bazaar discovery API.
 *
 * Why this exists
 * ---------------
 * Coinbase's bazaar (https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources)
 * only catalogs resources after CDP's facilitator processes a verify+settle
 * cycle for them. Our trust routes use a hand-rolled verifier that settles
 * EIP-3009 directly on-chain, so CDP never sees them and never catalogs them.
 *
 * This endpoint accepts a fully-signed x402 v2 payment payload from the
 * client, packages it into the exact PaymentRequirements shape CDP expects
 * (with the target paid endpoint URL set as `resource.url`), and POSTs to
 * CDP's /verify and /settle endpoints. CDP catalogs the resource as a side
 * effect.
 *
 * Request body:
 *   {
 *     paymentHeader: string,     // base64 X-PAYMENT header from createPaymentHeader
 *     resourceUrl: string,       // The endpoint URL we want CDP to catalog
 *     amount: string,            // micros (USDC base units)
 *     description?: string,      // optional, for the bazaar listing
 *   }
 *
 * Response:
 *   { success: true, verified: true, settled: bool, txHash?: string }
 *   { success: false, error: string }
 *
 * Security:
 *   - Only accepts payments to ghostkey.base.eth (PAY_TO_ADDRESS)
 *   - Rate-limited
 *   - Caps at $0.10 per call (no abuse vector)
 */

import { NextRequest, NextResponse } from 'next/server';
import { createCdpAuthHeaders } from '@coinbase/x402';

const PAY_TO = (
  process.env.PAY_TO_ADDRESS || '0xfA15Ee28939B222B0448261A22156070f0A7813C'
).trim();
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const CDP_BASE = 'https://api.cdp.coinbase.com/platform/v2/x402';

// Allowlist: only register theloopbreaker.com URLs
const ALLOWED_HOST_PATTERN = /^https:\/\/(theloopbreaker\.com|theloopbreaker-[a-z0-9-]+\.vercel\.app)\//;

// Cap per-call to prevent any abuse. Manifest max route price is $0.50
// (register-agent / create-*-bond). $1.00 cap covers all current routes
// with 2x headroom and still bounds any abuse to a trivial amount.
const MAX_AMOUNT_MICROS = 1_000_000n; // $1.00

interface RegisterBody {
  paymentHeader: string;
  resourceUrl: string;
  amount: string;
  description?: string;
}

/**
 * Decode the base64 X-PAYMENT header back into a PaymentPayload object.
 * x402 client SDK encodes via safeBase64Encode(JSON.stringify(payload)).
 * We reverse that here. Tolerates both standard base64 and url-safe base64.
 */
function decodePaymentHeader(header: string): any {
  const normalized = header.replace(/-/g, '+').replace(/_/g, '/');
  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
  const json = Buffer.from(padded, 'base64').toString('utf-8');
  return JSON.parse(json);
}

export async function POST(req: NextRequest) {
  let body: RegisterBody;
  try {
    body = await req.json();
  } catch {
    return NextResponse.json(
      { success: false, error: 'Invalid JSON body' },
      { status: 400 },
    );
  }

  const { paymentHeader, resourceUrl, amount, description } = body;

  if (!paymentHeader || !resourceUrl || !amount) {
    return NextResponse.json(
      { success: false, error: 'Missing required fields' },
      { status: 400 },
    );
  }

  // Validate cheap stuff first (allowlist + amount cap) before parsing payload.
  if (!ALLOWED_HOST_PATTERN.test(resourceUrl)) {
    return NextResponse.json(
      { success: false, error: 'resourceUrl not in allowlist' },
      { status: 400 },
    );
  }

  let amountBig: bigint;
  try {
    amountBig = BigInt(amount);
  } catch {
    return NextResponse.json(
      { success: false, error: 'amount not a valid integer' },
      { status: 400 },
    );
  }
  if (amountBig <= 0n || amountBig > MAX_AMOUNT_MICROS) {
    return NextResponse.json(
      { success: false, error: `amount out of bounds (max ${MAX_AMOUNT_MICROS})` },
      { status: 400 },
    );
  }

  let paymentPayload: any;
  try {
    paymentPayload = decodePaymentHeader(paymentHeader);
  } catch (e: any) {
    return NextResponse.json(
      { success: false, error: `Failed to decode paymentHeader: ${e?.message ?? e}` },
      { status: 400 },
    );
  }

  // CDP's hosted facilitator at `/platform/v2/x402/{verify,settle}` accepts
  // x402 V1-shape payloads, exactly as documented in:
  //   https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator/verify-a-payment
  //
  // V1 shape uses:
  //   - x402Version: 1 (top-level AND inside paymentPayload)
  //   - network: short alias ('base', not 'eip155:8453')
  //   - paymentRequirements.maxAmountRequired (not amount)
  //   - no `accepted` / `extensions` fields
  //
  // The page already signs with V1 shape via createPaymentHeader(wallet, 2, requirements)
  // — note the SDK's x402Version=2 arg is misleading; the on-wire payload is
  // still V1 shape. We forward the decoded payload as-is, only rewriting
  // network if the client used CAIP-2.
  const CAIP2_TO_ALIAS: Record<string, string> = {
    'eip155:8453': 'base',
    'eip155:84532': 'base-sepolia',
    'eip155:137': 'polygon',
    'eip155:80002': 'polygon-amoy',
  };
  function toAlias(network: string): string {
    return CAIP2_TO_ALIAS[network] || network;
  }

  if (paymentPayload && typeof paymentPayload === 'object') {
    paymentPayload = {
      x402Version: 1,
      scheme: paymentPayload.scheme || 'exact',
      network: toAlias(paymentPayload.network || 'base'),
      payload: paymentPayload.payload,
    };
  }

  // V1 paymentRequirements shape (matches CDP docs verify-a-payment example)
  const paymentRequirements = {
    scheme: 'exact',
    network: 'base',
    maxAmountRequired: amount,
    resource: resourceUrl,
    description: description || 'Vaultfire x402 paid endpoint',
    mimeType: 'application/json',
    payTo: PAY_TO,
    // Must match maxTimeoutSeconds the client signed against (sweep page = 600).
    // CDP recomputes validBefore from this when validating the EIP-712 typed data.
    maxTimeoutSeconds: 600,
    asset: USDC_BASE,
    extra: {
      name: 'USD Coin',
      version: '2',
    },
  };

  // Get CDP auth headers (uses CDP_API_KEY_ID / CDP_API_KEY_SECRET from env)
  const apiKeyId = process.env.CDP_API_KEY_ID;
  const apiKeySecret = process.env.CDP_API_KEY_SECRET;
  if (!apiKeyId || !apiKeySecret) {
    return NextResponse.json(
      { success: false, error: 'CDP API keys not configured' },
      { status: 503 },
    );
  }

  const authHeaderFn = createCdpAuthHeaders(apiKeyId, apiKeySecret);
  const headers = await authHeaderFn!();

  // Step 1: POST to CDP /verify
  const verifyHeaders: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(headers?.verify || {}),
  };
  const verifyRes = await fetch(`${CDP_BASE}/verify`, {
    method: 'POST',
    headers: verifyHeaders,
    body: JSON.stringify({
      x402Version: 1,
      paymentPayload,
      paymentRequirements,
    }),
  });

  const verifyText = await verifyRes.text();
  let verifyJson: any;
  try {
    verifyJson = JSON.parse(verifyText);
  } catch {
    return NextResponse.json(
      {
        success: false,
        stage: 'verify',
        status: verifyRes.status,
        error: `CDP verify returned non-JSON: ${verifyText.slice(0, 300)}`,
      },
      { status: 502 },
    );
  }

  if (!verifyRes.ok || verifyJson?.isValid === false || verifyJson?.valid === false) {
    return NextResponse.json(
      {
        success: false,
        stage: 'verify',
        status: verifyRes.status,
        error: verifyJson?.invalidReason || verifyJson?.error || 'verify rejected',
        cdpResponse: verifyJson,
      },
      { status: 402 },
    );
  }

  // Step 2: POST to CDP /settle (this is what registers the resource)
  const settleHeaders: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(headers?.settle || {}),
  };
  const settleRes = await fetch(`${CDP_BASE}/settle`, {
    method: 'POST',
    headers: settleHeaders,
    body: JSON.stringify({
      x402Version: 1,
      paymentPayload,
      paymentRequirements,
    }),
  });

  const settleText = await settleRes.text();
  let settleJson: any;
  try {
    settleJson = JSON.parse(settleText);
  } catch {
    return NextResponse.json(
      {
        success: false,
        stage: 'settle',
        status: settleRes.status,
        error: `CDP settle returned non-JSON: ${settleText.slice(0, 300)}`,
      },
      { status: 502 },
    );
  }

  if (!settleRes.ok || settleJson?.success === false) {
    return NextResponse.json(
      {
        success: false,
        stage: 'settle',
        status: settleRes.status,
        error: settleJson?.error || settleJson?.errorReason || 'settle rejected',
        cdpResponse: settleJson,
      },
      { status: 402 },
    );
  }

  return NextResponse.json({
    success: true,
    verified: true,
    settled: true,
    txHash: settleJson?.transaction || settleJson?.txHash,
    cdpResponse: settleJson,
  });
}

export async function OPTIONS() {
  return new NextResponse(null, {
    status: 204,
    headers: {
      'access-control-allow-origin': '*',
      'access-control-allow-methods': 'POST, OPTIONS',
      'access-control-allow-headers': 'Content-Type',
    },
  });
}
