import type { Account } from '@openocean.finance/wallet-management'
import type {
  ExecutionStatus,
  OpenOceanStep,
  Process,
  Route,
} from '@openocean.finance/widget-sdk'
import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js'
import { ethers } from 'ethers'
import { getPublicClient, getWalletClient } from 'wagmi/actions'
import { useSettingsStore } from '../stores/settings/useSettingsStore.js'
import { sendAndConfirmSolanaTransaction } from './SendAndConfirmSolanaTransaction.js'

const OpenOceanABI = [
  {
    inputs: [
      {
        internalType: 'contract IOpenOceanCaller',
        name: 'caller',
        type: 'address',
      },
      {
        components: [
          {
            internalType: 'contract IERC20',
            name: 'srcToken',
            type: 'address',
          },
          {
            internalType: 'contract IERC20',
            name: 'dstToken',
            type: 'address',
          },
          {
            internalType: 'address',
            name: 'srcReceiver',
            type: 'address',
          },
          {
            internalType: 'address',
            name: 'dstReceiver',
            type: 'address',
          },
          {
            internalType: 'uint256',
            name: 'amount',
            type: 'uint256',
          },
          {
            internalType: 'uint256',
            name: 'minReturnAmount',
            type: 'uint256',
          },
          {
            internalType: 'uint256',
            name: 'guaranteedAmount',
            type: 'uint256',
          },
          {
            internalType: 'uint256',
            name: 'flags',
            type: 'uint256',
          },
          {
            internalType: 'address',
            name: 'referrer',
            type: 'address',
          },
          {
            internalType: 'bytes',
            name: 'permit',
            type: 'bytes',
          },
        ],
        internalType: 'struct OpenOceanExchange.SwapDescription',
        name: 'desc',
        type: 'tuple',
      },
      {
        components: [
          {
            internalType: 'uint256',
            name: 'target',
            type: 'uint256',
          },
          {
            internalType: 'uint256',
            name: 'gasLimit',
            type: 'uint256',
          },
          {
            internalType: 'uint256',
            name: 'value',
            type: 'uint256',
          },
          {
            internalType: 'bytes',
            name: 'data',
            type: 'bytes',
          },
        ],
        internalType: 'struct IOpenOceanCaller.CallDescription[]',
        name: 'calls',
        type: 'tuple[]',
      },
    ],
    name: 'swap',
    outputs: [
      {
        internalType: 'uint256',
        name: 'returnAmount',
        type: 'uint256',
      },
    ],
    stateMutability: 'payable',
    type: 'function',
  },
]

// Add helper function for handling hexadecimal conversion
function hexToUint8Array(hexString: string): Uint8Array {
  const pairs = hexString.match(/[\dA-F]{2}/gi) || []
  return new Uint8Array(pairs.map((s) => Number.parseInt(s, 16)))
}

interface ExecuteRouteOptions {
  updateRouteHook?: (route: Route) => void
  acceptExchangeRateUpdateHook?: (params: any) => Promise<boolean>
  infiniteApproval?: boolean
  executeInBackground?: boolean
  account: Account
  wagmiConfig: any // Use any to avoid deep type instantiation
  onDisconnect?: (account: Account) => Promise<void>
  onOpenWalletMenu?: () => void
}

interface ExtendedOpenOceanStep extends OpenOceanStep {
  execution?: {
    status: ExecutionStatus
    process: Process[]
  }
  toolData?: {
    data: string
  }
}

interface ExtendedRoute extends Route {
  steps: ExtendedOpenOceanStep[]
  prependedOperatingExpenseCost?: string
  data?: {
    prependedOperatingExpenseCost?: string
  }
}

