import { fetchBlockscoutJson, normalizeAddressTokenTransfer, type BlockscoutTokenTransfer } from './blockscout'
import { configApi, type TokenListToken } from './config'
import { routesApi, type MissionControlLiquidityPool } from './routes'
import { tokenAggregationApi } from './tokenAggregation'
import {
  filterChain138Tokens,
  mergeTokenListLookups,
  type TokenListSurface,
} from './tokenListSurfaces'
import type { AddressTokenTransfer } from './addresses'

export interface TokenProfile {
  address: string
  name?: string
  symbol?: string
  decimals: number
  type?: string
  total_supply?: string
  holders?: number
  exchange_rate?: string | number | null
  icon_url?: string | null
  circulating_market_cap?: string | number | null
  volume_24h?: string | number | null
  liquidity_usd?: string | number | null
  market_updated_at?: string | null
  price_source?: 'blockscout' | 'token-aggregation' | 'derived'
}

export interface TokenHolder {
  address: string
  label?: string
  value: string
  token_decimals: number
}

export interface TokenProvenance {
  listed: boolean
  chainId?: number
  name?: string
  symbol?: string
  logoURI?: string
  tags: string[]
}

function normalizeTokenProfile(raw: {
  address: string
  name?: string | null
  symbol?: string | null
  decimals?: string | number | null
  type?: string | null
  total_supply?: string | null
  holders?: string | number | null
  exchange_rate?: string | number | null
  icon_url?: string | null
  circulating_market_cap?: string | number | null
  volume_24h?: string | number | null
  liquidity_usd?: string | number | null
  market_updated_at?: string | null
  price_source?: 'blockscout' | 'token-aggregation' | 'derived'
}): TokenProfile {
  return {
    address: raw.address,
    name: raw.name || undefined,
    symbol: raw.symbol || undefined,
    decimals: Number(raw.decimals || 0),
    type: raw.type || undefined,
    total_supply: raw.total_supply || undefined,
    holders: raw.holders != null ? Number(raw.holders) : undefined,
    exchange_rate: raw.exchange_rate ?? null,
    icon_url: raw.icon_url ?? null,
    circulating_market_cap: raw.circulating_market_cap ?? null,
    volume_24h: raw.volume_24h ?? null,
    liquidity_usd: raw.liquidity_usd ?? null,
    market_updated_at: raw.market_updated_at ?? null,
    price_source: raw.price_source || 'blockscout',
  }
}

function computeMarketCap(totalSupply: string | undefined, decimals: number, priceUsd: number | undefined): number | null {
  if (!totalSupply || priceUsd == null || !Number.isFinite(priceUsd)) {
    return null
  }

  const supplyNumeric = Number(totalSupply)
  if (!Number.isFinite(supplyNumeric) || Math.abs(supplyNumeric) > Number.MAX_SAFE_INTEGER) {
    return null
  }

  const normalizedSupply = supplyNumeric / 10 ** decimals
  if (!Number.isFinite(normalizedSupply)) {
    return null
  }

  return normalizedSupply * priceUsd
}

function mergeTokenProfileWithAggregation(
  blockscoutToken: TokenProfile | null,
  aggregationToken: Awaited<ReturnType<typeof tokenAggregationApi.getTokenSafe>>['data'],
): TokenProfile | null {
  if (!blockscoutToken && !aggregationToken) {
    return null
  }

  const priceUsd = aggregationToken?.market?.priceUsd
  const merged: TokenProfile = {
    address: blockscoutToken?.address || aggregationToken?.address || '',
    name: blockscoutToken?.name || aggregationToken?.name,
    symbol: blockscoutToken?.symbol || aggregationToken?.symbol,
    decimals: blockscoutToken?.decimals || aggregationToken?.decimals || 0,
    type: blockscoutToken?.type,
    total_supply: blockscoutToken?.total_supply || aggregationToken?.totalSupply,
    holders: blockscoutToken?.holders,
    exchange_rate: priceUsd ?? blockscoutToken?.exchange_rate ?? null,
    icon_url: blockscoutToken?.icon_url ?? null,
    circulating_market_cap:
      blockscoutToken?.circulating_market_cap ??
      computeMarketCap(blockscoutToken?.total_supply || aggregationToken?.totalSupply, blockscoutToken?.decimals || aggregationToken?.decimals || 0, priceUsd),
    volume_24h: aggregationToken?.market?.volume24h ?? blockscoutToken?.volume_24h ?? null,
    liquidity_usd: aggregationToken?.market?.liquidityUsd ?? blockscoutToken?.liquidity_usd ?? null,
    market_updated_at: aggregationToken?.market?.lastUpdated ?? blockscoutToken?.market_updated_at ?? null,
    price_source:
      priceUsd != null
        ? 'token-aggregation'
        : blockscoutToken?.exchange_rate != null
          ? 'blockscout'
          : blockscoutToken?.circulating_market_cap == null && blockscoutToken?.volume_24h == null && priceUsd == null
            ? 'derived'
            : blockscoutToken?.price_source || 'blockscout',
  }

  return merged.address ? merged : null
}

function normalizeTokenHolder(raw: {
  address?: {
    hash?: string | null
    name?: string | null
    label?: string | null
  } | null
  value?: string | null
  token?: {
    decimals?: string | number | null
  } | null
}): TokenHolder {
  return {
    address: raw.address?.hash || '',
    label: raw.address?.name || raw.address?.label || undefined,
    value: raw.value || '0',
    token_decimals: Number(raw.token?.decimals || 0),
  }
}

