/**
 * GET /api/protocol/recent
 *
 * Returns the most recent on-chain protocol activity across all 4 chains,
 * combined and sorted newest-first. No DB, no cache — every call queries
 * the live registries and bond contracts directly.
 *
 * Two activity sources:
 *   1. Agents — registered agents from all four ERC-8004 IdentityRegistry
 *      contracts, ordered by registeredAt descending.
 *   2. Bonds — partnership + accountability bonds from each chain's bond
 *      contracts, ordered by bondId descending (a proxy for creation time).
 *
 * Query params:
 *   ?limit=10        — max events to return (default 10, max 50)
 *
 * Response shape:
 * {
 *   events: [
 *     { kind: 'agent_registered', chain, address, name, vnsName, identityType, timestamp, txHashHint?, explorerUrl },
 *     { kind: 'bond_created',    chain, contract: 'partnership'|'accountability', bondId, explorerUrl }
 *   ],
 *   counts: { agents: 22, bonds: 36 },
 *   generatedAt: ISO8601
 * }
 */

import { NextResponse } from 'next/server';

export const dynamic = 'force-dynamic';
export const revalidate = 0;

const CORS_HEADERS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type',
};

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

const CHAINS = {
  base: {
    rpcs: ['https://base-rpc.publicnode.com', 'https://mainnet.base.org'],
    registry: '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'],
    registry: '0x7448057C95Fb8a8B974a566cdcc9Cd042166A3f8',
    partnership: '0xDC8447c66fE9D9c7D54607A98346A15324b7985D',
    accountability: '0x376831fB2457E34559891c32bEb61c442053C066',
    explorer: 'https://snowtrace.io',
  },
  arbitrum: {
    rpcs: ['https://arb1.arbitrum.io/rpc', 'https://arbitrum-one-rpc.publicnode.com'],
    registry: '0x83dd216449B3F0574E39043ECFE275946fa492e9',
    partnership: '0xdB54B8925664816187646174bdBb6Ac658A55a5F',
    accountability: '0xef3A944f4d7bb376699C83A29d7Cb42C90D9B6F0',
    explorer: 'https://arbiscan.io',
  },
  polygon: {
    rpcs: ['https://polygon-bor-rpc.publicnode.com'],
    registry: '0xD9bF6D92a1D9ee44a48c38481c046a819CBdf2ba',
    partnership: '0x83dd216449B3F0574E39043ECFE275946fa492e9',
    accountability: '0xdB54B8925664816187646174bdBb6Ac658A55a5F',
    explorer: 'https://polygonscan.com',
  },
} as const;

type ChainKey = keyof typeof CHAINS;
const CHAIN_KEYS = Object.keys(CHAINS) as ChainKey[];

const SEL_TOTAL_AGENTS = '0x3731a16f'; // getTotalAgents()
const SEL_REGISTERED_AGENT = '0xa7bd211c'; // registeredAgents(uint256)
const SEL_GET_AGENT = '0xfb3551ff'; // getAgent(address)
const SEL_NEXT_BOND_ID = '0xee53a423'; // nextBondId()

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

async function rpcCallOne(url: string, to: string, data: string): Promise<string | null> {
  try {
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_call', params: [{ to, data }, 'latest'] }),
      signal: AbortSignal.timeout(8000),
    });
    const json = await res.json();
    if (json.error || !json.result || json.result === '0x') return null;
    return json.result as string;
  } catch {
    return null;
  }
}

async function rpcCall(rpcs: readonly string[], to: string, data: string): Promise<string | null> {
  for (const url of rpcs) {
    const r = await rpcCallOne(url, to, data);
    if (r) return r;
  }
  return null;
}

/** Decode the getAgent(address) tuple. Returns the readable fields. */
function decodeGetAgent(hex: string): {
  agentURI: string;
  active: boolean;
  agentType: string;
  registeredAt: number;
} | null {
  try {
    const clean = hex.replace('0x', '');
    if (clean.length < 256) return null;
    const word = (i: number) => clean.slice(i * 64, (i + 1) * 64);
    const uriOffset = parseInt(word(0), 16) / 32;
    const active = parseInt(word(1), 16) === 1;
    const typeOffset = parseInt(word(2), 16) / 32;
    const registeredAt = parseInt(word(3), 16);
    const decodeStringAt = (offset: number) => {
      const len = parseInt(word(offset), 16);
      if (!len) return '';
      const bytesHex = clean.slice((offset + 1) * 64, (offset + 1) * 64 + len * 2);
      try {
        return Buffer.from(bytesHex, 'hex').toString('utf8');
      } catch {
        return '';
      }
    };
    return {
      agentURI: decodeStringAt(uriOffset),
      active,
      agentType: decodeStringAt(typeOffset),
      registeredAt,
    };
  } catch {
    return null;
  }
}

/** Best-effort name extraction from agentURI (handles JSON / vns / freeform). */
function extractName(agentURI: string, address: string): { name: string; vnsName?: string } {
  try {
    if (agentURI.startsWith('{')) {
      const j = JSON.parse(agentURI);
      const n = j.name || j.vns || j.fullName;
      if (n) return { name: n, vnsName: j.vns ?? (typeof n === 'string' && n.endsWith('.vns') ? n : undefined) };
    }
    if (agentURI.endsWith('.vns')) {
      return { name: agentURI.replace(/\.vns$/, ''), vnsName: agentURI };
    }
    if (agentURI && agentURI.length < 64) return { name: agentURI };
  } catch {
    /* fall through */
  }
  return { name: `agent-${address.slice(2, 8)}` };
}

