import { useState, useCallback, useEffect } from 'react'
import { useAccount, useWriteContract, useWaitForTransactionReceipt, useReadContract } from 'wagmi'
import { parseUnits, formatUnits } from 'viem'
import { abis, getContractAddress } from '@/lib/contracts'
import { Token } from '@/lib/api/tokens'
import { handleContractCatchError, isValidAmount, parseContractError } from '@/lib/errors'

type WithdrawCollateralStatus = 'idle' | 'withdrawing' | 'success' | 'error'

/**
 * Hook for withdrawing collateral (YT tokens) from lending protocol
 */
export function useWithdrawCollateral() {
  const { address, chainId } = useAccount()
  const [status, setStatus] = useState<WithdrawCollateralStatus>('idle')
  const [error, setError] = useState<string | null>(null)

  const {
    writeContractAsync: withdrawWrite,
    data: withdrawHash,
    isPending: isWithdrawPending,
    reset: resetWithdraw,
  } = useWriteContract()

  const {
    isLoading: isWithdrawConfirming,
    isSuccess: isWithdrawSuccess,
    isError: isWithdrawError,
    error: withdrawReceiptError,
  } = useWaitForTransactionReceipt({ hash: withdrawHash })

  const executeWithdrawCollateral = useCallback(async (token: Token, amount: string) => {
    if (status !== 'idle') return false
    if (!address || !chainId || !token?.contractAddress || !amount) {
      setError('Missing required parameters')
      return false
    }
    if (!isValidAmount(amount)) {
      setError('Invalid amount')
      return false
    }

    try {
      setError(null)
      setStatus('withdrawing')
      const lendingProxyAddress = getContractAddress('lendingProxy', chainId)
      if (!lendingProxyAddress) {
        throw new Error('Contract not deployed on this chain')
      }
      const decimals = token.onChainDecimals ?? token.decimals
      const amountInWei = parseUnits(amount, decimals)
      await withdrawWrite({
        address: lendingProxyAddress,
        abi: abis.lendingProxy,
        functionName: 'withdrawCollateral',
        args: [token.contractAddress as `0x${string}`, amountInWei],
      })
      return true
    } catch (err: unknown) {
      handleContractCatchError(err, 'Withdraw collateral failed', setError, setStatus as (s: string) => void)
      return false
    }
  }, [address, chainId, withdrawWrite, status])

  useEffect(() => {
    if (isWithdrawSuccess && status === 'withdrawing') {
      setStatus('success')
    }
  }, [isWithdrawSuccess, status])

  useEffect(() => {
    if (isWithdrawError && status === 'withdrawing') {
      setError(parseContractError(withdrawReceiptError))
      setStatus('error')
    }
  }, [isWithdrawError, status, withdrawReceiptError])

  const reset = useCallback(() => {
    setStatus('idle')
    setError(null)
    resetWithdraw()
  }, [resetWithdraw])

  return {
    status,
    error,
    isLoading: isWithdrawPending || isWithdrawConfirming,
    withdrawHash,
    executeWithdrawCollateral,
    reset,
  }
}

type CollateralStatus = 'idle' | 'approving' | 'approved' | 'supplying' | 'success' | 'error'

/**
 * Hook for supplying collateral (YT tokens) to lending protocol
 */