// Execute Solana transaction
async function executeSolanaSwap(
  step: ExtendedOpenOceanStep,
  options: ExecuteRouteOptions,
  process: Process,
  route: ExtendedRoute
): Promise<void> {
  try {
    // Check wallet connection status
    if (!options.account || !options.account.connector) {
      throw new Error('Wallet not connected or connector not initialized')
    }

    let transaction: any = ''
    const connection = new Connection(
      'https://burned-practical-bird.solana-mainnet.quiknode.pro/33f4786133c252415e194b29ee69ffc7671480ab'
    )
    const txData: any = step.transactionRequest?.data || ''
    const dexId = step.transactionRequest?.type || 0
    if (step.action.fromChainId === step.action.toChainId) {
      if (dexId == 6 || dexId == 7 || dexId == 9) {
        transaction = VersionedTransaction.deserialize(hexToUint8Array(txData))
      } else {
        transaction = Transaction.from(hexToUint8Array(txData))
      }
    } else {
      transaction = VersionedTransaction.deserialize(
        hexToUint8Array(txData.slice(2))
      )

      const { blockhash } = await connection.getLatestBlockhash()
      transaction.message.recentBlockhash = blockhash
    }

    // Check signTransaction method exists
    const connector = options.account.connector as any
    if (typeof connector.signTransaction !== 'function') {
      throw new Error('Wallet does not support transaction signing')
    }

    // Sign transaction
    const signedTx = await connector.signTransaction(transaction)
    if (!signedTx) {
      throw new Error('Failed to sign transaction')
    }

    // Serialize signed transaction
    const serializedTransaction = signedTx.serialize({
      verifySignatures: false,
      requireAllSignatures: false,
    })

    // Use improved transaction sender
    await sendAndConfirmSolanaTransaction({
      connection,
      serializedTransaction,
      process,
      updateRouteHook: (updatedProcess) => {
        // Update process status
        Object.assign(process, updatedProcess)

        // Update step execution status
        if (process.status === 'DONE') {
          step.execution!.status = 'DONE'
        } else if (process.status === 'FAILED') {
          step.execution!.status = 'FAILED'
        } else {
          step.execution!.status = 'PENDING'
        }

        // Call original updateRouteHook
        options.updateRouteHook?.(route)
      },
    })
  } catch (error) {
    console.error('Solana swap execution failed:', error)
    process.status = 'FAILED'
    process.error =
      error instanceof Error
        ? {
            code: 'EXECUTION_ERROR',
            message: error.message,
            htmlMessage: error.message,
          }
        : {
            code: 'UNKNOWN_ERROR',
            message: 'Unknown error occurred',
            htmlMessage: 'Unknown error occurred',
          }
    step.execution!.status = 'FAILED'
    options.updateRouteHook?.(route)
    throw error
  }
}

