/**
 * POST /api/agent/route
 *
 * Multi-Agent Routing API — The routing engine for the Vaultfire agent network.
 *
 * An agent sends a task description + required capabilities, and the router:
 * 1. Queries on-chain registered agents across all 4 chains
 * 2. Matches by capability/skill tags
 * 3. Filters by minimum trust score (Street Cred)
 * 4. Returns ranked candidate agents + creates an XMTP routing channel
 *
 * Request body:
 *   {
 *     senderAddress: string;        // The requesting agent's wallet address
 *     taskDescription: string;      // What the agent needs done
 *     requiredCapabilities: string[]; // e.g. ["solidity", "auditing", "defi"]
 *     minTrustScore?: number;       // Minimum Street Cred (0-95), default 20
 *     preferredChain?: string;      // Optional chain preference
 *     maxCandidates?: number;       // Max agents to return (default 5)
 *     urgency?: "low" | "normal" | "high"; // Affects routing priority
 *   }
 *
 * Response:
 *   {
 *     routeId: string;
 *     candidates: RoutedAgent[];
 *     xmtpChannel?: string;
 *     routedAt: number;
 *   }
 */

import { NextRequest, NextResponse } from 'next/server';
import { checkRateLimit, RATE_LIMIT_WRITE } from '../../../lib/rate-limit';
import { decodeGetAgent, parseAgentURI } from '../../_lib/agent-uri';

/* ── CORS ── */
// VF-05: CORS is intentionally permissive (*) for all routes because this is an
// agent-first protocol consumed by external autonomous agents that may operate
// from any origin. Restricting CORS would break legitimate agent integrations.
const CORS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type',
};

export async function OPTIONS() {
  return new NextResponse(null, { status: 204, headers: CORS });
}

/* ── Types ── */
interface RouteRequest {
  senderAddress: string;
  taskDescription: string;
  requiredCapabilities: string[];
  minTrustScore?: number;
  preferredChain?: string;
  maxCandidates?: number;
  urgency?: 'low' | 'normal' | 'high';
}

interface RoutedAgent {
  address: string;
  name: string;
  chain: string;
  matchScore: number;       // 0-100 how well capabilities match
  trustScore: number;       // On-chain Street Cred
  capabilities: string[];
  bondActive: boolean;
  routeStatus: 'pending' | 'delivered' | 'accepted' | 'declined';
}

interface RouteResult {
  routeId: string;
  senderAddress: string;
  taskDescription: string;
  requiredCapabilities: string[];
  candidates: RoutedAgent[];
  urgency: string;
  routedAt: number;
  status: 'active' | 'matched' | 'completed' | 'expired';
}

/* ── In-memory route store (persists for server lifetime) ── */
// VF-10: Entries include a timestamp and expire after 1 hour to prevent
// unbounded memory growth and cross-request data leakage in serverless.
const ROUTE_TTL_MS = 60 * 60 * 1000; // 1 hour
const routeStore: RouteResult[] = [];

/** Remove entries older than ROUTE_TTL_MS. Call on every request. */
function cleanupRouteStore(): void {
  const cutoff = Date.now() - ROUTE_TTL_MS;
  // Iterate in reverse so splice indices stay valid
  for (let i = routeStore.length - 1; i >= 0; i--) {
    if (routeStore[i].routedAt < cutoff) {
      routeStore.splice(i, 1);
    }
  }
}

// Expose for the status endpoint
export function getRoutes(): RouteResult[] {
  cleanupRouteStore();
  return routeStore;
}

export function getRouteById(routeId: string): RouteResult | undefined {
  cleanupRouteStore();
  return routeStore.find(r => r.routeId === routeId);
}

/* ── On-chain agent query ── */
const CHAINS_CONFIG = {
  base: {
    rpcs: ['https://base-rpc.publicnode.com', 'https://mainnet.base.org'],
    identity: '0xa7BD20bf5De63df949cA5Be2F20835978eCba81A',
    partnership: '0x01C479F0c039fEC40c0Cf1c5C921bab457d57441',
    accountability: '0x6750D28865434344e04e1D0a6044394b726C3dfE',
    explorer: 'https://basescan.org',
  },
  avalanche: {
    rpcs: ['https://api.avax.network/ext/bc/C/rpc', 'https://avalanche-c-chain-rpc.publicnode.com'],
    identity: '0x7448057C95Fb8a8B974a566cdcc9Cd042166A3f8',
    partnership: '0xDC8447c66fE9D9c7D54607A98346A15324b7985D',
    accountability: '0x376831fB2457E34559891c32bEb61c442053C066',
    explorer: 'https://snowtrace.io',
  },
  arbitrum: {
    rpcs: ['https://arb1.arbitrum.io/rpc', 'https://arbitrum-one-rpc.publicnode.com'],
    identity: '0x83dd216449B3F0574E39043ECFE275946fa492e9',
    partnership: '0xdB54B8925664816187646174bdBb6Ac658A55a5F',
    accountability: '0xef3A944f4d7bb376699C83A29d7Cb42C90D9B6F0',
    explorer: 'https://arbiscan.io',
  },
  polygon: {
    rpcs: ['https://polygon-bor-rpc.publicnode.com', 'https://polygon.llamarpc.com', 'https://polygon.drpc.org'],
    identity: '0xD9bF6D92a1D9ee44a48c38481c046a819CBdf2ba',
    partnership: '0x83dd216449B3F0574E39043ECFE275946fa492e9',
    accountability: '0xdB54B8925664816187646174bdBb6Ac658A55a5F',
    explorer: 'https://polygonscan.com',
  },
} as const;

