/**
 * POST /api/x402/settle
 *
 * Executes an x402 payment settlement by calling USDC.transferFrom on the
 * specified chain.
 *
 * 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.
 *
 * Security model
 * ---------------
 * - The relayer (deployer) private key is loaded ONLY from process.env.DEPLOYER_PRIVATE_KEY.
 * - The key is NEVER written to any file, logged, or returned in any response.
 * - Rate limiting should be applied at the edge (Vercel middleware, Cloudflare WAF, etc.).
 * - The internal /api/x402/verify endpoint is called before any on-chain action.
 *
 * 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  // expected recipient address (validation)
 *     expectedAmount?:    string  // expected USDC base-unit amount (validation)
 *     taskId?:            string  // optional task ID for logging
 *     chainId?:           number  // chain ID (default: 8453 = Base)
 *   }
 *
 * Response (success):
 *   {
 *     settled: true,
 *     txHash: string,
 *     from: string,
 *     to: string,
 *     amount: string,
 *     amountHuman: string,
 *     blockNumber: string | null,
 *     explorerUrl: string,
 *     chain: string,
 *     chainId: number,
 *     taskId: string | null
 *   }
 *
 * Response (failure):
 *   { settled: false, error: string }
 *
 * Environment variables:
 *   DEPLOYER_PRIVATE_KEY  — relayer signing key (required)
 *   RELAYER_ADDRESS       — relayer public address (optional, for display)
 *   BASE_RPC_URL          — Base mainnet RPC endpoint (optional override)
 *   AVALANCHE_RPC_URL     — Avalanche mainnet RPC endpoint (optional override)
 *   ARBITRUM_RPC_URL      — Arbitrum mainnet RPC endpoint (optional override)
 *   POLYGON_RPC_URL       — Polygon mainnet RPC endpoint (optional override)
 *
 * Facilitator: https://x402.org/facilitator
 * Deployer:    0xfA15Ee28939B222B0448261A22156070f0A7813C
 */

