package com.tangem.features.staking.impl.presentation.state.transformers.amount

import androidx.annotation.StringRes
import androidx.compose.ui.text.input.ImeAction
import com.tangem.common.extensions.isZero
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.staking.model.StakingIntegration
import com.tangem.domain.staking.model.common.StakingAmountRequirement
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
import com.tangem.lib.crypto.BlockchainUtils.isSolana
import com.tangem.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
import java.math.BigDecimal
import java.math.RoundingMode

internal class AmountRequirementStateTransformer(
    private val cryptoCurrencyStatus: CryptoCurrencyStatus,
    private val maxAmount: EnterAmountBoundary,
    private val integration: StakingIntegration,
    private val actionType: StakingActionCommonType,
    private val isSolanaUnstakeValidationEnabled: Boolean = false,
) : Transformer<AmountState> {
    override fun transform(prevState: AmountState): AmountState {
        return if (prevState is AmountState.Data) {
            updateWithError(
                prevState,
                actionType,
            )
        } else {
            prevState
        }
    }

    private fun updateWithError(amountState: AmountState.Data, actionType: StakingActionCommonType): AmountState {
        val requirementError = getRequirementError(amountState)
        val isIntegerOnlyError = isIntegerOnlyError(amountState, actionType)

        val cryptoAmount = amountState.amountTextField.cryptoAmount
        val roundedDownCrypto = cryptoAmount.value
            ?.setScale(0, RoundingMode.DOWN)
            ?.parseBigDecimal(0)
            .orEmpty()

        val isAmountZeroOrNull = if (amountState.amountTextField.isFiatValue) {
            amountState.amountTextField.fiatAmount.value.isNullOrZero()
        } else {
            amountState.amountTextField.cryptoAmount.value.isNullOrZero()
        }

        val errorText = when {
            amountState.amountTextField.isError -> amountState.amountTextField.error
            requirementError != null -> requirementError
            isIntegerOnlyError -> when (actionType) {
                is StakingActionCommonType.Enter -> resourceReference(
                    R.string.staking_amount_tron_integer_error,
                    wrappedList(roundedDownCrypto),
                )
                is StakingActionCommonType.Exit -> resourceReference(
                    R.string.staking_amount_tron_integer_error_unstaking,
                    wrappedList(roundedDownCrypto),
                )
                else -> null
            }
            else -> null
        }
        val isError = amountState.amountTextField.isError || requirementError != null
        return amountState.copy(
            isPrimaryButtonEnabled = !isAmountZeroOrNull && !isError,
            amountTextField = amountState.amountTextField.copy(
                isError = isError,
                isWarning = isIntegerOnlyError,
                error = errorText ?: amountState.amountTextField.error,
                keyboardOptions = amountState.amountTextField.keyboardOptions.copy(
                    imeAction = ImeAction.None,
                ),
            ),
        )
    }

    private fun getRequirementError(prevState: AmountState.Data): TextReference? {
        val amountDecimal = prevState.amountTextField.cryptoAmount.value ?: return null

        val isAlreadyErrorState = prevState.amountTextField.isError
        val isAmountZero = amountDecimal.isZero()

        if (isAlreadyErrorState || isAmountZero) return null

        return when (actionType) {
            is StakingActionCommonType.Enter -> {
                integration.enterArgs?.amountRequirement?.getError(
                    amount = amountDecimal,
                    minErrorRes = R.string.staking_amount_requirement_error,
                    maxErrorRes = R.string.staking_max_amount_requirement_error,
                )
            }
            is StakingActionCommonType.Exit -> {
                if (isSolanaUnstakeValidationEnabled &&
                    isSolana(cryptoCurrencyStatus.currency.network.rawId)
                ) {
                    getSolanaUnstakeError(amount = amountDecimal, staked = maxAmount.amount)
                } else {
                    integration.exitArgs?.amountRequirement?.getError(
                        amount = amountDecimal,
                        minErrorRes = R.string.staking_unstake_amount_requirement_error,
                        maxErrorRes = R.string.staking_max_amount_requirement_error,
                    )
                }
            }
            else -> null
        }
    }

    private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean {
        val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false

        val isEnterOrExit = actionType is StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit
        val isTron = isTron(cryptoCurrencyStatus.currency.network.rawId)

        val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero()

        return isEnterOrExit && isTron && !isIntegerOnly
    }

    private fun getSolanaUnstakeError(amount: BigDecimal, staked: BigDecimal?): TextReference? {
        val minimum = integration.exitMinimumAmount?.takeIf { it.isPositive() }
            ?: integration.enterMinimumAmount
        if (minimum == null || staked == null) return null

        // Full unstake is always allowed regardless of minimum delegation.
        if (amount.compareTo(staked) == 0) return null

        if (amount < minimum) {
            val formatted = minimum.format { crypto(cryptoCurrencyStatus.currency) }
            return resourceReference(
                R.string.staking_unstake_amount_requirement_error,
                wrappedList(formatted),
            )
        }

        if (staked - amount < minimum) {
            return resourceReference(R.string.staking_notification_low_staked_balance_text)
        }

        return null
    }

    private fun StakingAmountRequirement.getError(
        amount: BigDecimal,
        @StringRes minErrorRes: Int,
        @StringRes maxErrorRes: Int,
    ): TextReference? {
        if (!isRequired) return null

        val isExceedsMinRequirement = minimum?.compareTo(amount) == 1
        val effectiveMax = maximum?.takeIf { it.isPositive() } ?: maxAmount.amount
        val isExceedsMaxRequirement = effectiveMax?.compareTo(amount) == -1

        val (errorRes, boundary) = when {
            isExceedsMinRequirement -> minErrorRes to minimum
            isExceedsMaxRequirement -> maxErrorRes to effectiveMax
            else -> return null
        }

        val formatted = boundary.format { crypto(cryptoCurrencyStatus.currency) }
        return resourceReference(errorRes, wrappedList(formatted))
    }

    data class Data(
        val amountState: AmountState,
        val actionType: StakingActionCommonType,
    )
}