type Event =
  | {
      kind: 'agent_registered';
      chain: ChainKey;
      address: string;
      name: string;
      vnsName?: string;
      identityType: string;
      timestamp: number;
      timestampISO: string;
      explorerUrl: string;
    }
  | {
      kind: 'bond_created';
      chain: ChainKey;
      contract: 'partnership' | 'accountability';
      bondId: number;
      explorerUrl: string;
    };

async function getAgentEventsForChain(chain: ChainKey, perChain: number): Promise<Event[]> {
  const cfg = CHAINS[chain];
  const totalHex = await rpcCall(cfg.rpcs, cfg.registry, SEL_TOTAL_AGENTS);
  const total = totalHex ? parseInt(totalHex, 16) : 0;
  if (!total) return [];

  // Fetch the last `perChain` indices (newest first, indices are 0-based).
  const startIdx = Math.max(0, total - perChain);
  const indices: number[] = [];
  for (let i = total - 1; i >= startIdx; i--) indices.push(i);

  // 1. registeredAgents(i) → address
  const addrResults = await Promise.all(
    indices.map((i) => rpcCall(cfg.rpcs, cfg.registry, SEL_REGISTERED_AGENT + encodeUint(i))),
  );
  const addrs = addrResults.map((r) => (r ? decodeAddress(r) : '')).filter((a) => !!a);

  // 2. getAgent(address) → metadata
  const metaResults = await Promise.all(
    addrs.map((a) => rpcCall(cfg.rpcs, cfg.registry, SEL_GET_AGENT + encodeAddress(a))),
  );

  const events: Event[] = [];
  for (let i = 0; i < addrs.length; i++) {
    const meta = metaResults[i] ? decodeGetAgent(metaResults[i]!) : null;
    if (!meta) continue;
    const { name, vnsName } = extractName(meta.agentURI, addrs[i]);
    events.push({
      kind: 'agent_registered',
      chain,
      address: addrs[i],
      name,
      vnsName,
      identityType: meta.agentType || 'unknown',
      timestamp: meta.registeredAt,
      timestampISO: new Date(meta.registeredAt * 1000).toISOString(),
      explorerUrl: `${cfg.explorer}/address/${addrs[i]}`,
    });
  }
  return events;
}

async function getBondEventsForChain(chain: ChainKey, perChain: number): Promise<Event[]> {
  const cfg = CHAINS[chain];
  const out: Event[] = [];
  for (const kind of ['partnership', 'accountability'] as const) {
    const nextHex = await rpcCall(cfg.rpcs, cfg[kind], SEL_NEXT_BOND_ID);
    const next = nextHex ? parseInt(nextHex, 16) : 1;
    const total = Math.max(0, next - 1);
    if (!total) continue;
    const startId = Math.max(1, total - perChain + 1);
    for (let bondId = total; bondId >= startId; bondId--) {
      out.push({
        kind: 'bond_created',
        chain,
        contract: kind,
        bondId,
        explorerUrl: `${cfg.explorer}/address/${cfg[kind]}`,
      });
    }
  }
  return out;
}

export async function GET(request: Request) {
  const url = new URL(request.url);
  const limitParam = parseInt(url.searchParams.get('limit') || '10', 10);
  const limit = Math.max(1, Math.min(50, isFinite(limitParam) ? limitParam : 10));
  // Per-chain fetch slightly larger than limit so the merge has enough headroom.
  const perChain = Math.min(20, limit);

  const [agentEvents, bondEvents] = await Promise.all([
    Promise.all(CHAIN_KEYS.map((c) => getAgentEventsForChain(c, perChain))).then((arr) => arr.flat()),
    Promise.all(CHAIN_KEYS.map((c) => getBondEventsForChain(c, perChain))).then((arr) => arr.flat()),
  ]);

  // Sort agents newest first by registeredAt; bonds newest first by bondId per chain.
  agentEvents.sort((a, b) => {
    if (a.kind !== 'agent_registered' || b.kind !== 'agent_registered') return 0;
    return (b.timestamp || 0) - (a.timestamp || 0);
  });
  bondEvents.sort((a, b) => {
    if (a.kind !== 'bond_created' || b.kind !== 'bond_created') return 0;
    return b.bondId - a.bondId;
  });

  // Interleave: take the freshest from either bucket up to limit. Agents have
  // wall-clock timestamps; bonds don't (bondId only). Keep them grouped:
  // first half agents, then bonds — but cap each at limit/2 minimum.
  const half = Math.ceil(limit / 2);
  const merged: Event[] = [
    ...agentEvents.slice(0, half),
    ...bondEvents.slice(0, limit - Math.min(half, agentEvents.length)),
  ].slice(0, limit);

  return NextResponse.json(
    {
      events: merged,
      counts: { agents: agentEvents.length, bonds: bondEvents.length },
      generatedAt: new Date().toISOString(),
    },
    { headers: CORS_HEADERS },
  );
}
