/**
 * x402 Payment Verification Helper
 *
 * Handles both x402 v2 (PAYMENT-SIGNATURE with EIP-3009 transferWithAuthorization)
 * and v1 (X-Payment with custom EIP-712) payment flows.
 *
 * v2 flow: Client signs an EIP-3009 transferWithAuthorization for USDC.
 * Server recovers the signer, verifies parameters, then executes the transfer on-chain.
 *
 * Returns a proper x402 v2 Payment-Required header so the official @x402/fetch
 * client SDK can parse it and automatically handle the payment flow.
 */

import { NextRequest, NextResponse } from 'next/server';
import { x402ReleaseGateClosed } from '../../_lib/release-gate';

// Runtime payment configuration must be explicit. No address is fabricated from source.
const EXPECTED_RECIPIENT = process.env.PAY_TO_ADDRESS?.trim() ?? '';

// Base mainnet USDC
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

interface VerifyOptions {
  /** Minimum USDC amount in base units (6 decimals). e.g. '1000' = $0.001 */
  minAmount: string;
  /** Human-readable description for the endpoint */
  description?: string;
  /** Optional output schema for the response body (JSON Schema). Surfaced in the
   *  v2 Payment-Required header so the agentic.market validator and any client
   *  SDK can introspect the response shape before paying. */
  outputSchema?: Record<string, unknown>;
  /** Optional input schema describing accepted query/body parameters (JSON Schema). */
  inputSchema?: Record<string, unknown>;
  /** Optional example output for the bazaar.info.output.example block. */
  outputExample?: unknown;
  /** Optional example input for the bazaar.info.input.queryParams block. */
  inputExample?: Record<string, unknown>;
  /** HTTP method (defaults to GET). */
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
}

/**
 * Derive a structurally-correct placeholder example from a JSON Schema.
 * Used to auto-fill bazaar.info.output.example when a route hasn't supplied
 * an explicit example, so the agentic.market validator sees a valid sample.
 */
function deriveExampleFromSchema(schema: unknown): unknown {
  if (!schema || typeof schema !== 'object') return null;
  const s = schema as Record<string, unknown>;
  const type = s.type as string | string[] | undefined;
  const t = Array.isArray(type) ? type[0] : type;
  if (t === 'object' || (s.properties && !t)) {
    const props = (s.properties as Record<string, unknown>) || {};
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(props)) {
      out[k] = deriveExampleFromSchema(v);
    }
    return out;
  }
  if (t === 'array') {
    const items = s.items;
    return items ? [deriveExampleFromSchema(items)] : [];
  }
  if (t === 'string') {
    if (s.example !== undefined) return s.example;
    if (s.enum && Array.isArray(s.enum) && s.enum.length > 0) return s.enum[0];
    return '';
  }
  if (t === 'number' || t === 'integer') {
    return typeof s.example === 'number' ? s.example : 0;
  }
  if (t === 'boolean') {
    return false;
  }
  if (t === 'null') {
    return null;
  }
  if (s.example !== undefined) return s.example;
  return null;
}

interface VerifySuccess {
  valid: true;
  signer: string;
  amountHuman: string;
  chain: string;
}

interface VerifyFailure {
  valid: false;
  response: NextResponse;
}

function encodePaymentRequiredHeader(paymentRequired: object): string {
  return Buffer.from(JSON.stringify(paymentRequired)).toString('base64');
}

