/**
 * POST /api/x402/verify
 *
 * Verifies an x402 payment authorization header.
 *
 * Multi-chain support: Base, Avalanche, Arbitrum, Polygon.
 * The chain is determined by the `chainId` field in the request body,
 * defaulting to Base (8453) for backwards compatibility.
 *
 * The x402 protocol works as follows:
 *   1. Client signs an EIP-712 typed data structure authorizing a USDC transferFrom
 *   2. Client sends the signed authorization in the X-Payment header (base64-encoded JSON)
 *   3. This endpoint recovers the signer, checks the payment parameters, and verifies
 *      that the signer has sufficient USDC allowance for the relayer to execute transferFrom
 *
 * Supported Chains:
 *   Base (8453)      — USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
 *   Avalanche (43114) — USDC: 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E
 *   Arbitrum (42161)  — USDC: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831
 *   Polygon (137)     — USDC: 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
 *
 * Request body:
 *   {
 *     paymentHeader: string       // base64-encoded JSON: { signature, from, to, amount, nonce, deadline }
 *     expectedRecipient?: string  // address that should receive the payment
 *     expectedAmount?: string     // expected USDC amount in base units (6 decimals)
 *     chainId?: number            // chain ID (default: 8453 = Base)
 *   }
 *
 * Response (valid):
 *   { valid: true, signer, from, to, amount, amountHuman, allowance, balance, deadline, relayer, chain, chainId }
 *
 * Response (invalid):
 *   { valid: false, error: string }
 */

import { NextRequest, NextResponse } from 'next/server';
import { x402ReleaseGateClosed } from '../_lib/release-gate';
import {
  createPublicClient,
  http,
  recoverTypedDataAddress,
  parseAbi,
  type Hex,
  type Chain,
} from 'viem';
import { checkRateLimit, RATE_LIMIT_WRITE } from '../../../lib/rate-limit';
import { base } from 'viem/chains';
import { avalanche } from 'viem/chains';
import { arbitrum } from 'viem/chains';
import { polygon } from 'viem/chains';

// ---------------------------------------------------------------------------
// Chain Configuration
// ---------------------------------------------------------------------------

interface ChainConfig {
  chain: Chain;
  usdc: `0x${string}`;
  rpc: string;
  name: string;
  explorer: string;
}

const CHAIN_CONFIGS: Record<number, ChainConfig> = {
  8453: {
    chain: base,
    usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    rpc: process.env.BASE_RPC_URL || 'https://mainnet.base.org',
    name: 'Base',
    explorer: 'https://basescan.org',
  },
  43114: {
    chain: avalanche,
    usdc: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
    rpc: process.env.AVALANCHE_RPC_URL || 'https://api.avax.network/ext/bc/C/rpc',
    name: 'Avalanche',
    explorer: 'https://snowtrace.io',
  },
  42161: {
    chain: arbitrum,
    usdc: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
    rpc: process.env.ARBITRUM_RPC_URL || 'https://arb1.arbitrum.io/rpc',
    name: 'Arbitrum',
    explorer: 'https://arbiscan.io',
  },
  137: {
    chain: polygon,
    usdc: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
    rpc: process.env.POLYGON_RPC_URL || 'https://polygon-bor-rpc.publicnode.com',
    name: 'Polygon',
    explorer: 'https://polygonscan.com',
  },
};

const DEFAULT_CHAIN_ID = 8453;
const USDC_DECIMALS = 6;

// ---------------------------------------------------------------------------
// VF-03: Nonce deduplication store — prevents replay attacks
// ---------------------------------------------------------------------------
interface NonceEntry {
  seenAt: number;
}

const seenNonces = new Map<string, NonceEntry>();
const NONCE_TTL_MS = 60 * 60 * 1000; // 1 hour

function cleanupNonces(): void {
  const cutoff = Date.now() - NONCE_TTL_MS;
  for (const [key, entry] of seenNonces) {
    if (entry.seenAt < cutoff) seenNonces.delete(key);
  }
}

function hasNonceBeenSeen(nonce: string, chainId: number): boolean {
  cleanupNonces();
  return seenNonces.has(`${chainId}:${nonce}`);
}

