import { useAccount } from '@openocean.finance/wallet-management'
import type { Route } from '@openocean.finance/widget-sdk'
import { OpenOceanErrorCode } from '@openocean.finance/widget-sdk'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { parseUnits, formatUnits } from 'viem'
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
import { DebridgeService } from '../services/DebridgeService.js'
import { OpenOceanService } from '../services/OpenOceanService.js'
import { useFieldValues } from '../stores/form/useFieldValues.js'
import { useSetExecutableRoute } from '../stores/routes/useSetExecutableRoute.js'
import { useSettings } from '../stores/settings/useSettings.js'
import { defaultSlippage } from '../stores/settings/useSettingsStore.js'
import { WidgetEvent } from '../types/events.js'
import { getChainTypeFromAddress } from '../utils/chainType.js'
import { useChain } from './useChain.js'
import { useDebouncedWatch } from './useDebouncedWatch.js'
import { useGasPrice } from './useGasPrice.js'
import { useGasRefuel } from './useGasRefuel.js'
import { useIsBatchingSupported } from './useIsBatchingSupported.js'
import { useSwapOnly } from './useSwapOnly.js'
import { useToken } from './useToken.js'
import { useWidgetEvents } from './useWidgetEvents.js'
import { useServerErrorStore } from '../stores/useServerErrorStore.js'

const refetchTime = 60_000

interface RoutesProps {
  observableRoute?: Route
}

