import { ApiResponse } from './client'
import { fetchBlockscoutJson, normalizeTransaction, type BlockscoutInternalTransaction } from './blockscout'
import { resolveExplorerApiBase } from '../../../libs/frontend-api-client/api-base'

export interface Transaction {
  chain_id: number
  hash: string
  block_number: number
  block_hash: string
  transaction_index: number
  from_address: string
  to_address?: string
  value: string
  gas_price?: number
  max_fee_per_gas?: number
  max_priority_fee_per_gas?: number
  gas_limit: number
  gas_used?: number
  status?: number
  input_data?: string
  contract_address?: string
  created_at: string
  fee?: string
  method?: string
  revert_reason?: string
  transaction_tag?: string
  decoded_input?: {
    method_call?: string
    method_id?: string
    parameters: Array<{
      name?: string
      type?: string
      value?: unknown
    }>
  }
  token_transfers?: TransactionTokenTransfer[]
}

export interface TransactionTokenTransfer {
  block_number?: number
  from_address: string
  from_label?: string
  to_address: string
  to_label?: string
  token_address: string
  token_name?: string
  token_symbol?: string
  token_decimals: number
  amount: string
  type?: string
  timestamp?: string
}

export interface TransactionInternalCall {
  from_address: string
  from_label?: string
  to_address?: string
  to_label?: string
  contract_address?: string
  contract_label?: string
  type?: string
  value: string
  success?: boolean
  error?: string
  result?: string
  timestamp?: string
}

export interface TransactionLookupDiagnostic {
  checked_hash: string
  chain_id: number
  explorer_indexed: boolean
  rpc_transaction_found: boolean
  rpc_receipt_found: boolean
  latest_block_number?: number
  rpc_url?: string
}

const CHAIN_138_PUBLIC_RPC_URL = 'https://rpc-http-pub.d-bis.org'

function resolvePublicRpcUrl(chainId: number): string | null {
  if (chainId !== 138) {
    return null
  }

  const envValue = (process.env.NEXT_PUBLIC_CHAIN_138_RPC_URL || '').trim()
  return envValue || CHAIN_138_PUBLIC_RPC_URL
}

async function fetchJsonWithStatus<T>(input: RequestInfo | URL, init?: RequestInit): Promise<{ ok: boolean; status: number; data: T | null }> {
  const response = await fetch(input, init)
  let data: T | null = null
  try {
    data = (await response.json()) as T
  } catch {
    data = null
  }
  return { ok: response.ok, status: response.status, data }
}

async function fetchRpcResult<T>(rpcUrl: string, method: string, params: unknown[]): Promise<T | null> {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 6000)
  try {
    const response = await fetch(rpcUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 1,
        method,
        params,
      }),
      signal: controller.signal,
    })
    if (!response.ok) {
      return null
    }
    const payload = (await response.json()) as { result?: T | null }
    return payload.result ?? null
  } catch {
    return null
  } finally {
    clearTimeout(timeout)
  }
}

async function diagnoseMissingTransaction(chainId: number, hash: string): Promise<TransactionLookupDiagnostic> {
  const diagnostic: TransactionLookupDiagnostic = {
    checked_hash: hash,
    chain_id: chainId,
    explorer_indexed: false,
    rpc_transaction_found: false,
    rpc_receipt_found: false,
  }

  const explorerLookup = await fetchJsonWithStatus<unknown>(`${resolveExplorerApiBase()}/api/v2/transactions/${hash}`)
  diagnostic.explorer_indexed = explorerLookup.ok

  const rpcUrl = resolvePublicRpcUrl(chainId)
  if (!rpcUrl) {
    return diagnostic
  }

  diagnostic.rpc_url = rpcUrl

  const [transactionResult, receiptResult, latestBlockHex] = await Promise.all([
    fetchRpcResult<string | Record<string, unknown>>(rpcUrl, 'eth_getTransactionByHash', [hash]),
    fetchRpcResult<string | Record<string, unknown>>(rpcUrl, 'eth_getTransactionReceipt', [hash]),
    fetchRpcResult<string>(rpcUrl, 'eth_blockNumber', []),
  ])

  diagnostic.rpc_transaction_found = transactionResult != null
  diagnostic.rpc_receipt_found = receiptResult != null

  if (typeof latestBlockHex === 'string' && latestBlockHex.startsWith('0x')) {
    diagnostic.latest_block_number = parseInt(latestBlockHex, 16)
  }

  return diagnostic
}

function normalizeInternalTransactions(items: BlockscoutInternalTransaction[] | null | undefined): TransactionInternalCall[] {
  if (!Array.isArray(items)) {
    return []
  }

  return items.map((item) => ({
    from_address: item.from?.hash || '',
    from_label: item.from?.name || item.from?.label || undefined,
    to_address: item.to?.hash || undefined,
    to_label: item.to?.name || item.to?.label || undefined,
    contract_address: item.created_contract?.hash || undefined,
    contract_label: item.created_contract?.name || item.created_contract?.label || undefined,
    type: item.type || undefined,
    value: item.value || '0',
    success: item.success ?? undefined,
    error: item.error || undefined,
    result: item.result || undefined,
    timestamp: item.timestamp || undefined,
  }))
}

export const transactionsApi = {
  get: async (chainId: number, hash: string): Promise<ApiResponse<Transaction>> => {
    const raw = await fetchBlockscoutJson<unknown>(`/api/v2/transactions/${hash}`)
    return { data: normalizeTransaction(raw as never, chainId) }
  },
  /** Use when you need to check response.ok before setting state (avoids treating 4xx/5xx body as data). */
  getSafe: async (chainId: number, hash: string): Promise<{ ok: boolean; data: Transaction | null }> => {
    try {
      const { data } = await transactionsApi.get(chainId, hash)
      return { ok: true, data }
    } catch {
      return { ok: false, data: null }
    }
  },
  diagnoseMissing: async (chainId: number, hash: string): Promise<TransactionLookupDiagnostic> => {
    return diagnoseMissingTransaction(chainId, hash)
  },
  getInternalTransactionsSafe: async (hash: string): Promise<{ ok: boolean; data: TransactionInternalCall[] }> => {
    try {
      const raw = await fetchBlockscoutJson<{ items?: BlockscoutInternalTransaction[] } | BlockscoutInternalTransaction[]>(
        `/api/v2/transactions/${hash}/internal-transactions`
      )
      const items = Array.isArray(raw) ? raw : raw.items
      return { ok: true, data: normalizeInternalTransactions(items) }
    } catch {
      return { ok: false, data: [] }
    }
  },
  list: async (chainId: number, page: number, pageSize: number): Promise<ApiResponse<Transaction[]>> => {
    const params = new URLSearchParams({
      page: page.toString(),
      page_size: pageSize.toString(),
    })
    const raw = await fetchBlockscoutJson<{ items?: unknown[] }>(`/api/v2/transactions?${params.toString()}`)
    const data = Array.isArray(raw?.items) ? raw.items.map((item) => normalizeTransaction(item as never, chainId)) : []
    return { data }
  },
  /** Use when you need to check ok before setting state (avoids treating error body as list). */
  listSafe: async (chainId: number, page: number, pageSize: number): Promise<{ ok: boolean; data: Transaction[] }> => {
    try {
      const { data } = await transactionsApi.list(chainId, page, pageSize)
      return { ok: true, data }
    } catch {
      return { ok: false, data: [] }
    }
  },
}
