import { ChainId } from '@openocean.finance/widget-sdk'
import { type WalletClient, formatUnits, parseUnits } from 'viem'
import type { Quote } from '../registry.js'
import {
  BaseSwapAdapter,
  type Chain,
  type NormalizedQuote,
  type NormalizedTxResponse,
  type QuoteParams,
  type SwapStatus,
} from './BaseSwapAdapter.js'

// const DEFAULT_PEGASUS_API_BASE = 'https://stagenet-api.pegasusfi.xyz'
const DEFAULT_PEGASUS_API_BASE = 'https://pegasusfi.openocean.finance'
const PEGASUS_API_KEY = ''//'pegasus-b13023448e2d40c691001548'

const CHAIN_ID_TO_PEGASUS: Record<string, string> = {
  [ChainId.ETH]: 'ETH',
  [ChainId.BSC]: 'BSC',
  [ChainId.POL]: 'POL',
  [ChainId.ARB]: 'ARB',
  [ChainId.OPT]: 'OPT',
  [ChainId.AVA]: 'AVA',
  [ChainId.BAS]: 'BASE',
  [ChainId.FTM]: 'FTM',
  [ChainId.MNT]: 'MNT',
  [ChainId.SCL]: 'SCROLL',
  [ChainId.BLS]: 'BLAST',
  [ChainId.SON]: 'SONIC',
  [ChainId.UNI]: 'UNI',
  [ChainId.MAM]: 'METIS',
}

const NATIVE_TOKEN_SYMBOL: Record<string, string> = {
  [ChainId.ETH]: 'ETH',
  [ChainId.BSC]: 'BNB',
  [ChainId.POL]: 'POL',
  [ChainId.ARB]: 'ETH',
  [ChainId.OPT]: 'ETH',
  [ChainId.AVA]: 'AVAX',
  [ChainId.BAS]: 'ETH',
  [ChainId.FTM]: 'FTM',
  [ChainId.MNT]: 'MNT',
}

const erc20ApproveAbi = [
  {
    inputs: [
      { type: 'address', name: 'spender' },
      { type: 'uint256', name: 'amount' },
    ],
    name: 'approve',
    outputs: [{ type: 'bool', name: '' }],
    stateMutability: 'nonpayable',
    type: 'function',
  },
] as const

type PegasusRoute = {
  provider: string
  providerType: string
  expectedOutput: string
  estimatedTimeSeconds: number
  fees?: {
    affiliate?: string
    liquidity?: string
    outbound?: string
    total?: string
    totalBps?: number
    slippageBps?: number
  }
  inboundAddress?: string | null
  memo?: string | null
  router?: string | null
}

type PegasusQuoteResponse = {
  quoteId: string
  expiresAt: string
  routes: PegasusRoute[]
  warnings?: Array<{
    provider: string
    code: string
    message: string
    userMessage: string
  }>
}

type PegasusTxParams = {
  to?: string
  data?: string
  value?: string
  gasLimit?: string
  memo?: string
  chainId?: number
  router?: string
  approvalSpender?: string
  providerReferenceId?: string
  instaswapSwapLite?: {
    txid?: string
    depositAddress?: string
    depositTokenSymbol?: string
    instructions?: string
  }
}

type PegasusSwapResponse = {
  transactionId: string
  status: string
  providerType: string
  route: PegasusRoute
  txParams?: PegasusTxParams
  approvalTx?: {
    spender: string
    tokenAddress: string
    amount: string
  }
}

type PegasusRawQuote = PegasusQuoteResponse & {
  selectedRoute: PegasusRoute
}

type PegasusChainInfo = {
  chain: string
  chainId?: number
  providers?: string[]
  assets: string[]
}

type PegasusChainsResponse = {
  chains: PegasusChainInfo[]
}

type PegasusApiError = {
  error?: {
    code?: string
    message?: string
    userMessage?: string
  }
}

const CHAINS_CACHE_TTL_MS = 5 * 60 * 1000

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const NATIVE_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'