import { NextRequest, NextResponse } from 'next/server';
import {
  createPublicClient,
  createWalletClient,
  http,
  parseAbi,
  type Hex,
  type Chain,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
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;

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

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

export async function POST(req: NextRequest) {
  // -------------------------------------------------------------------------
  // 1. Load relayer key from environment — NEVER from request body or files
  // -------------------------------------------------------------------------
  const deployerKey = process.env['DEPLOYER_PRIVATE_KEY'];
  if (!deployerKey) {
    console.error('[x402/settle] DEPLOYER_PRIVATE_KEY is not set in environment');
    return NextResponse.json(
      {
        settled: false,
        error: 'Settlement service not configured — DEPLOYER_PRIVATE_KEY missing from environment',
      },
      { status: 503 },
    );
  }

  // Normalize key format (ensure 0x prefix)
  const normalizedKey = (deployerKey.startsWith('0x') ? deployerKey : `0x${deployerKey}`) as Hex;

  // -------------------------------------------------------------------------
  // 2. Parse and validate request body
  // -------------------------------------------------------------------------
  let body: {
    paymentHeader: string;
    expectedRecipient?: string;
    expectedAmount?: string;
    taskId?: string;
    chainId?: number;
  };

  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ settled: false, error: 'Invalid JSON body' }, { status: 400 });
  }

  const { paymentHeader, expectedRecipient, expectedAmount, taskId, chainId: requestedChainId } = body;

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

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

  // -------------------------------------------------------------------------
  // 3. Decode 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(
      { settled: false, error: 'Invalid paymentHeader encoding' },
      { status: 400 },
    );
  }

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

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

  // -------------------------------------------------------------------------
  // 4. Verify payment authorization before executing (call verify endpoint)
  // -------------------------------------------------------------------------
  const verifyPayload: Record<string, string | number> = { paymentHeader, chainId };
  if (expectedRecipient) verifyPayload['expectedRecipient'] = expectedRecipient;
  if (expectedAmount) verifyPayload['expectedAmount'] = expectedAmount;

  const verifyResponse = await fetch(new URL('/api/x402/verify', req.url).toString(), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(verifyPayload),
  });

  const verifyResult = await verifyResponse.json();

  if (!verifyResult.valid) {
    return NextResponse.json(
      {
        settled: false,
        error: `Payment verification failed: ${verifyResult.error}`,
      },
      { status: 400 },
    );
  }

  // -------------------------------------------------------------------------
  // 5. Belt-and-suspenders: re-check deadline
  // -------------------------------------------------------------------------
  const deadlineTs = BigInt(deadline);
  const nowTs = BigInt(Math.floor(Date.now() / 1000));
  if (deadlineTs < nowTs) {
    return NextResponse.json(
      {
        settled: false,
        error: `Payment authorization expired at ${new Date(Number(deadlineTs) * 1000).toISOString()}`,
      },
      { status: 400 },
    );
  }

  // -------------------------------------------------------------------------
  // 6. Create signing account from relayer key
  // -------------------------------------------------------------------------
  let account;
  try {
    account = privateKeyToAccount(normalizedKey);
  } catch {
    console.error('[x402/settle] Invalid DEPLOYER_PRIVATE_KEY format');
    return NextResponse.json(
      { settled: false, error: 'Settlement service configuration error' },
      { status: 503 },
    );
  }

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

  const walletClient = createWalletClient({
    account,
    chain: chainConfig.chain,
    transport: http(chainConfig.rpc),
  });

  const paymentAmount = BigInt(amount);

  // -------------------------------------------------------------------------
  // 7. Simulate USDC.transferFrom to catch reverts before spending gas
  // -------------------------------------------------------------------------
  try {
    await publicClient.simulateContract({
      address: chainConfig.usdc,
      abi: USDC_ABI,
      functionName: 'transferFrom',
      args: [from as `0x${string}`, to as `0x${string}`, paymentAmount],
      account: account.address,
    });
  } catch (simErr) {
    const msg = simErr instanceof Error ? simErr.message : String(simErr);
    console.error(`[x402/settle] Simulation failed on ${chainConfig.name}:`, msg);
    return NextResponse.json(
      { settled: false, error: `Transaction would revert on ${chainConfig.name}: ${msg}` },
      { status: 400 },
    );
  }

  // -------------------------------------------------------------------------
  // 8. Execute USDC.transferFrom on the target chain
  // -------------------------------------------------------------------------
  let txHash: Hex;
  try {
    txHash = await walletClient.writeContract({
      address: chainConfig.usdc,
      abi: USDC_ABI,
      functionName: 'transferFrom',
      args: [from as `0x${string}`, to as `0x${string}`, paymentAmount],
    });
  } catch (txErr) {
    const msg = txErr instanceof Error ? txErr.message : String(txErr);
    console.error(`[x402/settle] Transaction failed on ${chainConfig.name}:`, msg);
    return NextResponse.json(
      { settled: false, error: `Transaction failed on ${chainConfig.name}: ${msg}` },
      { status: 500 },
    );
  }

  // -------------------------------------------------------------------------
  // 9. Wait for receipt and return result
  // -------------------------------------------------------------------------
  let blockNumber: bigint | undefined;
  try {
    const receipt = await publicClient.waitForTransactionReceipt({
      hash: txHash,
      timeout: 30_000,
    });
    blockNumber = receipt.blockNumber;

    if (receipt.status === 'reverted') {
      return NextResponse.json(
        {
          settled: false,
          error: `Transaction was mined but reverted on ${chainConfig.name}`,
          txHash,
          explorerUrl: `${chainConfig.explorer}/tx/${txHash}`,
        },
        { status: 500 },
      );
    }
  } catch {
    // Receipt timeout — transaction was submitted; return hash for client to poll
    console.warn(`[x402/settle] Receipt timeout for tx on ${chainConfig.name}:`, txHash);
  }

  const humanAmount = (Number(paymentAmount) / 10 ** USDC_DECIMALS).toFixed(6);

  console.log(
    `[x402/settle] Settled on ${chainConfig.name}: ${humanAmount} USDC from ${from} to ${to} | tx: ${txHash}` +
      (taskId ? ` | task: ${taskId}` : ''),
  );

  return NextResponse.json({
    settled: true,
    txHash,
    from,
    to,
    amount: paymentAmount.toString(),
    amountHuman: humanAmount,
    blockNumber: blockNumber?.toString() ?? null,
    explorerUrl: `${chainConfig.explorer}/tx/${txHash}`,
    chain: chainConfig.name,
    chainId,
    taskId: taskId ?? null,
  });
}
