/**
 * GET /api/agent/vns?name=agent-name&chain=base  — Resolve a .vns name to an address
 * GET /api/agent/vns?address=0x...&chain=base     — Reverse-resolve address to .vns name
 * GET /api/agent/vns?chain=base                   — Info about the VNS contract on a chain
 *
 * VaultfireNameService — Agent-only naming system (.vns).
 *
 * Resolution strategy:
 *   1) Query the VaultfireNameService contract (Base, Arbitrum, Polygon only).
 *   2) If that returns no match, fall back to the ERC-8004 identity registry, which
 *      stores the agent's agentURI (a JSON blob containing `name`) in its on-chain
 *      identity record. This is the source of the live .vns names shown on the
 *      leaderboard.
 *
 * Avalanche has no standalone VNS contract; only identity-registry fallback applies.
 *
 * SECURITY: This route is read-only and does not handle private keys.
 */

import { NextRequest, NextResponse } from 'next/server';
import { ethers } from 'ethers';
import { resolveChain, SUPPORTED_CHAINS } from '../../_lib/resolve-chain';
import { withFailover, RPC_FALLBACKS } from '../../_lib/rpc-retry';
import { hexToUtf8 } from '../../_lib/agent-uri';

const CORS = {
  '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 });
}

/** VNS contract per chain (source of truth: /api/contracts). */
const VNS: Record<string, string | null> = {
  base:      '0xA9e6c2c0a731F1f56F6720Dfac2eB1440Ab9453a',
  arbitrum:  '0x7448057C95Fb8a8B974a566cdcc9Cd042166A3f8',
  polygon:   '0x7448057C95Fb8a8B974a566cdcc9Cd042166A3f8',
  avalanche: null,
};

/** ERC-8004 identity registry per chain (source of truth). */
const IDENTITY: Record<string, string> = {
  base:      '0xa7BD20bf5De63df949cA5Be2F20835978eCba81A',
  avalanche: '0x7448057C95Fb8a8B974a566cdcc9Cd042166A3f8',
  arbitrum:  '0x83dd216449B3F0574E39043ECFE275946fa492e9',
  polygon:   '0xD9bF6D92a1D9ee44a48c38481c046a819CBdf2ba',
};

const CHAIN_IDS: Record<string, number> = { base: 8453, avalanche: 43114, arbitrum: 42161, polygon: 137 };
const EXPLORERS: Record<string, string> = {
  base: 'https://basescan.org',
  avalanche: 'https://snowtrace.io',
  arbitrum: 'https://arbiscan.io',
  polygon: 'https://polygonscan.com',
};

const VNS_ABI = [
  'function resolveNameToAddress(string name) view returns (address)',
  'function resolveAddressToName(address addr) view returns (string)',
  'function isNameAvailable(string name) view returns (bool)',
];

// ── Raw JSON-RPC helpers (same approach as /api/agent/discover) ──────────
// The identity registry's getAgent() returns:
//   (string agentURI, bool active, string agentType, uint256 registeredAt)
// We decode manually since ethers' ABI decoder can be finicky with the
// ERC-8004 tuple layout across different compiler versions.

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

function encodeUint(n: number): string {
  return n.toString(16).padStart(64, '0');
}
function encodeAddressArg(addr: string): string {
  return addr.replace('0x', '').toLowerCase().padStart(64, '0');
}

async function rpcCallOne(url: string, data: string, to: 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(10000),
    });
    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(chain: string, data: string, to: string): Promise<string | null> {
  const rpcs = RPC_FALLBACKS[chain];
  if (!rpcs) return null;
  for (const url of rpcs) {
    const result = await rpcCallOne(url, data, to);
    if (result) return result;
  }
  return null;
}

function decodeAddressWord(hex: string): string {
  const clean = hex.replace('0x', '');
  if (clean.length < 64) return '';
  return '0x' + clean.slice(-40);
}

/**
 * Decode getAgent tuple: (string agentURI, bool active, string agentType, uint256 registeredAt)
 */