function parsePegasusAsset(asset: string): { symbol: string; address?: string } {
  const dashIndex = asset.indexOf('-')
  if (dashIndex === -1) {
    return { symbol: asset }
  }

  return {
    symbol: asset.slice(0, dashIndex),
    address: asset.slice(dashIndex + 1),
  }
}

function readEnvVar(name: string): string | undefined {
  try {
    if (typeof process !== 'undefined' && process.env?.[name]) {
      return process.env[name]
    }
  } catch {
    // ignore
  }

  try {
    const metaEnv = (import.meta as { env?: Record<string, string> }).env
    if (metaEnv?.[name]) {
      return metaEnv[name]
    }
    if (metaEnv?.[`VITE_${name}`]) {
      return metaEnv[`VITE_${name}`]
    }
  } catch {
    // ignore
  }

  return undefined
}

function getPegasusApiBase(): string {
  return (
    readEnvVar('PEGASUS_API_BASE')?.replace(/\/$/, '') ||
    DEFAULT_PEGASUS_API_BASE
  )
}

function getPegasusApiKey(): string {
  return PEGASUS_API_KEY
}

export class PegasusAdapter extends BaseSwapAdapter {
  private chainsCache: {
    fetchedAt: number
    data: PegasusChainsResponse
  } | null = null

  getName(): string {
    return 'Pegasus'
  }

  getIcon(): string {
    return 'https://pegasusfi.openocean.finance/favicon.ico'
  }

  getSupportedChains(): Chain[] {
    return Object.keys(CHAIN_ID_TO_PEGASUS).map(Number) as Chain[]
  }

  getSupportedTokens(_sourceChain: Chain, _destChain: Chain): any[] {
    return []
  }

  private async pegasusRequest<T>(
    path: string,
    init?: RequestInit
  ): Promise<T> {
    const response = await fetch(`${getPegasusApiBase()}${path}`, {
      ...init,
      headers: {
        // 'X-API-Key': getPegasusApiKey(),
        ...(init?.body ? { 'Content-Type': 'application/json' } : {}),
        ...(init?.headers || {}),
      },
    })

    const data = (await response.json()) as T & PegasusApiError
    if (!response.ok || data.error) {
      throw new Error(
        data.error?.userMessage ||
        data.error?.message ||
        `Pegasus API request failed (${response.status})`
      )
    }

    return data
  }

  private async getChains(): Promise<PegasusChainsResponse> {
    if (
      this.chainsCache &&
      Date.now() - this.chainsCache.fetchedAt < CHAINS_CACHE_TTL_MS
    ) {
      return this.chainsCache.data
    }

    const data = await this.pegasusRequest<PegasusChainsResponse>('/chains', {
      method: 'GET',
    })

    this.chainsCache = {
      fetchedAt: Date.now(),
      data,
    }

    return data
  }

  private async getChainAssets(pegasusChain: string): Promise<string[]> {
    const { chains } = await this.getChains()
    const chainData = chains.find((chain) => chain.chain === pegasusChain)

    if (!chainData?.assets?.length) {
      throw new Error(`No Pegasus assets found for chain ${pegasusChain}`)
    }

    return chainData.assets
  }

  private toPegasusChain(chain: Chain): string {
    const pegasusChain = CHAIN_ID_TO_PEGASUS[chain]
    if (!pegasusChain) {
      throw new Error(`Pegasus does not support chain ${chain}`)
    }
    return pegasusChain
  }