export async function verifyX402Payment(
  req: NextRequest,
  opts: VerifyOptions
): Promise<VerifySuccess | VerifyFailure> {
  // This helper must never issue a challenge or accept a payment until the release gate is replaced by reviewed evidence.
  return { valid: false, response: x402ReleaseGateClosed() };

  // x402 v2 uses PAYMENT-SIGNATURE, v1 uses X-Payment
  const paymentHeader =
    req.headers.get('Payment-Signature') ||
    req.headers.get('PAYMENT-SIGNATURE') ||
    req.headers.get('X-Payment') ||
    req.headers.get('x-payment');

  if (!paymentHeader) {
    // Build the x402 v2 PaymentRequired structure.
    // If this legacy code is reactivated after review, it must advertise only
    // the explicitly configured Base rail represented below.
    const paymentRequired: Record<string, unknown> = {
      x402Version: 2,
      error: 'Payment required',
      resource: {
        url: req.nextUrl.toString(),
        description: opts.description || 'Vaultfire Trust Service',
        mimeType: 'application/json',
      },
      accepts: [
        {
          scheme: 'exact',
          // 'base' is the short alias the x402 client SDKs (createPaymentHeader,
          // selectPaymentRequirements) match on. The CAIP-2 form 'eip155:8453'
          // also exists in the top-level `networks` field below for v2 crawlers.
          // Coinbase's CDP bazaar requires the short alias to catalog the route.
          network: 'base',
          amount: opts.minAmount,
          asset: USDC_BASE,
          payTo: EXPECTED_RECIPIENT,
          maxTimeoutSeconds: 60,
          extra: {
            name: 'USD Coin',
            version: '2',
          },
        },
      ],
    };
    // Build the v2 Bazaar Discovery Extension per the official x402 spec
    // (https://docs.cdp.coinbase.com/x402/bazaar). The extension contains an
    // `info` block with input/output discovery metadata and a `schema` block
    // (JSON Schema) that validates the structure of `info`. The agentic.market
    // validator at https://agentic.market/validate fails endpoints that ship a
    // bazaar extension without an info block, so we always emit both.
    const method = opts.method || 'GET';
    const isQueryMethod = method === 'GET' || method === 'HEAD' || method === 'DELETE';

    const bazaarInfo: Record<string, unknown> = {
      input: isQueryMethod
        ? {
            type: 'http',
            method,
            ...(opts.inputExample ? { queryParams: opts.inputExample } : {}),
            ...(opts.inputSchema ? { schema: opts.inputSchema } : {}),
          }
        : {
            type: 'http',
            method,
            bodyType: 'json',
            ...(opts.inputExample ? { body: opts.inputExample } : {}),
            ...(opts.inputSchema ? { schema: opts.inputSchema } : {}),
          },
    };
    if (opts.outputSchema || opts.outputExample) {
      // If the route didn't supply an explicit example, derive a minimal one
      // from the JSON Schema so the agentic.market validator sees an example
      // payload (it warns when output.example is missing). The derived example
      // is type-correct — strings get "", numbers get 0, booleans get false,
      // arrays get [], objects recurse — so consumers get a structurally
      // accurate template even when we haven't hand-curated one.
      const example =
        opts.outputExample !== undefined
          ? opts.outputExample
          : opts.outputSchema
            ? deriveExampleFromSchema(opts.outputSchema)
            : undefined;
      bazaarInfo.output = {
        type: 'json',
        ...(example !== undefined ? { example } : {}),
        ...(opts.outputSchema ? { schema: opts.outputSchema } : {}),
      };
    }

    const bazaarSchema = {
      $schema: 'https://json-schema.org/draft/2020-12/schema',
      type: 'object',
      properties: {
        input: { type: 'object' },
        output: { type: 'object' },
      },
      required: ['input'],
    };

    paymentRequired.extensions = {
      agentkit: {},
      bazaar: {
        info: bazaarInfo,
        schema: bazaarSchema,
      },
    };
    // Keep the legacy top-level outputSchema for v1 clients still reading it.
    if (opts.outputSchema) {
      paymentRequired.outputSchema = opts.outputSchema;
    }
    if (opts.inputSchema) {
      paymentRequired.inputSchema = opts.inputSchema;
    }

    const encoded = encodePaymentRequiredHeader(paymentRequired);

    // Build the full `accepts[]` shape (matches what's in the base64
    // Payment-Required header). Clients SDKs (createPaymentHeader,
    // selectPaymentRequirements) read these fields directly from the JSON
    // body — `maxAmountRequired` is the critical one: viem's BigInt() coercion
    // throws "not an integer" if it's missing.
    const fullAcceptsBase = {
      scheme: 'exact',
      network: 'base',
      maxAmountRequired: opts.minAmount,
      resource: req.nextUrl.toString(),
      description: opts.description || 'Vaultfire Trust Service',
      mimeType: 'application/json',
      payTo: EXPECTED_RECIPIENT,
      maxTimeoutSeconds: 60,
      asset: USDC_BASE,
      extra: {
        name: 'USD Coin',
        version: '2',
      },
    };

    return {
      valid: false,
      response: NextResponse.json(
        {
          x402Version: 2,
          error: 'Payment required',
          protocol: 'x402',
          price: `$${(Number(opts.minAmount) / 1e6).toFixed(4)} USDC`,
          // This endpoint accepts USDC on multiple networks. The canonical
          // machine-readable list is in the `Payment-Required` header (base64)
          // and the `accepts` field below.
          networks: ['eip155:8453'],
          accepts: [fullAcceptsBase],
          verify: 'https://theloopbreaker.com/api/x402/verify',
          settle: 'https://theloopbreaker.com/api/x402/settle',
          discovery: 'https://theloopbreaker.com/.well-known/x402.json',
        },
        {
          status: 402,
          headers: {
            'Payment-Required': encoded,
            'X-Payment-Required': 'true',
            'X-Payment-Price': `${opts.minAmount}`,
            'X-Payment-Currency': 'USDC',
            // Comma-separated list of accepted networks. Clients should parse
            // the base64 `Payment-Required` header for full per-network details.
            'X-Payment-Networks': 'eip155:8453',
            'X-Payment-PayTo-Base': EXPECTED_RECIPIENT,
          },
        }
      ),
    };
  }

  // Payment header present — verify it
  try {
    let paymentPayload: any;
    try {
      paymentPayload = JSON.parse(
        Buffer.from(paymentHeader, 'base64').toString('utf-8')
      );
    } catch {
      return {
        valid: false,
        response: NextResponse.json(
          { error: 'Invalid payment header encoding', protocol: 'x402' },
          { status: 400 }
        ),
      };
    }

    // Solana is deliberately not accepted or advertised by this runtime.

    // x402 v2 exact scheme: EIP-3009 transferWithAuthorization (Base)
    if (paymentPayload.x402Version === 2 && paymentPayload.payload?.authorization) {
      const auth = paymentPayload.payload.authorization;
      const sig = paymentPayload.payload.signature;
      const resource = paymentPayload.payload.resource ?? paymentPayload.resource;

      if (paymentPayload.network !== 'base' || resource !== req.nextUrl.toString()) {
        return {
          valid: false,
          response: NextResponse.json(
            { error: 'Payment payload is not bound to this Base resource', code: 'payment_request_binding_mismatch', protocol: 'x402' },
            { status: 400 },
          ),
        };
      }

      // Validate basic fields
      if (!auth.from || !auth.to || !auth.value || !sig) {
        return {
          valid: false,
          response: NextResponse.json(
            { error: 'Missing required fields in payment payload', protocol: 'x402' },
            { status: 400 }
          ),
        };
      }

      // Check amount meets minimum
      if (BigInt(auth.value) < BigInt(opts.minAmount)) {
        return {
          valid: false,
          response: NextResponse.json(
            { error: `Insufficient payment: ${auth.value} < ${opts.minAmount}`, protocol: 'x402' },
            { status: 402 }
          ),
        };
      }

      // Check recipient
      if (auth.to.toLowerCase() !== EXPECTED_RECIPIENT.toLowerCase()) {
        return {
          valid: false,
          response: NextResponse.json(
            { error: `Wrong recipient: ${auth.to}`, protocol: 'x402' },
            { status: 400 }
          ),
        };
      }

      // Check deadline
      const now = Math.floor(Date.now() / 1000);
      if (Number(auth.validAfter) > now || Number(auth.validBefore) < now) {
        return {
          valid: false,
          response: NextResponse.json(
            { error: 'Payment authorization expired', protocol: 'x402' },
            { status: 400 }
          ),
        };
      }

      // Execute the transfer on-chain via USDC.transferWithAuthorization
      // This settles the payment — the USDC moves from sender to recipient
      const settleResult = await settleEIP3009(auth, sig);

      if (!settleResult.success) {
        return {
          valid: false,
          response: NextResponse.json(
            { error: 'Payment settlement failed', code: 'payment_settlement_failed', protocol: 'x402' },
            { status: 503 },
          ),
        };
      }

      const humanAmount = (Number(auth.value) / 1e6).toFixed(4);
      return {
        valid: true,
        signer: auth.from,
        amountHuman: `$${humanAmount} USDC`,
        chain: 'Base',
      };
    }

    return {
      valid: false,
      response: NextResponse.json({ error: 'x402 v1 payment payloads are not accepted', protocol: 'x402' }, { status: 400 }),
    };
  } catch (err) {
    console.error('[x402/trust] Verification error:', err);
    return {
      valid: false,
      response: NextResponse.json(
        { error: 'Payment verification service error', protocol: 'x402' },
        { status: 503 }
      ),
    };
  }
}