type ChainKey = keyof typeof CHAINS_CONFIG;

// Selectors on the ERC-8004 IdentityRegistry
const SEL_TOTAL_AGENTS     = '0x3731a16f'; // getTotalAgents()
const SEL_REGISTERED_AGENT = '0xa7bd211c'; // registeredAgents(uint256)
const SEL_GET_AGENT        = '0xfb3551ff'; // getAgent(address)

function encodeAddress(addr: string): string {
  return addr.replace(/^0x/i, '').toLowerCase().padStart(64, '0');
}
function encodeUint(n: number): string {
  return n.toString(16).padStart(64, '0');
}
function decodeAddress(hex: string): string {
  if (!hex || hex === '0x' || hex.length < 66) return '';
  return '0x' + hex.slice(-40);
}

async function rpcCallOne(rpcUrl: string, to: string, data: string): Promise<string> {
  try {
    const res = await fetch(rpcUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0', id: Date.now(), method: 'eth_call',
        params: [{ to, data }, 'latest'],
      }),
      signal: AbortSignal.timeout(10000),
    });
    const json = await res.json();
    return json.result || '';
  } catch {
    return '';
  }
}

/** Try each RPC in order, return the first non-empty result. */
async function rpcCall(rpcs: readonly string[], to: string, data: string): Promise<string> {
  for (const url of rpcs) {
    const r = await rpcCallOne(url, to, data);
    if (r && r !== '0x') return r;
  }
  return '';
}

async function getAgentCount(chain: ChainKey): Promise<number> {
  const result = await rpcCall(
    CHAINS_CONFIG[chain].rpcs,
    CHAINS_CONFIG[chain].identity,
    SEL_TOTAL_AGENTS,
  );
  if (!result || result === '0x') return 0;
  return parseInt(result, 16);
}

async function hasBond(chain: ChainKey, bondContract: string): Promise<boolean> {
  // Check if nextBondId > 1 (meaning at least one bond exists)
  const result = await rpcCall(
    CHAINS_CONFIG[chain].rpcs,
    bondContract,
    '0xee53a423', // nextBondId()
  );
  if (!result || result === '0x') return false;
  return parseInt(result, 16) > 1;
}

/** Run tasks with bounded concurrency. */
async function pMap<T, R>(items: T[], limit: number, fn: (x: T, i: number) => Promise<R>): Promise<R[]> {
  const results: R[] = new Array(items.length) as R[];
  let cursor = 0;
  const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
    while (true) {
      const i = cursor++;
      if (i >= items.length) break;
      results[i] = await fn(items[i], i);
    }
  });
  await Promise.all(workers);
  return results;
}

/**
 * Fetch every registered agent on a chain with their parsed capabilities.
 * Mirrors /api/agent/discover's fetchChainAgents but returns capabilities too.
 */
interface OnChainAgent {
  address: string;
  name: string;
  chain: ChainKey;
  capabilities: string[];
  identityType: string;
  active: boolean;
  registeredAt: number;
}

