/**
 * 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.
 *
 * This is a Next.js App Router API route example. It can be adapted for
 * Express, Fastify, Bun, or any Node.js-compatible framework.
 *
 * Protocol
 * --------
 * The x402 protocol works as follows:
 *   1. Client signs an EIP-712 typed data structure authorizing USDC transfer.
 *   2. Client sends the signed authorization in the X-Payment header
 *      (base64-encoded JSON containing signature, from, to, amount, nonce, deadline).
 *   3. This endpoint recovers the signer from the EIP-712 signature, checks
 *      payment parameters, and verifies 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 payment authorization
 *     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: string,
 *     from: string,
 *     to: string,
 *     amount: string,
 *     amountHuman: string,
 *     allowance: string,
 *     balance: string,
 *     deadline: string,
 *     relayer: string,
 *     chain: string,
 *     chainId: number
 *   }
 *
 * Response (invalid):
 *   { valid: false, error: string }
 *
 * Facilitator: https://x402.org/facilitator
 * Deployer:    0xfA15Ee28939B222B0448261A22156070f0A7813C
 */

import { NextRequest, NextResponse } from 'next/server';
import {
  createPublicClient,
  http,
  recoverTypedDataAddress,
  parseAbi,
  type Hex,
  type Chain,
} from 'viem';
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-rpc.com',
    name: 'Polygon',
    explorer: 'https://polygonscan.com',
  },
};

const DEFAULT_CHAIN_ID = 8453;

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const USDC_DECIMALS = 6;

/**
 * Relayer address — the deployer key that will execute transferFrom.
 * Must match the address of DEPLOYER_PRIVATE_KEY in your environment.
 * Default: Vaultfire deployer address.
 */
const RELAYER_ADDRESS = (
  process.env['RELAYER_ADDRESS'] ||
  process.env['DEPLOYER_ADDRESS'] ||
  '0xfA15Ee28939B222B0448261A22156070f0A7813C'
) 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;

/**
 * Build the EIP-712 domain for a given chain.
 */
function getX402Domain(chainId: number, usdcAddress: `0x${string}`) {
  return {
    name: 'x402Payment',
    version: '1',
    chainId,
    verifyingContract: usdcAddress,
  } as const;
}

// ---------------------------------------------------------------------------
// Route handler
// ---------------------------------------------------------------------------

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    const {
      paymentHeader,
      expectedRecipient,
      expectedAmount,
      chainId: requestedChainId,
    } = body as {
      paymentHeader: string;
      expectedRecipient?: string;
      expectedAmount?: string;
      chainId?: number;
    };

    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);

    // -----------------------------------------------------------------------
    // 1. 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;

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

    // -----------------------------------------------------------------------
    // 3. 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 },
      );
    }

    // -----------------------------------------------------------------------
    // 4. 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: ${err instanceof Error ? err.message : 'unknown error'}`,
        },
        { status: 400 },
      );
    }

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

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

    // -----------------------------------------------------------------------
    // 7. Verify amount meets minimum (if provided)
    // -----------------------------------------------------------------------
    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: ${humanAmount} USDC provided, ${humanExpected} USDC required`,
        },
        { status: 400 },
      );
    }

    // -----------------------------------------------------------------------
    // 8. 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} approved, ${humanRequired} 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} available, ${humanRequired} required.`,
          balance: balance.toString(),
          required: paymentAmount.toString(),
        },
        { status: 400 },
      );
    }

    // -----------------------------------------------------------------------
    // 9. All checks passed — return valid response
    // -----------------------------------------------------------------------
    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: err instanceof Error ? err.message : 'Internal server error',
      },
      { status: 500 },
    );
  }
}
