/**
 * POST /api/x402/verify
 *
 * Verifies an x402 payment authorization header.
 *
 * 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
 *
 * EIP-712 Domain:
 *   name: "x402Payment"
 *   version: "1"
 *   chainId: 8453 (Base mainnet)
 *   verifyingContract: USDC on Base (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)
 *
 * EIP-712 Types:
 *   PaymentAuthorization {
 *     from: address
 *     to: address
 *     amount: uint256
 *     nonce: bytes32
 *     deadline: uint256
 *   }
 *
 * 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)
 *   }
 *
 * Response:
 *   { valid: true, signer: string, allowance: string, balance: string }
 *   { valid: false, error: string }
 */

import { NextRequest, NextResponse } from 'next/server';
import {
  createPublicClient,
  http,
  recoverTypedDataAddress,
  parseAbi,
  type Hex,
} from 'viem';
import { base } from 'viem/chains';

// USDC on Base mainnet
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const;
const USDC_DECIMALS = 6;
const BASE_CHAIN_ID = 8453;

// Relayer address — the deployer key that will execute transferFrom
// This must match the address of DEPLOYER_PRIVATE_KEY
const RELAYER_ADDRESS = (process.env.RELAYER_ADDRESS || process.env.DEPLOYER_ADDRESS || '0xfA15Ee28939B222B0448261A22156070f0A7813C') as `0x${string}`;

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

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

// EIP-712 domain for x402 payment authorization
const X402_DOMAIN = {
  name: 'x402Payment',
  version: '1',
  chainId: BASE_CHAIN_ID,
  verifyingContract: USDC_ADDRESS,
} as const;

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

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    const { paymentHeader, expectedRecipient, expectedAmount } = body;

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

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

    // 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: ${err instanceof Error ? err.message : 'unknown error'}` }, { 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: the signer must have approved the relayer for at least the payment amount
    const [allowance, balance] = await Promise.all([
      publicClient.readContract({
        address: USDC_ADDRESS,
        abi: USDC_ABI,
        functionName: 'allowance',
        args: [from as `0x${string}`, RELAYER_ADDRESS],
      }),
      publicClient.readContract({
        address: USDC_ADDRESS,
        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: ${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: ${humanBalance} USDC available, ${humanRequired} USDC required.`,
        balance: balance.toString(),
        required: paymentAmount.toString(),
      }, { status: 400 });
    }

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

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