/**
 * GET /api/hub/activity
 *
 * Real on-chain bond activity across all 4 chains. No fake "just now" timestamps.
 * Each event has a real block timestamp and an explorer link to the bond contract.
 *
 * Strategy:
 *   - For each chain, call nextBondId() on both bond contracts (partnership + accountability)
 *   - Walk back the last K bond IDs on each contract
 *   - For each bond ID, fetch getBond(id) → extract { aiAgent, stakeAmount, timestamp, active }
 *   - Return events sorted by timestamp desc, with native-token labels per chain
 *
 * No PII. Only public on-chain data: addresses, amounts, timestamps, contract addresses.
 *
 * Cached 60s in-memory.
 */

import { NextResponse } from 'next/server';

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

// Per-chain bond contracts. Source of truth: app/api/hub/stats/route.ts.
const CHAINS = {
  base: {
    rpcs: ['https://base-rpc.publicnode.com', 'https://mainnet.base.org'],
    partnership: '0x01C479F0c039fEC40c0Cf1c5C921bab457d57441',
    accountability: '0x6750D28865434344e04e1D0a6044394b726C3dfE',
    explorer: 'https://basescan.org',
    token: 'ETH',
    label: 'Base',
  },
  avalanche: {
    rpcs: ['https://api.avax.network/ext/bc/C/rpc', 'https://avalanche-c-chain-rpc.publicnode.com'],
    partnership: '0xDC8447c66fE9D9c7D54607A98346A15324b7985D',
    accountability: '0x376831fB2457E34559891c32bEb61c442053C066',
    explorer: 'https://snowtrace.io',
    token: 'AVAX',
    label: 'Avalanche',
  },
  arbitrum: {
    rpcs: ['https://arb1.arbitrum.io/rpc', 'https://arbitrum-one-rpc.publicnode.com'],
    partnership: '0xdB54B8925664816187646174bdBb6Ac658A55a5F',
    accountability: '0xef3A944f4d7bb376699C83A29d7Cb42C90D9B6F0',
    explorer: 'https://arbiscan.io',
    token: 'ETH',
    label: 'Arbitrum',
  },
  polygon: {
    rpcs: ['https://polygon-bor-rpc.publicnode.com'],
    partnership: '0x83dd216449B3F0574E39043ECFE275946fa492e9',
    accountability: '0xdB54B8925664816187646174bdBb6Ac658A55a5F',
    explorer: 'https://polygonscan.com',
    token: 'POL',
    label: 'Polygon',
  },
} as const;

type ChainKey = keyof typeof CHAINS;
type ContractKind = 'partnership' | 'accountability';

// Selectors (verified on-chain via app/api/agent/status/route.ts):
const NEXT_BOND_ID = '0xee53a423';
const GET_BOND = '0xd8fe7642';

// Bond struct word offsets (verified on-chain — see scripts that decoded
// real bonds on Base partnership + accountability contracts).
//
// Partnership bond struct (two participants):
//   word 1: id
//   word 2: human address
//   word 3: aiAgent address    ← attribution
//   word 5: stakeAmount (wei)
//   word 6: timestamp
//   word 9: active (bool)
//
// Accountability bond struct (single participant):
//   word 1: id
//   word 2: agent address       ← attribution
//   word 5: stakeAmount (wei)
//   word 6: timestamp
//   word 9: active (bool)
const PARTNERSHIP_AGENT_OFFSET = 3;
const ACCOUNTABILITY_AGENT_OFFSET = 2;
const STAKE_OFFSET = 5;
const TIMESTAMP_OFFSET = 6;
const ACTIVE_OFFSET = 9;

const HEX_WORD = 64; // hex chars per 32-byte word

async function rpcOne(url: string, method: string, params: unknown[]) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params }),
    signal: AbortSignal.timeout(8000),
  });
  return res.json();
}

async function rpc(rpcs: readonly string[], method: string, params: unknown[]): Promise<{ result?: string; error?: unknown }> {
  let lastError: unknown = null;
  for (const url of rpcs) {
    try {
      const json = await rpcOne(url, method, params);
      if (json && json.result && !json.error) return json;
      lastError = json?.error ?? { message: 'empty result' };
    } catch (e) {
      lastError = e;
    }
  }
  return { error: lastError };
}

async function getNextBondId(chain: ChainKey, kind: ContractKind): Promise<number> {
  const r = await rpc(CHAINS[chain].rpcs, 'eth_call', [
    { to: CHAINS[chain][kind], data: NEXT_BOND_ID },
    'latest',
  ]);
  if (!r.result || r.error) return 0;
  try {
    return parseInt(r.result, 16);
  } catch {
    return 0;
  }
}

interface BondEvent {
  id: string; // unique key for React
  chain: ChainKey;
  chainLabel: string;
  contract: ContractKind;
  bondId: number;
  aiAgent: string;        // 0x address that staked
  stakeAmount: number;    // in native token (ETH/AVAX/POL), already divided by 1e18
  token: string;          // 'ETH' | 'AVAX' | 'POL'
  timestamp: number;      // unix seconds
  active: boolean;
  explorerUrl: string;    // link to bond contract, anchored to this bond
}