// Execute EVM transaction
async function executeEvmSwap(
  step: ExtendedOpenOceanStep,
  options: ExecuteRouteOptions,
  process: Process,
  route: ExtendedRoute
): Promise<void> {
  try {
    let walletClient = await getWalletClient(options.wagmiConfig)

    // Check if wallet is connected
    if (!walletClient) {
      if (options.account?.connector && options.onDisconnect) {
        await options.onDisconnect(options.account)
      }
      if (options.onOpenWalletMenu) {
        options.onOpenWalletMenu()
      }
      throw new Error('Please connect wallet first')
    }

    // Check if current chain matches target chain
    const currentChainId = walletClient.chain.id
    const targetChainId = step.action.fromChainId

    if (currentChainId !== targetChainId) {
      try {
        // Try to switch to target chain
        await walletClient.switchChain({ id: targetChainId })

        // Get updated walletClient after chain switch
        walletClient = (await getWalletClient(options.wagmiConfig)) as any
        if (!walletClient || walletClient.chain.id !== targetChainId) {
          throw new Error('Failed to switch chain')
        }
      } catch (error) {
        console.error('Failed to switch chain:', error)
        throw new Error(`Please manually switch to chain ID: ${targetChainId}`)
      }
    }

    const publicClient = getPublicClient(options.wagmiConfig)
    if (!publicClient) {
      // If disconnect callback exists, disconnect the current connection first
      if (options.account?.connector && options.onDisconnect) {
        await options.onDisconnect(options.account)
      }
      // Open wallet menu to let user reconnect
      if (options.onOpenWalletMenu) {
        options.onOpenWalletMenu()
      }
      throw new Error('Public client not found')
    }

    console.log(
      'Current Chain:',
      publicClient.chain?.id,
      publicClient.chain?.name
    )
    console.log('Token Address:', step.action.fromToken.address)
    console.log('Token Chain ID:', step.action.fromToken.chainId)
    console.log('Owner Address:', walletClient.account.address)
    console.log('Spender Address:', step.estimate.approvalAddress)

    // Check token approval
    if (
      [
        '0x0000000000000000000000000000000000000000',
        '0x0000000000000000000000000000000000001010',
      ].indexOf(step.action.fromToken.address) === -1
    ) {
      let allowance = 0n
      try {
        allowance = (await publicClient.readContract({
          address: step.action.fromToken.address as `0x${string}`,
          abi: [
            {
              constant: true,
              inputs: [
                { name: 'owner', type: 'address' },
                { name: 'spender', type: 'address' },
              ],
              name: 'allowance',
              outputs: [{ name: '', type: 'uint256' }],
              type: 'function',
            },
          ],
          functionName: 'allowance',
          args: [
            walletClient.account.address,
            step.estimate.approvalAddress as `0x${string}`,
          ],
        })) as bigint
      } catch (error) {
        console.error('Failed to read allowance:', error)
        // Log additional context
        console.error('Context - Step:', JSON.stringify(step, null, 2))
        console.error('Context - Route:', JSON.stringify(route, null, 2))
        console.error('Context - Public Client Chain:', publicClient.chain)
        // Re-throw the error or handle it appropriately
        // throw new Error(`Failed to read allowance for token ${step.action.fromToken.address}: ${error instanceof Error ? error.message : String(error)}`);
      }

      const amount =
        BigInt(step.action.fromAmount) +
        BigInt(
          route?.prependedOperatingExpenseCost ||
            route?.data?.prependedOperatingExpenseCost ||
            '0'
        )
      if (allowance < BigInt(amount)) {
        const approvalAmount = options.infiniteApproval
          ? BigInt(
              '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
            )
          : BigInt(amount)

        const hash = await walletClient.writeContract({
          chain: walletClient.chain,
          account: walletClient.account,
          address: step.action.fromToken.address as `0x${string}`,
          abi: [
            {
              constant: false,
              inputs: [
                { name: 'spender', type: 'address' },
                { name: 'amount', type: 'uint256' },
              ],
              name: 'approve',
              outputs: [{ name: '', type: 'bool' }],
              type: 'function',
            },
          ],
          functionName: 'approve',
          args: [
            step.estimate.approvalAddress as `0x${string}`,
            approvalAmount,
          ],
        })

        await publicClient.waitForTransactionReceipt({ hash })
      }
    }

    const { transactionRequest } = step || {}
    const txRequest = {
      chain: publicClient.chain,
      to: transactionRequest?.to as `0x${string}`,
      data: (transactionRequest?.data as `0x${string}`) || '0x',
      value: BigInt(transactionRequest?.value || '0x0'),
      account: walletClient.account.address,
    }

    // 在这里根据useSettingsStore 中的 dynamicSlippage 参数来判断是否调用下面的函数 swap_quote_mev
    const { dynamicSlippage } = useSettingsStore.getState()
    if (dynamicSlippage) {
      try {
        // 构建 response 对象
        const response = {
          inAmount: step.action.fromAmount || '0',
          inToken: step.action.fromToken,
          data: transactionRequest?.data,
          from: walletClient.account.address,
          to: transactionRequest?.to,
          value: transactionRequest?.value || '0',
        }

        // 调用 swap_quote_mev 获取调整后的交易数据
        const adjustedResponse = await swapQuoteMEV(response, { publicClient })

        // 如果 swap_quote_mev 返回了修改后的数据，更新交易请求
        if (adjustedResponse && adjustedResponse.data !== response.data) {
          txRequest.data = adjustedResponse.data as `0x${string}`
          txRequest.value = BigInt(adjustedResponse.value || '0')
          console.log('Applied MEV protection with dynamic slippage')
        }
      } catch (error) {
        console.error('Failed to apply MEV protection:', error)
        // 错误时继续使用原始交易请求
      }
    }

    // Estimate gas
    const estimatedGas = await publicClient.estimateGas(txRequest)
    console.log('estimatedGas', estimatedGas)

    // Add estimated gas to transaction request (using 2x the estimated value to ensure transaction success)
    const finalTxRequest = {
      ...txRequest,
      gas: estimatedGas * 2n,
    }

    const hash = await walletClient.sendTransaction({
      ...finalTxRequest,
      kzg: undefined,
    })

    process.status = 'PENDING'
    process.txHash = hash
    process.message = 'Transaction pending'
    options.updateRouteHook?.(route)

    const receipt = await publicClient.waitForTransactionReceipt({ hash })

    process.status = receipt.status === 'success' ? 'DONE' : 'FAILED'
    process.doneAt = Date.now()
    process.message =
      receipt.status === 'success'
        ? 'Transaction confirmed'
        : 'Transaction failed'

    step.execution!.status = receipt.status === 'success' ? 'DONE' : 'FAILED'
    options.updateRouteHook?.(route)

    if (receipt.status !== 'success') {
      throw new Error('Transaction failed')
    }
  } catch (error) {
    console.error('EVM swap execution failed:', error)
    process.status = 'FAILED'
    process.error =
      error instanceof Error
        ? {
            code: 'EXECUTION_ERROR',
            message: error.message,
            htmlMessage: error.message,
          }
        : {
            code: 'UNKNOWN_ERROR',
            message: 'Unknown error occurred',
            htmlMessage: 'Unknown error occurred',
          }
    step.execution!.status = 'FAILED'
    options.updateRouteHook?.(route)
    throw error
  }
}