function decodeGetAgent(hex: string): {
  agentURI: string;
  active: boolean;
  agentType: string;
  registeredAt: number;
} | null {
  try {
    const data = hex.replace('0x', '');
    if (data.length < 256) return null;

    const uriOffset = parseInt(data.slice(0, 64), 16) * 2;
    const active = parseInt(data.slice(64, 128), 16) === 1;
    const typeOffset = parseInt(data.slice(128, 192), 16) * 2;
    const registeredAt = parseInt(data.slice(192, 256), 16);

    const uriLen = parseInt(data.slice(uriOffset, uriOffset + 64), 16);
    const uriHex = data.slice(uriOffset + 64, uriOffset + 64 + uriLen * 2);
    const agentURI = hexToUtf8(uriHex);

    const typeLen = parseInt(data.slice(typeOffset, typeOffset + 64), 16);
    const typeHex = data.slice(typeOffset + 64, typeOffset + 64 + typeLen * 2);
    const agentType = hexToUtf8(typeHex);

    return { agentURI, active, agentType, registeredAt };
  } catch {
    return null;
  }
}

/** Parse the agentURI JSON blob and extract the .vns name (matches discover logic). */
function nameFromAgentURI(uri: string, address: string): string {
  if (!uri) return `agent-${address.slice(2, 8).toLowerCase()}`;
  try {
    const parsed = JSON.parse(uri);
    const rawName = parsed.name || '';
    const cleanName = rawName.replace(/\.vns$/, '').toLowerCase().trim();
    if (cleanName && /^[a-z0-9][a-z0-9-]*$/.test(cleanName)) return cleanName;
  } catch { /* not JSON */ }

  // URL or plain string
  if (uri.startsWith('http://') || uri.startsWith('https://')) {
    try {
      const url = new URL(uri);
      const parts = url.pathname.split('/').filter(Boolean);
      const last = parts[parts.length - 1] || '';
      const extracted = last.replace(/\.vns$/, '').toLowerCase().trim();
      if (extracted && /^[a-z0-9][a-z0-9-]*$/.test(extracted)) return extracted;
    } catch { /* fall through */ }
  }

  const cleaned = uri.replace(/\.vns$/, '').toLowerCase().trim();
  if (/^[a-z0-9][a-z0-9-]*$/.test(cleaned)) return cleaned;
  return `agent-${address.slice(2, 8).toLowerCase()}`;
}

// ── VNS contract (ethers-based; simple ABI, known-good) ──────────────────

async function vnsResolveName(chain: string, name: string): Promise<string | null> {
  const vns = VNS[chain];
  if (!vns) return null;
  try {
    const addr = await withFailover(chain, async (provider) => {
      const c = new ethers.Contract(vns, VNS_ABI, provider);
      return c.resolveNameToAddress(name);
    });
    if (addr && addr !== ethers.ZeroAddress) return addr;
  } catch {
    /* fall through */
  }
  return null;
}

async function vnsReverseLookup(chain: string, address: string): Promise<string | null> {
  const vns = VNS[chain];
  if (!vns) return null;
  try {
    const name = await withFailover(chain, async (provider) => {
      const c = new ethers.Contract(vns, VNS_ABI, provider);
      return c.resolveAddressToName(address);
    });
    if (name && name.length > 0) return name;
  } catch {
    /* fall through */
  }
  return null;
}

// ── Identity-registry fallback (raw JSON-RPC, proven 4-field ABI) ────────

async function identityResolveName(chain: string, name: string): Promise<string | null> {
  const reg = IDENTITY[chain];
  if (!reg) return null;
  const target = name.replace(/\.vns$/i, '').toLowerCase().trim();

  const countHex = await rpcCall(chain, SEL_TOTAL_AGENTS, reg);
  if (!countHex) return null;
  const total = parseInt(countHex, 16);
  if (total === 0) return null;

  for (let i = 0; i < total; i++) {
    const addrHex = await rpcCall(chain, SEL_REGISTERED_AGENT + encodeUint(i), reg);
    if (!addrHex) continue;
    const addr = decodeAddressWord(addrHex);
    if (!addr || addr === '0x0000000000000000000000000000000000000000') continue;

    const metaHex = await rpcCall(chain, SEL_GET_AGENT + encodeAddressArg(addr), reg);
    if (!metaHex) continue;
    const decoded = decodeGetAgent(metaHex);
    if (!decoded) continue;

    const candidate = nameFromAgentURI(decoded.agentURI, addr);
    if (candidate === target) return addr;
  }
  return null;
}