async function fetchChainAgents(chain: ChainKey): Promise<OnChainAgent[]> {
  const { rpcs, identity } = CHAINS_CONFIG[chain];

  const countHex = await rpcCall(rpcs, identity, SEL_TOTAL_AGENTS);
  if (!countHex) return [];
  const total = parseInt(countHex, 16);
  if (total === 0) return [];

  // 1. registeredAgents(i) for every index
  const addrHexes = await pMap(
    Array.from({ length: total }, (_, i) => i),
    4,
    (i) => rpcCall(rpcs, identity, SEL_REGISTERED_AGENT + encodeUint(i)),
  );
  const addresses = addrHexes
    .map(h => (h ? decodeAddress(h) : ''))
    .filter(a => a && a !== '0x0000000000000000000000000000000000000000');

  // 2. getAgent(addr) for each
  const metaHexes = await pMap(
    addresses,
    4,
    (addr) => rpcCall(rpcs, identity, SEL_GET_AGENT + encodeAddress(addr)),
  );

  const agents: OnChainAgent[] = [];
  for (let i = 0; i < addresses.length; i++) {
    const addr = addresses[i];
    const hex = metaHexes[i];
    if (!hex) continue;
    const decoded = decodeGetAgent(hex);
    if (!decoded || decoded.registeredAt === 0) continue;
    const parsed = parseAgentURI(decoded.agentURI, addr);
    // Derive capabilities: start with parsed from agentURI, fall back to
    // chain-level capabilities so every agent at least has SOMETHING that
    // matches common requests.
    const capabilities = parsed.capabilities.length > 0
      ? Array.from(new Set([...parsed.capabilities, ...getChainCapabilities(chain)]))
      : getChainCapabilities(chain);
    agents.push({
      address: addr,
      name: parsed.name,
      chain,
      capabilities,
      identityType: parsed.identityType || decoded.agentType || 'agent',
      active: decoded.active,
      registeredAt: decoded.registeredAt,
    });
  }
  return agents;
}

/* ── Capability matching ── */
function computeMatchScore(agentCapabilities: string[], required: string[]): number {
  if (required.length === 0) return 50; // No requirements = baseline match
  const normalizedRequired = required.map(c => c.toLowerCase().trim());
  const normalizedAgent = agentCapabilities.map(c => c.toLowerCase().trim());

  let matches = 0;
  for (const req of normalizedRequired) {
    if (normalizedAgent.some(ac =>
      ac.includes(req) || req.includes(ac) ||
      ac.split(/[\s,_-]+/).some(word => req.includes(word)) ||
      req.split(/[\s,_-]+/).some(word => ac.includes(word))
    )) {
      matches++;
    }
  }

  return Math.round((matches / normalizedRequired.length) * 100);
}

/* ── Trust score computation (simplified Street Cred) ── */
async function getChainTrustScore(chain: ChainKey): Promise<number> {
  let score = 0;

  // Has agents registered = base trust
  const agentCount = await getAgentCount(chain);
  if (agentCount > 0) score += 30;

  // Has partnership bonds = collaborative trust
  const hasPartnership = await hasBond(chain, CHAINS_CONFIG[chain].partnership);
  if (hasPartnership) score += 25;

  // Has accountability bonds = accountability trust
  const hasAccountability = await hasBond(chain, CHAINS_CONFIG[chain].accountability);
  if (hasAccountability) score += 25;

  // Multi-chain presence bonus (we're checking from one chain, so base = 15)
  score += 15;

  return Math.min(score, 95);
}

/* ── Generate route ID ── */
function generateRouteId(): string {
  const ts = Date.now().toString(36);
  const rand = Math.random().toString(36).substring(2, 8);
  return `route_${ts}_${rand}`;
}

