import { resolveExplorerApiBase } from '@/libs/frontend-api-client/api-base'
import type { Block } from './blocks'
import type { Transaction } from './transactions'
import type {
  AddressInfo,
  AddressTokenBalance,
  AddressTokenTransfer,
  TransactionSummary,
} from './addresses'

export function getExplorerApiBase() {
  return resolveExplorerApiBase()
}

export async function fetchBlockscoutJson<T>(path: string): Promise<T> {
  const response = await fetch(`${getExplorerApiBase()}${path}`)
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  return response.json() as Promise<T>
}

type HashLike = string | { hash?: string | null } | null | undefined
type StringLike = string | number | null | undefined

export function extractHash(value: HashLike): string {
  if (!value) return ''
  return typeof value === 'string' ? value : value.hash || ''
}

export function extractLabel(value: unknown): string {
  if (!value || typeof value !== 'object') return ''
  const candidate = value as {
    name?: string | null
    label?: string | null
    display_name?: string | null
    symbol?: string | null
  }
  return candidate.name || candidate.label || candidate.display_name || candidate.symbol || ''
}

function toNumber(value: unknown): number {
  if (typeof value === 'number') return value
  if (typeof value === 'string' && value.trim() !== '') return Number(value)
  return 0
}

function toNullableNumber(value: unknown): number | undefined {
  if (value == null) return undefined
  const numeric = toNumber(value)
  return Number.isFinite(numeric) ? numeric : undefined
}

export interface BlockscoutAddressRef {
  hash?: string | null
  name?: string | null
  label?: string | null
  is_contract?: boolean
  is_verified?: boolean
}

export interface BlockscoutTokenRef {
  address?: string | null
  name?: string | null
  symbol?: string | null
  decimals?: StringLike
  type?: string | null
  total_supply?: string | null
  holders?: StringLike
}

export interface BlockscoutTokenTransfer {
  block_hash?: string
  block_number?: StringLike
  from?: BlockscoutAddressRef | null
  to?: BlockscoutAddressRef | null
  log_index?: StringLike
  method?: string | null
  timestamp?: string | null
  token?: BlockscoutTokenRef | null
  total?: {
    decimals?: StringLike
    value?: string | null
  } | null
  transaction_hash?: string
  type?: string | null
}

export interface BlockscoutDecodedInput {
  method_call?: string | null
  method_id?: string | null
  parameters?: Array<{
    name?: string | null
    type?: string | null
    value?: unknown
  }>
}

export interface BlockscoutInternalTransaction {
  from?: BlockscoutAddressRef | null
  to?: BlockscoutAddressRef | null
  created_contract?: BlockscoutAddressRef | null
  success?: boolean | null
  error?: string | null
  result?: string | null
  timestamp?: string | null
  transaction_hash?: string | null
  type?: string | null
  value?: string | null
}

interface BlockscoutBlock {
  hash: string
  height: number | string
  timestamp: string
  miner: HashLike
  transaction_count: number | string
  gas_used: number | string
  gas_limit: number | string
}

interface BlockscoutTransaction {
  hash: string
  block_number: number | string
  from: HashLike
  to: HashLike
  value: string
  status?: string | null
  result?: string | null
  gas_price?: number | string | null
  gas_limit: number | string
  gas_used?: number | string | null
  max_fee_per_gas?: number | string | null
  max_priority_fee_per_gas?: number | string | null
  raw_input?: string | null
  timestamp: string
  created_contract?: HashLike
  fee?: { value?: string | null } | string | null
  method?: string | null
  revert_reason?: string | null
  transaction_tag?: string | null
  decoded_input?: BlockscoutDecodedInput | null
  token_transfers?: BlockscoutTokenTransfer[] | null
  actions?: unknown[] | null
}

function normalizeStatus(raw: BlockscoutTransaction): number {
  const value = (raw.status || raw.result || '').toString().toLowerCase()
  if (value === 'success' || value === 'ok' || value === '1') return 1
  if (value === 'error' || value === 'failed' || value === '0') return 0
  return 0
}

export function normalizeBlock(raw: BlockscoutBlock, chainId: number): Block {
  return {
    chain_id: chainId,
    number: toNumber(raw.height),
    hash: raw.hash,
    timestamp: raw.timestamp,
    miner: extractHash(raw.miner),
    transaction_count: toNumber(raw.transaction_count),
    gas_used: toNumber(raw.gas_used),
    gas_limit: toNumber(raw.gas_limit),
  }
}

