package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory

import com.tangem.common.getRewardStakingBalance
import com.tangem.common.getTotalStakingBalance
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.RewardBlockType
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.model.optionOrNull
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.IconState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.features.tokendetails.impl.R
import com.tangem.lib.crypto.BlockchainUtils.isStakingRewardUnavailable
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
import com.tangem.utils.logging.TangemLogger
import java.math.BigDecimal

internal class TokenDetailsStakingInfoConverter(
    private val cryptoCurrencyStatus: CryptoCurrencyStatus,
    private val appCurrencyProvider: Provider<AppCurrency>,
    private val clickIntents: TokenDetailsClickIntents,
    private val currentState: TokenDetailsState,
    private val stakingEntryInfo: StakingEntryInfo?,
) : Converter<StakingAvailability, TokenDetailsState> {

    override fun convert(value: StakingAvailability): TokenDetailsState {
        return currentState.copy(
            stakingBlocksState = getYieldBalance(cryptoCurrencyStatus, currentState, value),
        )
    }

    private fun getYieldBalance(
        status: CryptoCurrencyStatus,
        state: TokenDetailsState,
        stakingAvailability: StakingAvailability,
    ): StakingBlockUM? {
        TangemLogger.i(
            "Define staking block for [${status.currency.id.value}] with availability:\n$stakingAvailability",
        )
        return when (stakingAvailability) {
            StakingAvailability.TemporaryUnavailable -> StakingBlockUM.TemporaryUnavailable
            StakingAvailability.Unavailable -> null
            is StakingAvailability.Full -> getStakedBlockOrNull(status)
            is StakingAvailability.Available -> getStakingInfoBlock(
                status = status,
                state = state,
                isBetaMode = stakingAvailability.optionOrNull?.integrationId == StakingIntegrationID.P2PEthPool,
            )
        }
    }

    @Suppress("CyclomaticComplexMethod")
    private fun getStakingInfoBlock(
        status: CryptoCurrencyStatus,
        state: TokenDetailsState,
        isBetaMode: Boolean,
    ): StakingBlockUM? {
        val stakingBalance = status.value.stakingBalance as? StakingBalance.Data

        val stakingCryptoAmount = stakingBalance?.getTotalStakingBalance(status.currency.network.rawId)

        val hasPendingBalances = when (stakingBalance) {
            is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.isNotEmpty()
            is StakingBalance.Data.P2PEthPool -> !stakingBalance.unstakingAmount.isNullOrZero()
            null -> false
        }
        val pendingAmount = when (stakingBalance) {
            is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.sumOf { it.amount }
            is StakingBalance.Data.P2PEthPool -> stakingBalance.unstakingAmount
            null -> BigDecimal.ZERO
        }

        val iconState = state.tokenInfoBlockState.iconState

        TangemLogger.i(
            """
            getStakingInfoBlock:
            – stakingBalance: ${stakingBalance ?: "null"}
            – stakingCryptoAmount: $stakingCryptoAmount
            – stakingEntryInfo: $stakingEntryInfo
            """.trimIndent(),
        )

        return when {
            stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> {
                if (!hasPendingBalances) {
                    getStakeAvailableState(
                        stakingEntryInfo = stakingEntryInfo,
                        iconState = iconState,
                        isEnabled = isStakingButtonEnabled(status),
                        isBetaMode = isBetaMode,
                    )
                } else {
                    getStakedBlockWithFiatAmount(status, pendingAmount, null)
                }
            }
            stakingCryptoAmount.isNullOrZero() && stakingEntryInfo == null -> {
                null
            }
            else -> getStakedBlockWithFiatAmount(
                status = status,
                stakingAmount = stakingCryptoAmount,
                rewardAmount = when (stakingBalance) {
                    is StakingBalance.Data.StakeKit -> stakingBalance.getRewardStakingBalance()
                    is StakingBalance.Data.P2PEthPool -> stakingBalance.totalRewards
                    else -> BigDecimal.ZERO
                },
            )
        }
    }

    private fun getStakedBlockOrNull(status: CryptoCurrencyStatus): StakingBlockUM? {
        val stakingBalance = status.value.stakingBalance as? StakingBalance.Data
        val stakingCryptoAmount = stakingBalance?.getTotalStakingBalance(status.currency.network.rawId)
        val hasPendingBalances = when (stakingBalance) {
            is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.isNotEmpty()
            is StakingBalance.Data.P2PEthPool -> !stakingBalance.unstakingAmount.isNullOrZero()
            null -> false
        }
        return when {
            !stakingCryptoAmount.isNullOrZero() -> getStakedBlockWithFiatAmount(
                status = status,
                stakingAmount = stakingCryptoAmount,
                rewardAmount = when (stakingBalance) {
                    is StakingBalance.Data.StakeKit -> stakingBalance.getRewardStakingBalance()
                    is StakingBalance.Data.P2PEthPool -> stakingBalance.totalRewards
                    else -> BigDecimal.ZERO
                },
            )
            // Pending-only path: reachable for StakeKit (pending items are not part of the total);
            // for P2PEthPool the unstaking amount is already included in getTotalStakingBalance above.
            hasPendingBalances -> getStakedBlockWithFiatAmount(
                status = status,
                stakingAmount = when (stakingBalance) {
                    is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.sumOf { it.amount }
                    is StakingBalance.Data.P2PEthPool -> stakingBalance.unstakingAmount
                    null -> BigDecimal.ZERO
                },
                rewardAmount = null,
            )
            else -> null
        }
    }

    private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean {
        return status.value is CryptoCurrencyStatus.Loaded ||
            status.value is CryptoCurrencyStatus.NoQuote ||
            status.value is CryptoCurrencyStatus.Custom
    }

    private fun getStakedBlockWithFiatAmount(
        status: CryptoCurrencyStatus,
        stakingAmount: BigDecimal?,
        rewardAmount: BigDecimal?,
    ): StakingBlockUM.Staked {
        val fiatRate = status.value.fiatRate
        return getStakedState(
            status = status,
            stakingCryptoAmount = stakingAmount,
            stakingFiatAmount = stakingAmount?.let { fiatRate?.multiply(it) },
            stakingRewardAmount = rewardAmount?.let { fiatRate?.multiply(it) },
        )
    }

    private fun getStakeAvailableState(
        stakingEntryInfo: StakingEntryInfo,
        iconState: IconState,
        isEnabled: Boolean,
        isBetaMode: Boolean,
    ): StakingBlockUM.StakeAvailable {
        return StakingBlockUM.StakeAvailable(
            titleText = resourceReference(id = R.string.token_details_staking_block_title),
            subtitleText = resourceReference(
                id = R.string.staking_notification_earn_rewards_text,
                formatArgs = wrappedList(stakingEntryInfo.tokenSymbol),
            ),
            iconState = iconState,
            isEnabled = isEnabled,
            onStakeClicked = clickIntents::onStakeBannerClick,
            isBetaMode = isBetaMode,
        )
    }

    private fun getStakedState(
        status: CryptoCurrencyStatus,
        stakingCryptoAmount: BigDecimal?,
        stakingFiatAmount: BigDecimal?,
        stakingRewardAmount: BigDecimal?,
    ): StakingBlockUM.Staked {
        return StakingBlockUM.Staked(
            cryptoAmount = stakingCryptoAmount,
            fiatAmount = stakingFiatAmount,
            cryptoValue = stringReference(
                stakingCryptoAmount.format {
                    crypto(
                        symbol = status.currency.symbol,
                        decimals = status.currency.decimals,
                    )
                },
            ),
            fiatValue = stringReference(
                stakingFiatAmount.format {
                    fiat(
                        appCurrencyProvider().code,
                        appCurrencyProvider().symbol,
                    )
                },
            ),
            rewardValue = getRewardText(status, stakingRewardAmount),
            onStakeClicked = clickIntents::onStakeBannerClick,
            isBetaMode = status.value.stakingBalance is StakingBalance.Data.P2PEthPool,
        )
    }

    private fun getRewardText(status: CryptoCurrencyStatus, stakingRewardAmount: BigDecimal?): TextReference {
        val blockchainId = status.currency.network.rawId
        val isCoin = status.currency.id.isCoin
        val stakingBalance = status.value.stakingBalance

        val rewardBlockType = when {
            stakingBalance is StakingBalance.Data.P2PEthPool -> {
                if (stakingBalance.totalRewards.isNullOrZero()) {
                    RewardBlockType.RewardUnavailable.DefaultRewardUnavailable
                } else {
                    RewardBlockType.EthereumEarnedRewards
                }
            }
            isStakingRewardUnavailable(blockchainId, isCoin) -> {
                RewardBlockType.RewardUnavailable.DefaultRewardUnavailable
            }
            stakingRewardAmount.isNullOrZero() -> RewardBlockType.NoRewards
            else -> RewardBlockType.Rewards
        }

        return when (rewardBlockType) {
            RewardBlockType.NoRewards -> resourceReference(R.string.staking_details_no_rewards_to_claim)
            RewardBlockType.CardanoNoRewards -> resourceReference(R.string.staking_cardano_details_rewards_info_text)
            RewardBlockType.RewardUnavailable.DefaultRewardUnavailable,
            RewardBlockType.RewardUnavailable.SolanaRewardUnavailable,
            -> TextReference.EMPTY
            RewardBlockType.EthereumEarnedRewards -> {
                val cryptoRewardAmount = (stakingBalance as? StakingBalance.Data.P2PEthPool)?.totalRewards
                resourceReference(
                    R.string.staking_details_autocompound_rewards_earned,
                    wrappedList(
                        cryptoRewardAmount.format {
                            crypto(
                                symbol = status.currency.symbol,
                                decimals = status.currency.decimals,
                            )
                        },
                    ),
                )
            }
            RewardBlockType.RewardsRequirementsError,
            RewardBlockType.Rewards,
            -> resourceReference(
                R.string.staking_details_rewards_to_claim,
                wrappedList(
                    stakingRewardAmount.format {
                        fiat(
                            appCurrencyProvider().code,
                            appCurrencyProvider().symbol,
                        )
                    },
                ),
            )
        }
    }
}