export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
  const {
    subvariant,
    sdkConfig,
    contractTool,
    bridges,
    exchanges,
    fee,
    feeConfig,
    useRelayerRoutes,
    referrer
  } = useWidgetConfig()
  const setExecutableRoute = useSetExecutableRoute()
  const queryClient = useQueryClient()
  const emitter = useWidgetEvents()
  const swapOnly = useSwapOnly()
  const {
    disabledBridges,
    disabledExchanges,
    enabledBridges,
    enabledExchanges,
    enabledAutoRefuel,
    routePriority,
    slippage,
  } = useSettings([
    'disabledBridges',
    'disabledExchanges',
    'enabledBridges',
    'enabledExchanges',
    'enabledAutoRefuel',
    'routePriority',
    'slippage',
  ])
  const [fromTokenAmount] = useDebouncedWatch(500, 'fromAmount')
  const [
    fromChainId,
    fromTokenAddress,
    _toAddress,
    toTokenAmount,
    toChainId,
    toTokenAddress,
    contractCalls,
  ] = useFieldValues(
    'fromChain',
    'fromToken',
    'toAddress',
    'toAmount',
    'toChain',
    'toToken',
    'contractCalls'
  )
  const { token: fromToken } = useToken(fromChainId, fromTokenAddress)
  const { token: toToken } = useToken(toChainId, toTokenAddress)
  const { chain: fromChain } = useChain(fromChainId)
  const { chain: toChain } = useChain(toChainId)
  const { enabled: enabledRefuel, fromAmount: gasRecommendationFromAmount } =
    useGasRefuel()

  const { gasPrice } = useGasPrice(fromChainId?.toString() || '')
  const { account } = useAccount({ chainType: fromChain?.chainType })
  const { isBatchingSupported, isBatchingSupportedLoading } =
    useIsBatchingSupported(fromChain, account.address)

  const hasAmount = Number(fromTokenAmount) > 0 || Number(toTokenAmount) > 0

  const contractCallQuoteEnabled: boolean =
    subvariant === 'custom' ? Boolean(contractCalls && account.address) : true

  const toAddress = fromChainId === toChainId || (fromChain?.chainType === 'EVM' && toChain?.chainType === 'EVM') ? account.address : _toAddress
  // When we bridge between ecosystems we need to be sure toAddress is set and has the same chainType as toChain
  // If toAddress is set, it must have the same chainType as toChain
  const hasToAddressAndChainTypeSatisfied: boolean =
    !!toChain &&
    !!toAddress &&
    getChainTypeFromAddress(toAddress) === toChain.chainType
  // We need to check for toAddress only if it is set
  const isToAddressSatisfied = toAddress
    ? hasToAddressAndChainTypeSatisfied
    : true

  const isEnabled =
    Boolean(Number(fromChain?.id)) &&
    Boolean(Number(toChain?.id)) &&
    Boolean(fromToken?.address) &&
    Boolean(toToken?.address) &&
    !Number.isNaN(slippage) &&
    hasAmount &&
    isToAddressSatisfied &&
    contractCallQuoteEnabled &&
    !isBatchingSupportedLoading

  const queryKey = [
    'routes',
    account.address,
    fromChain?.id as number,
    fromToken?.address as string,
    fromTokenAmount,
    toAddress,
    toChain?.id as number,
    toToken?.address as string,
    toTokenAmount,
    contractCalls,
    slippage,
    swapOnly,
    disabledBridges,
    disabledExchanges,
    enabledBridges,
    enabledExchanges,
    routePriority,
    subvariant,
    sdkConfig?.routeOptions?.allowSwitchChain,
    enabledRefuel && enabledAutoRefuel,
    gasRecommendationFromAmount,
    feeConfig?.fee || fee,
    !!isBatchingSupported,
    observableRoute?.id,
  ] as const

  const { data, isLoading, isFetching, isFetched, dataUpdatedAt, refetch } =
    useQuery({
      queryKey,
      queryFn: async ({
        queryKey: [
          _,
          fromAddress,
          fromChainId,
          fromTokenAddress,
          fromTokenAmount,
          toAddress,
          toChainId,
          toTokenAddress,
          toTokenAmount,
          contractCalls,
          slippage = defaultSlippage,
          swapOnly,
          disabledBridges,
          disabledExchanges,
          allowedBridges,
          allowedExchanges,
          routePriority,
          subvariant,
          allowSwitchChain,
          enabledRefuel,
          gasRecommendationFromAmount,
          fee,
          isBatchingSupported,
          _observableRouteId,
        ],
        signal,
      }) => {
        try {
          useServerErrorStore.getState().setError(null)
          const fromAmount = parseUnits(fromTokenAmount, fromToken!.decimals)
          const formattedSlippage = slippage
            ? (Number.parseFloat(slippage) / 100).toString()
            : '0.01' // Default slippage 1%

          let quoteResult: any // Initialize quoteResult

          // Check if it's a cross-chain swap
          if (fromChainId !== toChainId) {
            // Use DebridgeService for cross-chain quotes
            if (fromToken && toToken) {
              // Construct Asset objects for DebridgeService
              const fromMsg = {
                address: fromTokenAddress,
                symbol: fromToken.symbol,
                decimals: fromToken.decimals,
                name: fromToken.name,
                icon: fromToken.logoURI,
                chainId: fromChainId,
              }
              const toMsg = {
                address: toTokenAddress,
                symbol: toToken.symbol,
                decimals: toToken.decimals,
                name: toToken.name,
                icon: toToken.logoURI,
                chainId: toChainId,
              }

              quoteResult = await DebridgeService.swapUThenCross({
                fromMsg: fromMsg,
                toMsg: toMsg,
                inAmount: fromAmount.toString(),
                slippage_tolerance: formattedSlippage, // Debridge might use a different format/unit
                account: account?.address || '',
                receiver: toAddress, // Assuming receiver is the same as account for now
              })
              // Add a flag or modify structure to indicate it's a Debridge route
              if (quoteResult) {
                quoteResult.isDebridgeRoute = true
              }
            } else {
              console.warn(
                'Cannot get Debridge quote: Missing account address, fromToken, or toToken.'
              )
              quoteResult = null // Or handle error appropriately
            }
          } else {
            // Use OpenOceanService for same-chain swaps
            if (account.address) {
              quoteResult = await OpenOceanService.getSwapQuote({
                chain: fromChainId.toString(),
                inTokenSymbol: fromToken?.symbol || '',
                inTokenAddress: fromTokenAddress,
                outTokenSymbol: toToken?.symbol || '',
                outTokenAddress: toTokenAddress,
                amount: fromAmount.toString(),
                account: account.address,
                slippage: formattedSlippage, // OpenOcean expects slippage like '100' for 1%
                gasPrice: gasPrice || '10', // Consider chain-specific defaults
                enabledDexIds: fromChainId === 1151111081099710 ? '6' : '', // Example for Solana specific dex
                referrer: referrer?.address || '',
                referrerFee: referrer?.fee || '',
              })
            } else {
              // Use getQuote if account is not connected (view mode)
              quoteResult = await OpenOceanService.getQuote({
                chain: fromChainId.toString(),
                inTokenSymbol: fromToken?.symbol || '',
                inTokenAddress: fromTokenAddress,
                outTokenSymbol: toToken?.symbol || '',
                outTokenAddress: toTokenAddress,
                amount: fromAmount.toString(),
                slippage: formattedSlippage, // OpenOcean expects slippage like '100' for 1%
                gasPrice: gasPrice || '10', // Consider chain-specific defaults
                enabledDexIds: fromChainId === 1151111081099710 ? '6' : '', // Example for Solana specific dex
              })
            }
            // Ensure the structure is consistent or add a flag
            if (quoteResult) {
              quoteResult.isDebridgeRoute = false
            }
          }
          const data = quoteResult && quoteResult.data || {}
          // minOutAmount calculation is now handled within DebridgeService or OpenOceanService
          const isDebridge = data.isDebridgeRoute === true

          let toAmountMin = '0';
          if (data?.minOutAmount) {
            toAmountMin = data.minOutAmount;
          } else if (isDebridge) {
            toAmountMin = '0';
          } else {
            const amount = Number(data?.outAmount || 0);
            const slippageValue = Number.parseFloat(slippage);
            const minAmount = (amount * (100 - slippageValue)) / 100;
            toAmountMin = minAmount.toFixed(20).replace(/\.?0+$/, '');
            // 如果还是科学计数法，再强制转字符串
            if (toAmountMin.includes('e') || toAmountMin.includes('E')) {
              toAmountMin = minAmount.toLocaleString('fullwide', { useGrouping: false, maximumSignificantDigits: 21 });
              toAmountMin = toAmountMin.replace(/\.?0+$/, '');
            }
          }
          const route: Route = {
            id: data?.orderId || Date.now().toString(),
            fromChainId: fromChainId,
            fromAmountUSD: data?.fromTokenUSD || '0',
            fromAmount: fromAmount.toString(),
            fromToken: fromToken!,
            fromAddress: fromAddress || '',
            toChainId: toChainId,
            toAmountUSD: data?.toTokenUSD || '0',
            toAmount: data?.outAmount || '0',
            toAmountMin,
            toToken: toToken!,
            toAddress: toAddress || '',
            insurance: {
              state: 'NOT_INSURABLE',
              feeAmountUsd: '0',
            },
            steps: [
              {
                id: '1',
                type: 'openocean',
                tool: isDebridge ? 'debridge' : 'openocean',
                transactionRequest: {
                  chainId: data?.chainId || fromChainId,
                  from: data?.from,
                  data: data?.transaction || data?.data || '0x',
                  to: data?.to,
                  value: data?.value || '0x0',
                  gasPrice: data?.gasPrice,
                  type: isDebridge ? '0x0' : data?.dexId || '0x0',
                },
                toolDetails: {
                  key: isDebridge ? 'debridge' : 'openocean',
                  name: isDebridge ? 'Debridge' : 'OpenOcean',
                  logoURI: isDebridge
                    ? 'https://s3.openocean.finance/static/debridge.svg'
                    : 'https://assets.coingecko.com/coins/images/17014/small/ooe_log.png',
                },
                action: {
                  fromChainId: fromChainId,
                  fromAmount: fromAmount.toString(),
                  fromToken: fromToken!,
                  toChainId: toChainId,
                  toToken: toToken!,
                  slippage: Number(formattedSlippage),
                  fromAddress: fromAddress || '',
                  toAddress: toAddress || '',
                },
                estimate: {
                  fromAmount: fromAmount.toString(),
                  toAmount: data?.outAmount || '0',
                  toAmountMin,
                  approvalAddress:
                    data?.approveContract ||
                    (isDebridge
                      ? data?.to
                      : '0x6352a56caadC4F1E25CD6c75970Fa768A3304e64'),
                  executionDuration: data?.executionDuration || Math.floor(Math.random() * 20) + 40,
                  tool: isDebridge ? 'debridge' : 'openocean',
                  feeCosts: data?.feeCosts || [
                    {
                      name: 'Gas Fee',
                      description: 'Estimated gas fee',
                      token: fromToken!,
                      amount: (data?.estimatedGas || '0').toString(),
                      amountUSD: '0',
                      percentage: '0',
                      included: true,
                    },
                  ],
                },
                includedSteps: [],
              },
            ],
            ...(data as any),
          }
          const routes = [route]
          emitter.emit(WidgetEvent.AvailableRoutes, routes)
          return routes
        } catch (error) {
          console.error('Failed to fetch OpenOcean routes:', error)
          return []
        }
      },
      enabled: isEnabled,
      staleTime: refetchTime,
      refetchInterval(query) {
        return Math.min(
          Math.abs(refetchTime - (Date.now() - query.state.dataUpdatedAt)),
          refetchTime
        )
      },
      retry(failureCount, error: any) {
        if (process.env.NODE_ENV === 'development') {
          console.warn('Route query failed:', { failureCount, error })
        }
        if (failureCount >= 3) {
          return false
        }
        if (error?.code === OpenOceanErrorCode.NotFound) {
          return false
        }
        return true
      },
    })

  const setReviewableRoute = (route: Route) => {
    const queryDataKey = queryKey.toSpliced(queryKey.length - 1, 1, route.id)
    queryClient.setQueryData(
      queryDataKey,
      { routes: [route] },
      { updatedAt: dataUpdatedAt }
    )
    setExecutableRoute(route)
  }

  return {
    routes: data,
    isLoading,
    isFetching,
    isFetched,
    dataUpdatedAt,
    refetchTime,
    refetch,
    fromChain,
    toChain,
    queryKey,
    setReviewableRoute,
  }
}
