import BigNumber from 'bignumber.js'
import { useCallback, useMemo } from 'react'

import useCreditManagerStore from 'stores/useCreditManagerStore'
import { getTokenDecimals } from 'utils/tokens'

import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
import useTokenPrices from './useTokenPrices'
import useRedbankBalances from './useRedbankBalances'

const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
  const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)

  return hourlyAPY.times(amount).toNumber()
}

const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
  const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)

  const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
  const { data: marketsData } = useMarkets()
  const { data: tokenPrices } = useTokenPrices()
  const { data: redbankBalances } = useRedbankBalances()

  const tokenDecimals = getTokenDecimals(denom)

  const getTokenValue = useCallback(
    (amount: string, denom: string) => {
      if (!tokenPrices) return 0

      return BigNumber(amount).times(tokenPrices[denom]).toNumber()
    },
    [tokenPrices],
  )

  const tokenAmountInCreditAccount = useMemo(() => {
    return positionsData?.coins.find((coin) => coin.denom === denom)?.amount ?? 0
  }, [denom, positionsData])

  const maxAmount = useMemo(() => {
    if (!marketsData || !tokenPrices || !positionsData || !redbankBalances || !denom) return 0

    const hasDebt = positionsData.debts.length > 0

    const borrowTokenPrice = tokenPrices[denom]
    const borrowTokenMaxLTV = Number(marketsData[denom].max_loan_to_value)

    // approximate debt value in an hour timespan to avoid throwing on smart contract level
    // due to debt interest being applied
    const totalLiabilitiesValue = positionsData?.debts.reduce((acc, coin) => {
      const estimatedInterestAmount = getApproximateHourlyInterest(
        coin.amount,
        marketsData[coin.denom].borrow_rate,
      )
      const tokenDebtValue = BigNumber(getTokenValue(coin.amount, coin.denom)).plus(
        estimatedInterestAmount,
      )

      return tokenDebtValue.plus(acc).toNumber()
    }, 0)

    const positionsWeightedAverageWithoutAsset = positionsData?.coins.reduce((acc, coin) => {
      if (coin.denom === denom) return acc

      const tokenWeightedValue = BigNumber(getTokenValue(coin.amount, coin.denom)).times(
        Number(marketsData[coin.denom].max_loan_to_value),
      )

      return tokenWeightedValue.plus(acc).toNumber()
    }, 0)

    const isHealthyAfterFullWithdraw = !hasDebt
      ? true
      : positionsWeightedAverageWithoutAsset / totalLiabilitiesValue > 1

    let maxAmountCapacity = 0

    if (isHealthyAfterFullWithdraw) {
      const maxBorrow = BigNumber(positionsWeightedAverageWithoutAsset)
        .minus(totalLiabilitiesValue)
        .div(borrowTokenPrice)

      maxAmountCapacity = maxBorrow
        .plus(tokenAmountInCreditAccount)
        .decimalPlaces(tokenDecimals)
        .toNumber()
    } else {
      const requiredCollateral = BigNumber(totalLiabilitiesValue)
        .minus(positionsWeightedAverageWithoutAsset)
        .dividedBy(borrowTokenPrice * borrowTokenMaxLTV)

      maxAmountCapacity = BigNumber(tokenAmountInCreditAccount)
        .minus(requiredCollateral)
        .decimalPlaces(tokenDecimals)
        .toNumber()

      // required collateral might be greater than the amount in the credit account which will result
      // in a negative max amount capacity
      maxAmountCapacity = maxAmountCapacity < 0 ? 0 : maxAmountCapacity
    }

    const marketLiquidity = redbankBalances?.[denom] ?? 0

    const maxWithdrawAmount = BigNumber(maxAmountCapacity).gt(marketLiquidity)
      ? marketLiquidity
      : maxAmountCapacity
    const isMaxAmountHigherThanBalance = BigNumber(maxWithdrawAmount).gt(tokenAmountInCreditAccount)

    if (!borrow && isMaxAmountHigherThanBalance) {
      return BigNumber(tokenAmountInCreditAccount)
        .div(10 ** tokenDecimals)
        .toNumber()
    }

    return BigNumber(maxWithdrawAmount)
      .div(10 ** tokenDecimals)
      .decimalPlaces(tokenDecimals)
      .toNumber()
  }, [
    borrow,
    denom,
    getTokenValue,
    marketsData,
    positionsData,
    redbankBalances,
    tokenAmountInCreditAccount,
    tokenDecimals,
    tokenPrices,
  ])

  return maxAmount
}

export default useCalculateMaxWithdrawAmount