function markNonceSeen(nonce: string, chainId: number): void {
  seenNonces.set(`${chainId}:${nonce}`, { seenAt: Date.now() });
}

// Relayer address — the deployer key that will execute transferFrom.
// VF-013: Read from X402_RELAYER_ADDRESS env var so it can be rotated without a
// redeploy.  The hardcoded fallback exists only to prevent the route from
// crashing in local development where the env var may not be set.
const RELAYER_ADDRESS = (
  process.env.X402_RELAYER_ADDRESS ||
  process.env.RELAYER_ADDRESS ||
  process.env.DEPLOYER_ADDRESS ||
  '0xfA15Ee28939B222B0448261A22156070f0A7813C'
).trim() as `0x${string}`;

const USDC_ABI = parseAbi([
  'function allowance(address owner, address spender) view returns (uint256)',
  'function balanceOf(address account) view returns (uint256)',
]);

// EIP-712 types for PaymentAuthorization
const X402_TYPES = {
  PaymentAuthorization: [
    { name: 'from', type: 'address' },
    { name: 'to', type: 'address' },
    { name: 'amount', type: 'uint256' },
    { name: 'nonce', type: 'bytes32' },
    { name: 'deadline', type: 'uint256' },
  ],
} as const;

function getX402Domain(chainId: number, usdcAddress: `0x${string}`) {
  return {
    name: 'x402Payment',
    version: '1',
    chainId,
    verifyingContract: usdcAddress,
  } as const;
}