/**
 * 将带精度的金额转换为实际金额
 * @param amount 带精度的金额
 * @param decimals 精度
 * @returns 实际金额
 */
function decimals2Amount(amount: string | number, decimals = 18): number {
  return Number(amount) / Math.pow(10, decimals)
}

/**
 * 交易响应类型
 */
interface SwapResponse {
  inAmount?: string
  inToken?: {
    decimals?: number
    price?: string | number
    priceUSD?: string | number
    address?: string
  }
  data?: string
  from?: string
  to?: string
  value?: string
  minOutAmount?: string
}

/**
 * 根据动态滑点调整交易参数，提供 MEV 保护
 */
async function swapQuoteMEV(
  response: SwapResponse,
  options?: { publicClient?: any }
): Promise<SwapResponse> {
  try {
    const inAmount = response?.inAmount
    const inTokenDecimals = response?.inToken?.decimals || 18
    const inTokenPrice = Number(response?.inToken?.priceUSD || 0)
    const amount = decimals2Amount(inAmount, inTokenDecimals) * inTokenPrice
    if (amount < 1) {
      return response
    }
    const { publicClient } = options
    const OPENOCEAN_CONTRACT = new ethers.Contract(
      '0x6352a56caadC4F1E25CD6c75970Fa768A3304e64',
      OpenOceanABI
    )
    if (
      ethers.hexlify(ethers.getBytes(response?.data || '0x').slice(0, 4)) !==
      OPENOCEAN_CONTRACT.interface.getFunction('swap').selector
    ) {
      return response
    }
    const oldCallData = OPENOCEAN_CONTRACT.interface.decodeFunctionData(
      'swap',
      response?.data
    )
    const callData = [...oldCallData]
    callData[1] = [...oldCallData[1]]
    const minOutAmount = BigInt(callData[1][5] || 0)
    const outAmount = BigInt(callData[1][6] || 0)
    const slippageAmount = outAmount - minOutAmount

    const minOutAmounts = await Promise.all(
      [1, 2, 3].map(async (i) => {
        const mockMinOutAmount =
          minOutAmount + (slippageAmount / 4n) * BigInt(i)
        callData[1][5] = mockMinOutAmount
        const params = {
          from: response?.from as `0x${string}`,
          to: response?.to as `0x${string}`,
          data: OPENOCEAN_CONTRACT.interface.encodeFunctionData(
            'swap',
            callData
          ) as `0x${string}`,
          value: BigInt(response?.value || '0'),
        }
        try {
          await publicClient.estimateGas(params)
          return mockMinOutAmount
        } catch (error) {
          console.error('Failed to estimate gas:', error)
          return undefined
        }
      })
    )
    let [min1, min2] = minOutAmounts
      .filter((value) => value !== undefined)
      .sort((a, b) => (BigInt(b || 0) > BigInt(a || 0) ? 1 : -1))
      .slice(0, 2)
    min1 = min1 ?? minOutAmount
    min2 = min2 ?? minOutAmount

    const randomFactor = BigInt(Math.floor(Math.random() * 10000))
    const minOutAmountDiff = BigInt(min1 || 0) - BigInt(min2 || 0)
    const finalMinOutAmount =
      BigInt(min2 || 0) + (minOutAmountDiff * randomFactor) / BigInt(10000)

    if (finalMinOutAmount < minOutAmount) {
      return response
    }
    callData[1][5] = finalMinOutAmount
    const finalCallData = OPENOCEAN_CONTRACT.interface.encodeFunctionData(
      'swap',
      callData
    )
    response.minOutAmount = finalMinOutAmount.toString()
    response.data = finalCallData
    return response
  } catch (error) {
    return response
  }
}