export function normalizeTransaction(raw: BlockscoutTransaction, chainId: number): Transaction {
  return {
    chain_id: chainId,
    hash: raw.hash,
    block_number: toNumber(raw.block_number),
    block_hash: '',
    transaction_index: 0,
    from_address: extractHash(raw.from),
    to_address: extractHash(raw.to) || undefined,
    value: raw.value || '0',
    gas_price: raw.gas_price != null ? toNumber(raw.gas_price) : undefined,
    max_fee_per_gas: raw.max_fee_per_gas != null ? toNumber(raw.max_fee_per_gas) : undefined,
    max_priority_fee_per_gas: raw.max_priority_fee_per_gas != null ? toNumber(raw.max_priority_fee_per_gas) : undefined,
    gas_limit: toNumber(raw.gas_limit),
    gas_used: raw.gas_used != null ? toNumber(raw.gas_used) : undefined,
    status: normalizeStatus(raw),
    input_data: raw.raw_input || undefined,
    contract_address: extractHash(raw.created_contract) || undefined,
    created_at: raw.timestamp,
    fee: typeof raw.fee === 'string' ? raw.fee : raw.fee?.value || undefined,
    method: raw.method || undefined,
    revert_reason: raw.revert_reason || undefined,
    transaction_tag: raw.transaction_tag || undefined,
    decoded_input: raw.decoded_input
      ? {
          method_call: raw.decoded_input.method_call || undefined,
          method_id: raw.decoded_input.method_id || undefined,
          parameters: Array.isArray(raw.decoded_input.parameters)
            ? raw.decoded_input.parameters.map((parameter) => ({
                name: parameter.name || undefined,
                type: parameter.type || undefined,
                value: parameter.value,
              }))
            : [],
        }
      : undefined,
    token_transfers: Array.isArray(raw.token_transfers)
      ? raw.token_transfers.map((transfer) => ({
          block_number: toNullableNumber(transfer.block_number),
          from_address: extractHash(transfer.from),
          from_label: extractLabel(transfer.from),
          to_address: extractHash(transfer.to),
          to_label: extractLabel(transfer.to),
          token_address: transfer.token?.address || '',
          token_name: transfer.token?.name || undefined,
          token_symbol: transfer.token?.symbol || undefined,
          token_decimals: toNullableNumber(transfer.token?.decimals) ?? toNullableNumber(transfer.total?.decimals) ?? 18,
          amount: transfer.total?.value || '0',
          type: transfer.type || undefined,
          timestamp: transfer.timestamp || undefined,
        }))
      : [],
  }
}

export function normalizeTransactionSummary(raw: BlockscoutTransaction): TransactionSummary {
  return {
    hash: raw.hash,
    block_number: toNumber(raw.block_number),
    from_address: extractHash(raw.from),
    to_address: extractHash(raw.to) || undefined,
    value: raw.value || '0',
    status: normalizeStatus(raw),
  }
}

interface BlockscoutAddress {
  hash: string
  coin_balance?: string | null
  is_contract?: boolean
  is_verified?: boolean
  name?: string | null
  public_tags?: Array<{ label?: string; display_name?: string; name?: string } | string>
  private_tags?: Array<{ label?: string; display_name?: string; name?: string } | string>
  watchlist_names?: string[]
  has_token_transfers?: boolean
  has_tokens?: boolean
  creation_transaction_hash?: string | null
  token?: BlockscoutTokenRef | null
}

export function normalizeAddressInfo(
  raw: BlockscoutAddress,
  counters: {
    transactions_count?: number | string
    token_balances_count?: number | string
    token_transfers_count?: number | string
    internal_transactions_count?: number | string
    logs_count?: number | string
  },
  chainId: number,
): AddressInfo {
  const tags = [
    ...(raw.public_tags || []),
    ...(raw.private_tags || []),
    ...(raw.watchlist_names || []),
  ]
    .map((tag) => {
      if (typeof tag === 'string') return tag
      return tag.display_name || tag.label || tag.name || ''
    })
    .filter(Boolean)

  return {
    address: raw.hash,
    chain_id: chainId,
    transaction_count: Number(counters.transactions_count || 0),
    token_count: Number(counters.token_balances_count || 0),
    token_transfer_count: Number(counters.token_transfers_count || 0),
    internal_transaction_count: Number(counters.internal_transactions_count || 0),
    logs_count: Number(counters.logs_count || 0),
    is_contract: !!raw.is_contract,
    is_verified: !!raw.is_verified,
    has_token_transfers: !!raw.has_token_transfers,
    has_tokens: !!raw.has_tokens,
    balance: raw.coin_balance || undefined,
    creation_transaction_hash: raw.creation_transaction_hash || undefined,
    label: raw.name || raw.token?.symbol || undefined,
    tags,
    token_contract: raw.token?.address
      ? {
          address: raw.token.address,
          symbol: raw.token.symbol || undefined,
          name: raw.token.name || undefined,
          decimals: toNullableNumber(raw.token.decimals),
          type: raw.token.type || undefined,
          total_supply: raw.token.total_supply || undefined,
          holders: toNullableNumber(raw.token.holders),
        }
      : undefined,
  }
}

export function normalizeAddressTokenBalance(raw: {
  token?: BlockscoutTokenRef | null
  value?: string | null
}): AddressTokenBalance {
  return {
    token_address: raw.token?.address || '',
    token_name: raw.token?.name || undefined,
    token_symbol: raw.token?.symbol || undefined,
    token_type: raw.token?.type || undefined,
    token_decimals: toNullableNumber(raw.token?.decimals) ?? 18,
    value: raw.value || '0',
    holder_count: raw.token?.holders != null ? toNumber(raw.token.holders) : undefined,
    total_supply: raw.token?.total_supply || undefined,
  }
}

export function normalizeAddressTokenTransfer(raw: BlockscoutTokenTransfer): AddressTokenTransfer {
  return {
    transaction_hash: raw.transaction_hash || '',
    block_number: toNullableNumber(raw.block_number) ?? 0,
    timestamp: raw.timestamp || undefined,
    from_address: extractHash(raw.from),
    from_label: extractLabel(raw.from),
    to_address: extractHash(raw.to),
    to_label: extractLabel(raw.to),
    token_address: raw.token?.address || '',
    token_name: raw.token?.name || undefined,
    token_symbol: raw.token?.symbol || undefined,
    token_decimals: toNullableNumber(raw.token?.decimals) ?? toNullableNumber(raw.total?.decimals) ?? 18,
    value: raw.total?.value || '0',
    type: raw.type || undefined,
  }
}