async function getTokenListLookup(): Promise<Map<string, TokenListToken>> {
  const [reportResult, extendedResult] = await Promise.all([
    tokensApi.listReportSafe(138),
    tokensApi.listForSurface('extended', 138),
  ])
  return mergeTokenListLookups(
    reportResult.ok ? reportResult.data : [],
    extendedResult.ok ? extendedResult.data : [],
  )
}

export const tokensApi = {
  getSafe: async (address: string): Promise<{ ok: boolean; data: TokenProfile | null }> => {
    try {
      const [blockscoutResult, aggregationResult] = await Promise.allSettled([
        fetchBlockscoutJson<{
        address: string
        name?: string | null
        symbol?: string | null
        decimals?: string | number | null
        type?: string | null
        total_supply?: string | null
        holders?: string | number | null
        exchange_rate?: string | number | null
        icon_url?: string | null
        circulating_market_cap?: string | number | null
        volume_24h?: string | number | null
      }>(`/api/v2/tokens/${address}`),
        tokenAggregationApi.getTokenSafe(138, address),
      ])

      const blockscoutToken =
        blockscoutResult.status === 'fulfilled' ? normalizeTokenProfile(blockscoutResult.value) : null
      const aggregationToken =
        aggregationResult.status === 'fulfilled' && aggregationResult.value.ok ? aggregationResult.value.data : null
      const merged = mergeTokenProfileWithAggregation(blockscoutToken, aggregationToken)

      return { ok: merged != null, data: merged }
    } catch {
      return { ok: false, data: null }
    }
  },

  getTransfersSafe: async (
    address: string,
    page = 1,
    pageSize = 10
  ): Promise<{ ok: boolean; data: AddressTokenTransfer[] }> => {
    try {
      const params = new URLSearchParams({
        page: page.toString(),
        items_count: pageSize.toString(),
      })
      const raw = await fetchBlockscoutJson<{ items?: BlockscoutTokenTransfer[] }>(
        `/api/v2/tokens/${address}/transfers?${params.toString()}`
      )
      return {
        ok: true,
        data: Array.isArray(raw.items) ? raw.items.map((item) => normalizeAddressTokenTransfer(item)) : [],
      }
    } catch {
      return { ok: false, data: [] }
    }
  },

  getHoldersSafe: async (
    address: string,
    page = 1,
    pageSize = 10
  ): Promise<{ ok: boolean; data: TokenHolder[] }> => {
    try {
      const params = new URLSearchParams({
        page: page.toString(),
        items_count: pageSize.toString(),
      })
      const raw = await fetchBlockscoutJson<{ items?: Array<{
        address?: { hash?: string | null; name?: string | null; label?: string | null } | null
        value?: string | null
        token?: { decimals?: string | number | null } | null
      }> }>(`/api/v2/tokens/${address}/holders?${params.toString()}`)
      return {
        ok: true,
        data: Array.isArray(raw.items) ? raw.items.map((item) => normalizeTokenHolder(item)) : [],
      }
    } catch {
      return { ok: false, data: [] }
    }
  },

  getProvenanceSafe: async (address: string): Promise<{ ok: boolean; data: TokenProvenance | null }> => {
    try {
      const lookup = await getTokenListLookup()
      const token = lookup.get(address.toLowerCase())
      if (!token) {
        return { ok: true, data: { listed: false, tags: [] } }
      }
      return {
        ok: true,
        data: {
          listed: true,
          chainId: token.chainId,
          name: token.name,
          symbol: token.symbol,
          logoURI: token.logoURI,
          tags: token.tags || [],
        },
      }
    } catch {
      return { ok: false, data: null }
    }
  },

  listForSurface: async (
    surface: TokenListSurface,
    chainId = 138,
  ): Promise<{ ok: boolean; data: TokenListToken[]; source?: 'report' | 'config' }> => {
    try {
      if (surface === 'extended') {
        const response = await configApi.getTokenList()
        return {
          ok: true,
          source: 'config',
          data: filterChain138Tokens(response.tokens || [], chainId),
        }
      }

      const report = await tokensApi.listReportSafe(chainId)
      if (report.ok && report.data.length > 0) {
        return { ok: true, source: 'report', data: report.data }
      }

      const response = await configApi.getTokenList()
      return {
        ok: true,
        source: 'config',
        data: filterChain138Tokens(response.tokens || [], chainId),
      }
    } catch {
      return { ok: false, data: [] }
    }
  },

  listCuratedSafe: async (chainId = 138): Promise<{ ok: boolean; data: TokenListToken[] }> => {
    const result = await tokensApi.listForSurface('extended', chainId)
    return { ok: result.ok, data: result.data }
  },

  listReportSafe: async (chainId = 138): Promise<{ ok: boolean; data: TokenListToken[] }> => {
    try {
      const response = await fetchBlockscoutJson<{ tokens?: TokenListToken[] }>(
        `/api/v1/report/token-list?chainId=${chainId}`,
      )
      const data = filterChain138Tokens(response.tokens || [], chainId)
      return { ok: true, data }
    } catch {
      return { ok: false, data: [] }
    }
  },

  getRelatedPoolsSafe: async (address: string): Promise<{ ok: boolean; data: MissionControlLiquidityPool[] }> => {
    try {
      const response = await routesApi.getTokenPools(address)
      return { ok: true, data: response.pools || [] }
    } catch {
      return { ok: false, data: [] }
    }
  },
}