export async function POST(req: NextRequest) {
  return x402ReleaseGateClosed();

  /* Release gate intentionally makes the legacy implementation unreachable.
     Keep it below only for a separately reviewed replacement/migration. */
  // VF-02: Rate limiting — 20 POST requests per minute per IP
  const rateLimitResponse = checkRateLimit(req, RATE_LIMIT_WRITE);
  if (rateLimitResponse) return rateLimitResponse;

  try {
    const body = await req.json();
    const { paymentHeader, expectedRecipient, expectedAmount, chainId: requestedChainId } = body;

    if (!paymentHeader) {
      return NextResponse.json({ valid: false, error: 'Missing paymentHeader' }, { status: 400 });
    }

    // Resolve chain configuration
    const chainId = requestedChainId && CHAIN_CONFIGS[requestedChainId]
      ? requestedChainId
      : DEFAULT_CHAIN_ID;
    const chainConfig = CHAIN_CONFIGS[chainId];

    const publicClient = createPublicClient({
      chain: chainConfig.chain,
      transport: http(chainConfig.rpc),
    });

    const X402_DOMAIN = getX402Domain(chainId, chainConfig.usdc);

    // Decode the base64-encoded payment header
    let paymentData: {
      signature: Hex;
      from: `0x${string}`;
      to: `0x${string}`;
      amount: string;
      nonce: Hex;
      deadline: string;
    };

    try {
      const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');
      paymentData = JSON.parse(decoded);
    } catch {
      return NextResponse.json({ valid: false, error: 'Invalid paymentHeader encoding' }, { status: 400 });
    }

    const { signature, from, to, amount, nonce, deadline } = paymentData;

    // Validate required fields
    if (!signature || !from || !to || !amount || !nonce || !deadline) {
      return NextResponse.json({ valid: false, error: 'Missing required fields in payment header' }, { status: 400 });
    }

    // VF-03: Check nonce has not been replayed
    if (hasNonceBeenSeen(nonce, chainId)) {
      return NextResponse.json({
        valid: false,
        error: 'Nonce has already been used. This payment authorization cannot be replayed.',
      }, { status: 400 });
    }

    // Check deadline has not passed
    const deadlineTs = BigInt(deadline);
    const nowTs = BigInt(Math.floor(Date.now() / 1000));
    if (deadlineTs < nowTs) {
      return NextResponse.json({
        valid: false,
        error: `Payment authorization expired at ${new Date(Number(deadlineTs) * 1000).toISOString()}`,
      }, { status: 400 });
    }

    // Recover signer from EIP-712 signature
    let recoveredSigner: `0x${string}`;
    try {
      recoveredSigner = await recoverTypedDataAddress({
        domain: X402_DOMAIN,
        types: X402_TYPES,
        primaryType: 'PaymentAuthorization',
        message: {
          from: from as `0x${string}`,
          to: to as `0x${string}`,
          amount: BigInt(amount),
          nonce: nonce as Hex,
          deadline: BigInt(deadline),
        },
        signature: signature as Hex,
      });
    } catch (err) {
      return NextResponse.json({
        valid: false,
        error: 'Signature recovery failed: invalid payment signature',
      }, { status: 400 });
    }

    // Verify the recovered signer matches the claimed 'from' address
    if (recoveredSigner.toLowerCase() !== from.toLowerCase()) {
      return NextResponse.json({
        valid: false,
        error: `Signature mismatch: recovered ${recoveredSigner}, expected ${from}`,
      }, { status: 400 });
    }

    // Verify recipient matches expected
    if (expectedRecipient && to.toLowerCase() !== expectedRecipient.toLowerCase()) {
      return NextResponse.json({
        valid: false,
        error: `Recipient mismatch: payment is to ${to}, expected ${expectedRecipient}`,
      }, { status: 400 });
    }

    // Verify amount matches expected
    if (expectedAmount && BigInt(amount) < BigInt(expectedAmount)) {
      const humanAmount = (Number(amount) / 10 ** USDC_DECIMALS).toFixed(6);
      const humanExpected = (Number(expectedAmount) / 10 ** USDC_DECIMALS).toFixed(6);
      return NextResponse.json({
        valid: false,
        error: `Insufficient payment amount: ${humanAmount} USDC, expected at least ${humanExpected} USDC`,
      }, { status: 400 });
    }

    // Check USDC allowance and balance on-chain
    const [allowance, balance] = await Promise.all([
      publicClient.readContract({
        address: chainConfig.usdc,
        abi: USDC_ABI,
        functionName: 'allowance',
        args: [from as `0x${string}`, RELAYER_ADDRESS],
      }),
      publicClient.readContract({
        address: chainConfig.usdc,
        abi: USDC_ABI,
        functionName: 'balanceOf',
        args: [from as `0x${string}`],
      }),
    ]);

    const paymentAmount = BigInt(amount);

    if (allowance < paymentAmount) {
      const humanAllowance = (Number(allowance) / 10 ** USDC_DECIMALS).toFixed(6);
      const humanRequired = (Number(paymentAmount) / 10 ** USDC_DECIMALS).toFixed(6);
      return NextResponse.json({
        valid: false,
        error: `Insufficient USDC allowance on ${chainConfig.name}: ${humanAllowance} USDC approved, ${humanRequired} USDC required. Signer must approve the relayer (${RELAYER_ADDRESS}) first.`,
        allowance: allowance.toString(),
        required: paymentAmount.toString(),
      }, { status: 400 });
    }

    if (balance < paymentAmount) {
      const humanBalance = (Number(balance) / 10 ** USDC_DECIMALS).toFixed(6);
      const humanRequired = (Number(paymentAmount) / 10 ** USDC_DECIMALS).toFixed(6);
      return NextResponse.json({
        valid: false,
        error: `Insufficient USDC balance on ${chainConfig.name}: ${humanBalance} USDC available, ${humanRequired} USDC required.`,
        balance: balance.toString(),
        required: paymentAmount.toString(),
      }, { status: 400 });
    }

    // VF-03: Mark nonce as seen only after all validations pass
    markNonceSeen(nonce, chainId);

    // All checks passed
    return NextResponse.json({
      valid: true,
      signer: recoveredSigner,
      from,
      to,
      amount: paymentAmount.toString(),
      amountHuman: (Number(paymentAmount) / 10 ** USDC_DECIMALS).toFixed(6),
      allowance: allowance.toString(),
      balance: balance.toString(),
      deadline: new Date(Number(deadlineTs) * 1000).toISOString(),
      relayer: RELAYER_ADDRESS,
      chain: chainConfig.name,
      chainId,
    });

  } catch (err) {
    console.error('[x402/verify] Error:', err);
    return NextResponse.json({
      valid: false,
      error: 'Internal server error',
    }, { status: 500 });
  }
}