  private async resolvePegasusToken(chain: Chain, token: any): Promise<string> {
    const pegasusChain = this.toPegasusChain(chain)
    const assets = await this.getChainAssets(pegasusChain)

    const address = token.address?.toLowerCase?.() || ''
    const isNative =
      token.isNative ||
      address === ZERO_ADDRESS ||
      address === NATIVE_ADDRESS

    if (isNative) {
      const nativeCandidates = [
        NATIVE_TOKEN_SYMBOL[chain],
        token.symbol,
      ]
        .filter(Boolean)
        .map((symbol) => symbol!.toUpperCase())

      for (const symbol of nativeCandidates) {
        const nativeAsset = assets.find(
          (asset) => !asset.includes('-') && asset.toUpperCase() === symbol
        )
        if (nativeAsset) {
          return nativeAsset
        }
      }

      throw new Error(
        `Native token not supported on Pegasus chain ${pegasusChain}`
      )
    }

    const erc20Asset = assets.find((asset) => {
      const parsed = parsePegasusAsset(asset)
      return parsed.address?.toLowerCase() === address
    })

    if (erc20Asset) {
      return erc20Asset
    }

    const symbolAsset = assets.find((asset) => {
      const parsed = parsePegasusAsset(asset)
      return (
        parsed.address &&
        parsed.symbol.toUpperCase() === token.symbol?.toUpperCase()
      )
    })

    if (symbolAsset) {
      return symbolAsset
    }

    throw new Error(
      `Token ${token.symbol || address} is not supported on Pegasus chain ${pegasusChain}`
    )
  }

  private selectBestRoute(
    routes: PegasusRoute[],
    warnings?: PegasusQuoteResponse['warnings']
  ): PegasusRoute {
    if (!routes.length) {
      throw new Error('No Pegasus routes found')
    }

    const blockedProviders = new Set(
      (warnings || [])
        .filter((warning) =>
          ['UNSUPPORTED_PAIR', 'CHAIN_HALTED'].includes(warning.code)
        )
        .map((warning) => warning.provider.toLowerCase())
    )

    const eligibleRoutes = routes.filter(
      (route) => !blockedProviders.has(route.provider.toLowerCase())
    )
    const targetRoutes = eligibleRoutes.length > 0 ? eligibleRoutes : routes

    return targetRoutes.reduce((best, current) =>
      Number.parseFloat(current.expectedOutput) >
        Number.parseFloat(best.expectedOutput)
        ? current
        : best
    )
  }

  async getQuote(params: QuoteParams): Promise<NormalizedQuote> {
    try {
      const fromChain = this.toPegasusChain(params.fromChain)
      const toChain = this.toPegasusChain(params.toChain)
      const fromToken = await this.resolvePegasusToken(
        params.fromChain,
        params.fromToken
      )
      const toToken = await this.resolvePegasusToken(
        params.toChain,
        params.toToken
      )
      const amount = formatUnits(
        BigInt(params.amount),
        params.fromToken.decimals
      )

      const searchParams = new URLSearchParams({
        fromChain,
        fromToken,
        toChain,
        toToken,
        amount,
      })

      if (params.sender) {
        searchParams.set('fromAddress', params.sender)
      }
      if (params.recipient) {
        searchParams.set('toAddress', params.recipient)
      }
      if (params.slippage > 0) {
        searchParams.set('slippageBps', params.slippage.toString())
      }

      const quoteResponse = await this.pegasusRequest<PegasusQuoteResponse>(
        `/quote?${searchParams.toString()}`,
        { method: 'GET' }
      )

      const selectedRoute = this.selectBestRoute(
        quoteResponse.routes,
        quoteResponse.warnings
      )

      const formattedInputAmount = amount
      const formattedOutputAmount = selectedRoute.expectedOutput
      const outputAmount = parseUnits(
        formattedOutputAmount,
        params.toToken.decimals
      )

      const inputUsd = params.tokenInUsd * Number.parseFloat(formattedInputAmount)
      const outputUsd =
        params.tokenOutUsd * Number.parseFloat(formattedOutputAmount)

      const priceImpact =
        !inputUsd || !outputUsd
          ? Number.NaN
          : ((inputUsd - outputUsd) * 100) / inputUsd

      const rate =
        Number.parseFloat(formattedInputAmount) > 0
          ? Number.parseFloat(formattedOutputAmount) /
          Number.parseFloat(formattedInputAmount)
          : 0

      const rawQuote: PegasusRawQuote = {
        ...quoteResponse,
        selectedRoute,
      }

      return {
        quoteParams: params,
        outputAmount,
        formattedOutputAmount,
        inputUsd,
        outputUsd,
        rate,
        timeEstimate: selectedRoute.estimatedTimeSeconds || 0,
        priceImpact,
        gasFeeUsd: 0,
        contractAddress:
          selectedRoute.router ||
          selectedRoute.inboundAddress ||
          '',
        rawQuote,
        protocolFee: Number.parseFloat(selectedRoute.fees?.total || '0'),
        platformFeePercent: (params.feeBps * 100) / 10_000,
      }
    } catch (error: any) {
      return this.handleError(error)
    }
  }