/**
 * Settle an EIP-3009 transferWithAuthorization on Base.
 * Calls USDC.transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s)
 */
async function settleEIP3009(
  auth: { from: string; to: string; value: string; validAfter: string; validBefore: string; nonce: string },
  signature: string
): Promise<{ success: boolean; txHash?: string; error?: string }> {
  try {
    if (auth.from.toLowerCase() === auth.to.toLowerCase()) {
      return { success: false, error: 'self-payment is not a settlement' };
    }

    // Real payments require an explicit relayer configuration.
    const relayerKey = process.env.DEPLOYER_PRIVATE_KEY;
    if (!relayerKey) {
      return { success: false, error: 'No relayer configured' };
    }

    // Dynamic import to avoid loading viem in the cold path
    const { createPublicClient, createWalletClient, http, parseAbi } = await import('viem');
    const { base } = await import('viem/chains');
    const { privateKeyToAccount } = await import('viem/accounts');

    const normalizedKey = (relayerKey.startsWith('0x') ? relayerKey : `0x${relayerKey}`) as `0x${string}`;
    const account = privateKeyToAccount(normalizedKey);

    const publicClient = createPublicClient({ chain: base, transport: http('https://mainnet.base.org') });
    const walletClient = createWalletClient({ account, chain: base, transport: http('https://mainnet.base.org') });

    // Split signature into v, r, s
    const sigBytes = signature.startsWith('0x') ? signature.slice(2) : signature;
    const r = `0x${sigBytes.slice(0, 64)}` as `0x${string}`;
    const s = `0x${sigBytes.slice(64, 128)}` as `0x${string}`;
    const v = parseInt(sigBytes.slice(128, 130), 16);

    const abi = parseAbi([
      'function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)',
    ]);

    const txHash = await walletClient.writeContract({
      address: USDC_BASE as `0x${string}`,
      abi,
      functionName: 'transferWithAuthorization',
      args: [
        auth.from as `0x${string}`,
        auth.to as `0x${string}`,
        BigInt(auth.value),
        BigInt(auth.validAfter),
        BigInt(auth.validBefore),
        auth.nonce as `0x${string}`,
        v,
        r,
        s,
      ],
    });

    const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, timeout: 30_000 });
    if (receipt.status !== 'success') return { success: false, error: 'Settlement transaction reverted' };
    return { success: true, txHash };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    return { success: false, error: msg };
  }
}
