/**
 * Mainnet ENS resolution (forward + reverse) via JSON-RPC.
 * Chain 138 has no native ENS — resolution always uses Ethereum mainnet.
 */

import { keccak256 } from 'js-sha3'

const MAINNET_RPC =
  (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_MAINNET_RPC_URL) ||
  'https://ethereum.publicnode.com'

const ENS_REGISTRY = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'

const CACHE_TTL_MS = 60 * 60 * 1000
const forwardCache = new Map<string, { value: string | null; at: number }>()
const reverseCache = new Map<string, { value: string | null; at: number }>()

function readCache<T>(map: Map<string, { value: T; at: number }>, key: string): T | undefined {
  const hit = map.get(key)
  if (!hit) return undefined
  if (Date.now() - hit.at > CACHE_TTL_MS) {
    map.delete(key)
    return undefined
  }
  return hit.value
}

function writeCache<T>(map: Map<string, { value: T; at: number }>, key: string, value: T) {
  map.set(key, { value, at: Date.now() })
}

async function rpcCall(method: string, params: unknown[]): Promise<string> {
  const response = await fetch(MAINNET_RPC, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
  })
  const data = await response.json()
  if (data.error) throw new Error(data.error.message || 'RPC error')
  return data.result as string
}

function strip0x(hex: string): string {
  return hex.startsWith('0x') ? hex.slice(2) : hex
}

function pad32(hex: string): string {
  return hex.padStart(64, '0')
}

// Minimal namehash (ENS EIP-137)
function namehash(name: string): string {
  let node = new Uint8Array(32)
  if (name) {
    const labels = name.split('.').reverse()
    for (const label of labels) {
      const labelHash = keccak256.arrayBuffer(new TextEncoder().encode(label))
      const combined = new Uint8Array(64)
      combined.set(node)
      combined.set(new Uint8Array(labelHash), 32)
      node = new Uint8Array(keccak256.arrayBuffer(combined))
    }
  }
  return `0x${Array.from(node, (b) => b.toString(16).padStart(2, '0')).join('')}`
}

async function getResolver(node: string): Promise<string | null> {
  const data = `0x0178b8bf${pad32(strip0x(node))}`
  const result = await rpcCall('eth_call', [{ to: ENS_REGISTRY, data }, 'latest'])
  if (!result || result === '0x' || result.length < 66) return null
  const resolver = `0x${result.slice(-40)}`
  if (resolver.toLowerCase() === ZERO_ADDRESS) return null
  return resolver
}

export async function resolveEnsAddress(name: string): Promise<string | null> {
  const normalized = name.trim().toLowerCase()
  if (!normalized.endsWith('.eth')) return null
  const cached = readCache(forwardCache, normalized)
  if (cached !== undefined) return cached

  try {
    const node = namehash(normalized)
    const resolver = await getResolver(node)
    if (!resolver) {
      writeCache(forwardCache, normalized, null)
      return null
    }
    const data = `0x3b3b57de${pad32(strip0x(node))}`
    const result = await rpcCall('eth_call', [{ to: resolver, data }, 'latest'])
    if (!result || result.length < 66) {
      writeCache(forwardCache, normalized, null)
      return null
    }
    const address = `0x${result.slice(-40)}`
    if (address.toLowerCase() === ZERO_ADDRESS) {
      writeCache(forwardCache, normalized, null)
      return null
    }
    writeCache(forwardCache, normalized, address)
    return address
  } catch {
    writeCache(forwardCache, normalized, null)
    return null
  }
}

export async function resolveEnsName(address: string): Promise<string | null> {
  const normalized = address.trim().toLowerCase()
  if (!/^0x[a-f0-9]{40}$/.test(normalized)) return null
  const cached = readCache(reverseCache, normalized)
  if (cached !== undefined) return cached

  try {
    const reverseNode = namehash(`${normalized.slice(2)}.addr.reverse`)
    const resolver = await getResolver(reverseNode)
    if (!resolver) {
      writeCache(reverseCache, normalized, null)
      return null
    }
    const data = `0x691f3431${pad32(strip0x(reverseNode))}`
    const result = await rpcCall('eth_call', [{ to: resolver, data }, 'latest'])
    if (!result || result === '0x' || result.length <= 2) {
      writeCache(reverseCache, normalized, null)
      return null
    }
    const hex = strip0x(result)
    const offset = parseInt(hex.slice(0, 64), 16) * 2
    const len = parseInt(hex.slice(offset, offset + 64), 16)
    const nameHex = hex.slice(offset + 64, offset + 64 + len * 2)
    const name = new TextDecoder().decode(
      new Uint8Array(nameHex.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) || []),
    )
    const value = name || null
    writeCache(reverseCache, normalized, value)
    return value
  } catch {
    writeCache(reverseCache, normalized, null)
    return null
  }
}

export function isEnsName(query: string): boolean {
  const trimmed = query.trim().toLowerCase()
  return trimmed.endsWith('.eth') && trimmed.length > 4 && !trimmed.includes(' ')
}