// Execute transaction
async function executeSwap(
  route: ExtendedRoute,
  options: ExecuteRouteOptions
): Promise<Route> {
  const updatedRoute = { ...route }

  try {
    // Execute transactions for each step
    for (const step of updatedRoute.steps) {
      // Update status to start execution
      const process: Process = {
        type: 'SWAP',
        status: 'STARTED',
        startedAt: Date.now(),
        message: 'Preparing swap transaction',
        txHash: '',
      }

      step.execution = {
        status: 'PENDING',
        process: [process],
      }

      options.updateRouteHook?.(updatedRoute)

      // Check wallet connection status
      if (!options.account || !options.account.connector) {
        // If connector exists but not connected, disconnect first
        if (options.account?.connector && options.onDisconnect) {
          await options.onDisconnect(options.account)
        }
        // Open wallet menu
        if (options.onOpenWalletMenu) {
          options.onOpenWalletMenu()
        }
        throw new Error('Please connect wallet first')
      }

      // Execute different transaction logic based on chain type
      const currentStep = route.steps[0]
      if (currentStep.action?.fromChainId === 1151111081099710) {
        await executeSolanaSwap(currentStep, options, process, updatedRoute)
      } else {
        await executeEvmSwap(currentStep, options, process, updatedRoute)
      }
    }

    return updatedRoute
  } catch (error: unknown) {
    console.error('Swap execution failed:', error)
    updatedRoute.steps.forEach((step) => {
      if (step.execution?.status === 'PENDING') {
        step.execution.status = 'FAILED'
        step.execution.process[0].status = 'FAILED'
        step.execution.process[0].message =
          error instanceof Error ? error.message : 'Transaction failed'
      }
    })

    options.updateRouteHook?.(updatedRoute)
    throw error
  }
}

// Export main function
export async function executeRoute(
  route: Route,
  options: ExecuteRouteOptions
): Promise<Route> {
  // Check wallet connection status
  if (!options.account.isConnected) {
    throw new Error('Wallet not connected')
  }

  return executeSwap(route as ExtendedRoute, options)
}