  async executeSwap(
    { quote }: Quote,
    walletClient: WalletClient
  ): Promise<NormalizedTxResponse> {
    const rawQuote = quote.rawQuote as PegasusRawQuote
    if (!rawQuote?.quoteId || !rawQuote?.selectedRoute?.provider) {
      throw new Error('Pegasus quote is missing quoteId or provider')
    }

    const account = walletClient.account?.address
    if (!account) {
      throw new Error('Wallet client account is not defined')
    }

    const swapResponse = await this.pegasusRequest<PegasusSwapResponse>(
      '/swap',
      {
        method: 'POST',
        body: JSON.stringify({
          quoteId: rawQuote.quoteId,
          provider: rawQuote.selectedRoute.provider,
          fromAddress: quote.quoteParams.sender || account,
          toAddress: quote.quoteParams.recipient || account,
          slippageBps: quote.quoteParams.slippage,
        }),
      }
    )

    if (swapResponse.approvalTx) {
      await walletClient.writeContract({
        chain: undefined,
        account,
        address: swapResponse.approvalTx.tokenAddress as `0x${string}`,
        abi: erc20ApproveAbi,
        functionName: 'approve',
        args: [
          swapResponse.approvalTx.spender as `0x${string}`,
          BigInt(swapResponse.approvalTx.amount),
        ],
      })
    }

    const txParams = swapResponse.txParams
    let sourceTxHash = swapResponse.transactionId

    if (txParams?.to) {
      sourceTxHash = await walletClient.sendTransaction({
        chain: undefined,
        account,
        to: txParams.to as `0x${string}`,
        data: (txParams.data as `0x${string}`) || '0x',
        value: BigInt(txParams.value || '0'),
        gas: txParams.gasLimit ? BigInt(txParams.gasLimit) : undefined,
        kzg: undefined,
      })
    } else if (txParams?.instaswapSwapLite?.depositAddress) {
      throw new Error(
        `Pegasus route requires deposit to ${txParams.instaswapSwapLite.depositAddress}. Manual deposit flow is not implemented yet.`
      )
    } else if (rawQuote.selectedRoute.inboundAddress) {
      throw new Error(
        `Pegasus route requires deposit to ${rawQuote.selectedRoute.inboundAddress}. Manual deposit flow is not implemented yet.`
      )
    }

    return {
      sender: quote.quoteParams.sender,
      id: swapResponse.transactionId,
      sourceTxHash,
      adapter: this.getName(),
      sourceChain: quote.quoteParams.fromChain,
      targetChain: quote.quoteParams.toChain,
      inputAmount: quote.quoteParams.amount,
      outputAmount: quote.outputAmount.toString(),
      sourceToken: quote.quoteParams.fromToken,
      targetToken: quote.quoteParams.toToken,
      timestamp: Date.now(),
    }
  }

  async getTransactionStatus(p: NormalizedTxResponse): Promise<SwapStatus> {
    try {
      const data = await this.pegasusRequest<{
        status?: string
        destinationTxHash?: string
        txHash?: string
      }>(`/swap/${encodeURIComponent(p.id)}`, { method: 'GET' })

      const status = data.status?.toLowerCase()
      let finalStatus: SwapStatus['status'] = 'Processing'

      if (status === 'completed' || status === 'success' || status === 'done') {
        finalStatus = 'Success'
      } else if (status === 'failed' || status === 'reverted') {
        finalStatus = 'Failed'
      } else if (status === 'refunded') {
        finalStatus = 'Refunded'
      }

      return {
        txHash: data.destinationTxHash || data.txHash || '',
        status: finalStatus,
      }
    } catch (error) {
      console.error('Failed to get Pegasus transaction status:', error)
      return {
        txHash: '',
        status: 'Processing',
      }
    }
  }
}