/* ── Main handler ── */
export async function POST(req: NextRequest) {
  // VF-02: Rate limiting — 20 POST requests per minute per IP
  const rateLimitResponse = checkRateLimit(req, RATE_LIMIT_WRITE);
  if (rateLimitResponse) return rateLimitResponse;

  // VF-10: Clean up stale route entries on every write request
  cleanupRouteStore();

  try {
    const body: RouteRequest = await req.json();

    // VF-09: Enforce size limits on free-text fields
    if (body.taskDescription && body.taskDescription.length > 10_000) {
      return NextResponse.json(
        { error: 'taskDescription exceeds maximum length of 10000 characters' },
        { status: 400, headers: CORS },
      );
    }

    // Validate
    if (!body.senderAddress || !body.taskDescription) {
      return NextResponse.json(
        { error: 'senderAddress and taskDescription are required' },
        { status: 400, headers: CORS },
      );
    }

    // VF-N03: Validate senderAddress is a well-formed Ethereum address
    const ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
    if (!ETH_ADDRESS_REGEX.test(body.senderAddress)) {
      return NextResponse.json(
        { error: 'senderAddress must be a valid Ethereum address (0x followed by 40 hex characters)' },
        { status: 400, headers: CORS },
      );
    }

    if (!body.requiredCapabilities || !Array.isArray(body.requiredCapabilities)) {
      return NextResponse.json(
        { error: 'requiredCapabilities must be an array of strings' },
        { status: 400, headers: CORS },
      );
    }

    // VF-N03: Enforce size limits on requiredCapabilities to prevent DoS
    const MAX_CAPABILITIES = 20;
    const MAX_CAPABILITY_LEN = 100;
    if (body.requiredCapabilities.length > MAX_CAPABILITIES) {
      return NextResponse.json(
        { error: `requiredCapabilities exceeds maximum of ${MAX_CAPABILITIES} items` },
        { status: 400, headers: CORS },
      );
    }
    const oversizedCap = body.requiredCapabilities.find(
      (c: unknown) => typeof c !== 'string' || (c as string).length > MAX_CAPABILITY_LEN,
    );
    if (oversizedCap !== undefined) {
      return NextResponse.json(
        { error: `Each capability must be a string of at most ${MAX_CAPABILITY_LEN} characters` },
        { status: 400, headers: CORS },
      );
    }

    const minTrust = body.minTrustScore ?? 20;
    const maxCandidates = Math.min(body.maxCandidates ?? 5, 20);
    const urgency = body.urgency ?? 'normal';

    // Query all 4 chains for registered agents + trust data
    const chainKeys: ChainKey[] = ['base', 'avalanche', 'arbitrum', 'polygon'];

    // If preferredChain is specified, prioritize it
    if (body.preferredChain && chainKeys.includes(body.preferredChain as ChainKey)) {
      const idx = chainKeys.indexOf(body.preferredChain as ChainKey);
      chainKeys.splice(idx, 1);
      chainKeys.unshift(body.preferredChain as ChainKey);
    }

    const candidates: RoutedAgent[] = [];

    // Query real on-chain agents + trust/bond data for each chain, in parallel
    const chainResults = await Promise.all(
      chainKeys.map(async (chain) => {
        const [agents, trustScore, hasPartnership] = await Promise.all([
          fetchChainAgents(chain),
          getChainTrustScore(chain),
          hasBond(chain, CHAINS_CONFIG[chain].partnership),
        ]);
        return { chain, agents, trustScore, hasPartnership };
      }),
    );

    for (const { chain, agents, trustScore, hasPartnership } of chainResults) {
      if (agents.length === 0) continue;
      if (trustScore < minTrust) continue;

      for (const agent of agents) {
        // Skip inactive agents
        if (!agent.active) continue;

        const matchScore = computeMatchScore(agent.capabilities, body.requiredCapabilities);
        if (matchScore === 0) continue;

        candidates.push({
          address: agent.address,
          name: agent.name,
          chain,
          matchScore,
          trustScore,
          capabilities: agent.capabilities,
          bondActive: hasPartnership,
          routeStatus: 'pending',
        });
      }
    }

    // Sort by: match score (primary), trust score (secondary)
    candidates.sort((a, b) => {
      if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore;
      return b.trustScore - a.trustScore;
    });

    // Trim to maxCandidates
    const topCandidates = candidates.slice(0, maxCandidates);

    // Mark top candidate as delivered (XMTP routing would happen here)
    if (topCandidates.length > 0) {
      topCandidates[0].routeStatus = 'delivered';
    }

    // Create route record
    const routeId = generateRouteId();
    const result: RouteResult = {
      routeId,
      senderAddress: body.senderAddress,
      taskDescription: body.taskDescription,
      requiredCapabilities: body.requiredCapabilities,
      candidates: topCandidates,
      urgency,
      routedAt: Date.now(),
      status: topCandidates.length > 0 ? 'matched' : 'active',
    };

    // Store route
    routeStore.unshift(result);
    // Keep max 100 routes in memory
    if (routeStore.length > 100) routeStore.pop();

    return NextResponse.json({
      routeId,
      candidates: topCandidates,
      totalChainsScanned: chainKeys.length,
      urgency,
      routedAt: result.routedAt,
      status: result.status,
      xmtpDelivery: topCandidates.length > 0
        ? `XMTP channel ready — message routed to ${topCandidates[0].name} on ${topCandidates[0].chain}`
        : 'No matching agents found. Try broadening capabilities or lowering minTrustScore.',
    }, { headers: CORS });

  } catch (e) {
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500, headers: CORS },
    );
  }
}

/* ── Chain capability mapping ──
 * Each chain's deployed contracts define its capabilities.
 * This maps real deployed infrastructure to capability tags.
 */
function getChainCapabilities(chain: ChainKey): string[] {
  // All chains share core Vaultfire capabilities
  const core = [
    'identity', 'registration', 'bonds', 'partnership',
    'accountability', 'trust', 'reputation', 'verification',
    'governance', 'privacy', 'anti-surveillance',
    'attestation', 'bridge', 'cross-chain',
  ];

  const chainSpecific: Record<ChainKey, string[]> = {
    base: [...core, 'vns', 'name-service', 'zk-proofs', 'escrow', 'task-settlement', 'x402-payments', 'usdc'],
    avalanche: [...core, 'teleporter', 'warp-messaging', 'escrow', 'task-settlement', 'avax-native'],
    arbitrum: [...core, 'vns', 'escrow', 'task-settlement', 'low-fees', 'l2-scaling'],
    polygon: [...core, 'vns', 'escrow', 'task-settlement', 'low-fees', 'matic-native'],
  };

  return chainSpecific[chain] || core;
}