function decodeBond(chain: ChainKey, kind: ContractKind, bondId: number, hex: string): BondEvent | null {
  // Strip leading 0x if present
  const data = hex.startsWith('0x') ? hex.slice(2) : hex;
  // Need at least 10 words to safely decode through ACTIVE_OFFSET (9)
  if (data.length < HEX_WORD * 10) return null;

  const wordAt = (idx: number) => data.slice(idx * HEX_WORD, (idx + 1) * HEX_WORD);

  // Agent address lives at a different offset depending on contract kind.
  const agentOffset = kind === 'partnership' ? PARTNERSHIP_AGENT_OFFSET : ACCOUNTABILITY_AGENT_OFFSET;
  const aiAgentRaw = wordAt(agentOffset);
  const aiAgent = '0x' + aiAgentRaw.slice(-40);
  // stakeAmount in wei (uint256)
  let stakeWei: bigint;
  try {
    stakeWei = BigInt('0x' + wordAt(STAKE_OFFSET));
  } catch {
    return null;
  }
  const stakeAmount = Number(stakeWei) / 1e18;
  // timestamp (uint256, but realistically fits in number)
  let timestamp = 0;
  try {
    timestamp = Number(BigInt('0x' + wordAt(TIMESTAMP_OFFSET)));
  } catch {
    timestamp = 0;
  }
  const activeWord = wordAt(ACTIVE_OFFSET);
  const active = /[1-9a-f]/i.test(activeWord);

  // Don't surface obviously zero/empty bond entries (e.g. uninitialized slots).
  if (aiAgent === '0x0000000000000000000000000000000000000000' || timestamp === 0) {
    return null;
  }

  return {
    id: `${chain}-${kind}-${bondId}`,
    chain,
    chainLabel: CHAINS[chain].label,
    contract: kind,
    bondId,
    aiAgent,
    stakeAmount,
    token: CHAINS[chain].token,
    timestamp,
    active,
    explorerUrl: `${CHAINS[chain].explorer}/address/${CHAINS[chain][kind]}`,
  };
}

async function fetchBond(chain: ChainKey, kind: ContractKind, bondId: number): Promise<BondEvent | null> {
  const idHex = bondId.toString(16).padStart(HEX_WORD, '0');
  const r = await rpc(CHAINS[chain].rpcs, 'eth_call', [
    { to: CHAINS[chain][kind], data: GET_BOND + idHex },
    'latest',
  ]);
  if (!r.result || r.error) return null;
  return decodeBond(chain, kind, bondId, r.result);
}

// How many recent bonds to surface per (chain, contract). Total max events =
// CHAINS * 2 * PER. With 4 chains, PER=3 yields up to 24 events; PER=4 yields 32.
const PER = 3;

let cache: { data: { events: BondEvent[]; timestamp: number }; ts: number } | null = null;
const CACHE_TTL = 60_000;

export async function GET() {
  try {
    if (cache && Date.now() - cache.ts < CACHE_TTL) {
      return NextResponse.json(cache.data, { headers: { ...CORS_HEADERS, 'Cache-Control': 'public, max-age=30, s-maxage=60' } });
    }

    const chainKeys = Object.keys(CHAINS) as ChainKey[];
    const kinds: ContractKind[] = ['partnership', 'accountability'];

    // Resolve last bond IDs per (chain, kind) in parallel.
    const headIds = await Promise.all(
      chainKeys.flatMap(chain =>
        kinds.map(async kind => ({ chain, kind, head: await getNextBondId(chain, kind) }))
      )
    );

    // Build target bond IDs: last PER bonds per contract (descending IDs).
    const targets: Array<{ chain: ChainKey; kind: ContractKind; bondId: number }> = [];
    for (const { chain, kind, head } of headIds) {
      // nextBondId returns the ID that will be assigned to the next bond.
      // Most recent existing bond id is head - 1. Bonds start at 1.
      const last = head - 1;
      for (let i = 0; i < PER; i++) {
        const id = last - i;
        if (id < 1) break;
        targets.push({ chain, kind, bondId: id });
      }
    }

    // Fetch all in parallel.
    const events = (
      await Promise.all(targets.map(t => fetchBond(t.chain, t.kind, t.bondId)))
    ).filter((e): e is BondEvent => e !== null);

    // Sort by timestamp desc.
    events.sort((a, b) => b.timestamp - a.timestamp);

    const data = { events, timestamp: Date.now() };
    cache = { data, ts: Date.now() };

    return NextResponse.json(data, {
      headers: {
        ...CORS_HEADERS,
        'Cache-Control': 'public, max-age=30, s-maxage=60',
      },
    });
  } catch {
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500, headers: CORS_HEADERS },
    );
  }
}