export function useLendingCollateral() {
  const { address, chainId } = useAccount()
  const [status, setStatus] = useState<CollateralStatus>('idle')
  const [error, setError] = useState<string | null>(null)
  const [pendingToken, setPendingToken] = useState<Token | null>(null)
  const [pendingAmount, setPendingAmount] = useState<string>('')

  const {
    writeContractAsync: approveWrite,
    data: approveHash,
    isPending: isApprovePending,
    reset: resetApprove,
  } = useWriteContract()

  const {
    isLoading: isApproveConfirming,
    isSuccess: isApproveSuccess,
    isError: isApproveError,
    error: approveReceiptError,
  } = useWaitForTransactionReceipt({ hash: approveHash })

  const {
    writeContractAsync: supplyWrite,
    data: supplyHash,
    isPending: isSupplyPending,
    reset: resetSupply,
  } = useWriteContract()

  const {
    isLoading: isSupplyConfirming,
    isSuccess: isSupplySuccess,
    isError: isSupplyError,
    error: supplyReceiptError,
  } = useWaitForTransactionReceipt({ hash: supplyHash })

  const executeApprove = useCallback(async (token: Token, amount: string) => {
    if (status !== 'idle') return false
    if (!address || !chainId || !token?.contractAddress || !amount) {
      setError('Missing required parameters')
      return false
    }
    if (!isValidAmount(amount)) {
      setError('Invalid amount')
      return false
    }

    try {
      setError(null)
      setStatus('approving')

      const lendingProxyAddress = getContractAddress('lendingProxy', chainId)
      if (!lendingProxyAddress) {
        throw new Error('Contract not deployed on this chain')
      }

      const decimals = token.onChainDecimals ?? token.decimals
      const amountInWei = parseUnits(amount, decimals)

      await approveWrite({
        address: token.contractAddress as `0x${string}`,
        abi: abis.YTToken,
        functionName: 'approve',
        args: [lendingProxyAddress, amountInWei],
      })

      return true
    } catch (err: unknown) {
      handleContractCatchError(err, 'Approve failed', setError, setStatus as (s: string) => void)
      return false
    }
  }, [address, chainId, approveWrite, status])

  const executeSupplyCollateral = useCallback(async (token: Token, amount: string) => {
    if (status === 'supplying' || status === 'success' || status === 'error') return false
    if (!address || !chainId || !token?.contractAddress || !amount) {
      setError('Missing required parameters')
      return false
    }
    if (!isValidAmount(amount)) {
      setError('Invalid amount')
      return false
    }

    try {
      setError(null)
      setStatus('supplying')

      const lendingProxyAddress = getContractAddress('lendingProxy', chainId)
      if (!lendingProxyAddress) {
        throw new Error('Contract not deployed on this chain')
      }

      const decimals = token.onChainDecimals ?? token.decimals
      const amountInWei = parseUnits(amount, decimals)

      await supplyWrite({
        address: lendingProxyAddress,
        abi: abis.lendingProxy,
        functionName: 'supplyCollateral',
        args: [token.contractAddress as `0x${string}`, amountInWei],
      })

      return true
    } catch (err: unknown) {
      handleContractCatchError(err, 'Supply collateral failed', setError, setStatus as (s: string) => void)
      return false
    }
  }, [address, chainId, supplyWrite, status])

  const executeApproveAndSupply = useCallback(async (token: Token, amount: string) => {
    if (status !== 'idle') return
    setPendingToken(token)
    setPendingAmount(amount)
    const approveSuccess = await executeApprove(token, amount)
    if (!approveSuccess) return
  }, [executeApprove, status])

  // Auto-execute supply when approve is successful
  useEffect(() => {
    if (isApproveSuccess && status === 'approving' && pendingToken && pendingAmount) {
      setStatus('approved')
      executeSupplyCollateral(pendingToken, pendingAmount)
    }
  }, [isApproveSuccess, status, pendingToken, pendingAmount, executeSupplyCollateral])

  useEffect(() => {
    if (isApproveError && status === 'approving') {
      setError(parseContractError(approveReceiptError))
      setStatus('error')
    }
  }, [isApproveError, status, approveReceiptError])

  useEffect(() => {
    if (isSupplySuccess && status === 'supplying') {
      setStatus('success')
    }
  }, [isSupplySuccess, status])

  useEffect(() => {
    if (isSupplyError && status === 'supplying') {
      setError(parseContractError(supplyReceiptError))
      setStatus('error')
    }
  }, [isSupplyError, status, supplyReceiptError])

  const reset = useCallback(() => {
    setStatus('idle')
    setError(null)
    setPendingToken(null)
    setPendingAmount('')
    resetApprove()
    resetSupply()
  }, [resetApprove, resetSupply])

  return {
    status,
    error,
    isLoading: isApprovePending || isApproveConfirming || isSupplyPending || isSupplyConfirming,
    approveHash,
    supplyHash,
    executeApprove,
    executeSupplyCollateral,
    executeApproveAndSupply,
    reset,
  }
}

/**
 * Query user's wallet balance of a specific YT token (ERC20 balanceOf)
 */
export function useYTWalletBalance(token: Token | undefined) {
  const { address } = useAccount()

  const { data: balance, isLoading, refetch } = useReadContract({
    address: token?.contractAddress as `0x${string}` | undefined,
    abi: abis.YTToken,
    functionName: 'balanceOf',
    args: address ? [address] : undefined,
    query: {
      enabled: !!address && !!token?.contractAddress,
      retry: false,
    },
  })

  const decimals = token?.onChainDecimals ?? token?.decimals ?? 18
  const formattedBalance = balance
    ? formatUnits(balance as bigint, decimals)
    : '0.00'

  return {
    balance: balance as bigint || 0n,
    formattedBalance,
    isLoading,
    refetch,
  }
}

/**
 * Query collateral balance for a specific YT token
 */
export function useCollateralBalance(token: Token | undefined) {
  const { address, chainId } = useAccount()
  const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined

  const {
    data: balance,
    isLoading,
    refetch
  } = useReadContract({
    address: lendingProxyAddress,
    abi: abis.lendingProxy,
    functionName: 'userCollateral',
    args: address && token?.contractAddress ? [address, token.contractAddress as `0x${string}`] : undefined,
    query: {
      enabled: !!address && !!lendingProxyAddress && !!token?.contractAddress,
      retry: false,
    },
  })

  const decimals = token?.onChainDecimals ?? token?.decimals ?? 18
  const formattedBalance = balance
    ? formatUnits(balance as bigint, decimals)
    : '0.00'

  return {
    balance: balance as bigint || 0n,
    formattedBalance,
    isLoading,
    refetch,
  }
}