async function identityReverseLookup(chain: string, address: string): Promise<string | null> {
  const reg = IDENTITY[chain];
  if (!reg) return null;
  const metaHex = await rpcCall(chain, SEL_GET_AGENT + encodeAddressArg(address), reg);
  if (!metaHex) return null;
  const decoded = decodeGetAgent(metaHex);
  if (!decoded || decoded.registeredAt === 0) return null;
  return nameFromAgentURI(decoded.agentURI, address);
}

export async function GET(request: NextRequest) {
  const params = request.nextUrl.searchParams;
  const chain = resolveChain(params.get('chain'));
  const rawName = params.get('name');
  const rawAddress = params.get('address');

  if (!chain) {
    return NextResponse.json(
      { error: `Unsupported chain. Use one of: ${SUPPORTED_CHAINS.join(', ')}` },
      { status: 400, headers: CORS },
    );
  }

  const vnsAddr = VNS[chain];
  const info = {
    chain,
    chainId: CHAIN_IDS[chain],
    vnsContract: vnsAddr,
    identityRegistry: IDENTITY[chain],
    explorerUrl: vnsAddr ? `${EXPLORERS[chain]}/address/${vnsAddr}` : null,
    note:
      chain === 'avalanche'
        ? 'Avalanche has no VaultfireNameService contract. Names are resolved via the ERC-8004 identity registry.'
        : 'Names live in the identity registry agentURI JSON. VNS contract is deployed for future direct registrations.',
    abi: {
      vns: {
        resolveNameToAddress: 'resolveNameToAddress(string name) → address',
        resolveAddressToName: 'resolveAddressToName(address addr) → string',
        isNameAvailable:      'isNameAvailable(string name) → bool',
        registerName:         'registerName(string name, address addr)  // write — not callable via this endpoint',
      },
      identityRegistry: {
        getAgent:             'getAgent(address) → (string agentURI, bool active, string agentType, uint256 registeredAt)',
      },
    },
  };

  // ───── Resolve name → address ─────────────────────────────────────────
  if (rawName !== null) {
    const name = rawName.trim().replace(/\.vns$/i, '');
    if (!name) {
      return NextResponse.json({ error: 'Invalid name — must be non-empty' }, { status: 400, headers: CORS });
    }
    const vnsHit = await vnsResolveName(chain, name);
    const identityHit = vnsHit ? null : await identityResolveName(chain, name);
    const resolved = vnsHit ?? identityHit;
    return NextResponse.json(
      {
        ...info,
        query: { type: 'resolve', name },
        resolved,
        resolvedVia: vnsHit ? 'vns' : identityHit ? 'identity-registry' : null,
      },
      { headers: CORS },
    );
  }

  // ───── Reverse: address → name ────────────────────────────────────────
  if (rawAddress !== null) {
    if (!ethers.isAddress(rawAddress)) {
      return NextResponse.json({ error: 'Invalid address' }, { status: 400, headers: CORS });
    }
    const vnsHit = await vnsReverseLookup(chain, rawAddress);
    const identityHit = vnsHit ? null : await identityReverseLookup(chain, rawAddress);
    const resolved = vnsHit ?? identityHit;
    return NextResponse.json(
      {
        ...info,
        query: { type: 'reverse', address: rawAddress },
        resolved,
        resolvedVia: vnsHit ? 'vns' : identityHit ? 'identity-registry' : null,
      },
      { headers: CORS },
    );
  }

  // ───── Info only (no name/address) ────────────────────────────────────
  return NextResponse.json(info, { headers: CORS });
}
