package com.tangem.features.send.feeselector.model

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.getOrElse
import arrow.core.left
import arrow.core.raise.either
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCryptoCurrencyStatus
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.domain.transaction.usecase.gasless.GetAvailableFeeTokensUseCase
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.NonceInserted
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeItem
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeNonce
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
import com.tangem.features.send.api.subcomponents.feeSelector.params.FeeSelectorParams
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorCheckReloadListener
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorCheckReloadTrigger
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadListener
import com.tangem.features.send.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents
import com.tangem.features.send.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents.GasPriceInserter
import com.tangem.features.send.feeselector.model.transformers.FeeItemSelectedTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorCustomValueChangedTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorErrorTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorLoadedTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorLoadingTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorNonceChangeTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorRemoveSuggestedTransformer
import com.tangem.features.send.feeselector.model.transformers.FeeSelectorTokenSelectedTransformer
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.transformer.update
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import com.tangem.utils.logging.TangemLogger

@Suppress("LongParameterList")
internal class FeeSelectorLogic @AssistedInject constructor(
    @Assisted private val params: FeeSelectorParams,
    @Assisted private val modelScope: CoroutineScope,
    private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
    private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
    private val feeSelectorReloadListener: FeeSelectorReloadListener,
    private val feeSelectorCheckReloadListener: FeeSelectorCheckReloadListener,
    private val feeSelectorCheckReloadTrigger: FeeSelectorCheckReloadTrigger,
    private val feeSelectorAlertFactory: FeeSelectorAlertFactory,
    private val analyticsEventHandler: AnalyticsEventHandler,
    private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
    private val getUserWalletUseCase: GetUserWalletUseCase,
    private val getAvailableFeeTokensUseCase: GetAvailableFeeTokensUseCase,
    isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
) : FeeSelectorIntents {

    private var appCurrency: AppCurrency = AppCurrency.Default
    private val loadFeeJobHolder = JobHolder()
    val uiState = MutableStateFlow(params.state)

    val isGaslessEnabled = params.onLoadFeeExtended != null &&
        isGaslessFeeSupportedForNetwork(params.feeCryptoCurrencyStatus.currency.network) &&
        params.cryptoCurrencyStatus.currency is CryptoCurrency.Token

    val shouldShowOnlySpeedOption: StateFlow<Boolean>
        field = MutableStateFlow(params.shouldShowOnlySpeedOption)

    init {
        initAppCurrency()
        subscribeOnFeeReloadTriggerUpdates()
        subscribeOnFeeCheckReloadTriggerUpdates()
        subscribeOnFeeLoadingStateTriggerUpdates()
        loadFee()
    }

    private fun initAppCurrency() {
        modelScope.launch {
            appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
        }
    }

    private fun loadFee(isReload: Boolean = false) {
        if (uiState.value is FeeSelectorUM.Error && !isReload) {
            return
        }

        if (uiState.value !is FeeSelectorUM.Content) {
            uiState.update(FeeSelectorLoadingTransformer)
        }

        modelScope.launch {
            callLoadFee()
                .fold(
                    ifLeft = { error -> uiState.update(FeeSelectorErrorTransformer(error)) },
                    ifRight = { fee ->
                        uiState.update(
                            FeeSelectorLoadedTransformer(
                                cryptoCurrencyStatus = params.cryptoCurrencyStatus,
                                feeCryptoCurrencyStatus = fee.selectedTokenOrNull ?: params.feeCryptoCurrencyStatus,
                                appCurrency = appCurrency,
                                fees = fee,
                                feeStateConfiguration = params.feeStateConfiguration,
                                isFeeApproximate = isFeeApproximate(fee.transactionFee.normal.amount.type),
                                feeSelectorIntents = this@FeeSelectorLogic,
                                shouldDisableCustomFee = params.onDisableCustomFee(),
                            ),
                        )
                    },
                )
        }.saveIn(loadFeeJobHolder)
    }

    private fun isFeeApproximate(amountType: AmountType): Boolean {
        val networkId = params.feeCryptoCurrencyStatus.currency.network.id
        return isFeeApproximateUseCase(networkId = networkId, amountType = amountType)
    }

    override fun onFeeItemSelected(feeItem: FeeItem) {
        if (feeItem is FeeItem.Custom) {
            analyticsEventHandler.send(
                CommonSendFeeAnalyticEvents.CustomFeeButtonClicked(
                    categoryName = params.analyticsCategoryName,
                    blockchain = params.cryptoCurrencyStatus.currency.network.name,
                    token = params.cryptoCurrencyStatus.currency.symbol,
                ),
            )
        }
        uiState.update(FeeItemSelectedTransformer(feeItem))
    }

    override fun onTokenSelected(status: CryptoCurrencyStatus) {
        uiState.update(FeeSelectorTokenSelectedTransformer(status))
        loadFee()
    }

    override fun onCustomFeeValueChange(index: Int, value: String) {
        uiState.update(
            FeeSelectorCustomValueChangedTransformer(
                index = index,
                value = value,
                intents = this,
                appCurrency = appCurrency,
                feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
            ),
        )
    }

    override fun onNonceChange(value: String) {
        uiState.update(FeeSelectorNonceChangeTransformer(value = value))
    }

    override fun onDoneClick() {
        val feeSelectorUM = uiState.value as? FeeSelectorUM.Content ?: return
        analyticsEventHandler.send(
            CommonSendFeeAnalyticEvents.SelectedFee(
                categoryName = params.analyticsCategoryName,
                feeType = feeSelectorUM.toAnalyticType(),
                source = params.analyticsSendSource,
                feeToken = feeSelectorUM.feeExtraInfo.feeCryptoCurrencyStatus.currency.symbol,
                blockchain = params.cryptoCurrencyStatus.currency.network.name,
            ),
        )
        val isCustomFeeEdited = feeSelectorUM.selectedFeeItem.fee.amount.value != feeSelectorUM.fees.normal.amount.value
        if (feeSelectorUM.selectedFeeItem is FeeItem.Custom && isCustomFeeEdited) {
            analyticsEventHandler.send(GasPriceInserter(categoryName = params.analyticsCategoryName))
        }
        val nonceField = feeSelectorUM.feeNonce as? FeeNonce.Nonce
        if (nonceField != null && nonceField.nonce != null) {
            analyticsEventHandler.send(
                NonceInserted(
                    categoryName = params.analyticsCategoryName,
                    token = params.feeCryptoCurrencyStatus.currency.symbol,
                    blockchain = params.feeCryptoCurrencyStatus.currency.network.name,
                ),
            )
        }
    }

    private fun subscribeOnFeeReloadTriggerUpdates() {
        feeSelectorReloadListener.reloadTriggerFlow
            .onEach { data ->
                if (data.isRemoveSuggestedFee) {
                    uiState.update(FeeSelectorRemoveSuggestedTransformer)
                }
                loadFee(isReload = true)
            }
            .launchIn(modelScope)
    }

    private fun subscribeOnFeeLoadingStateTriggerUpdates() {
        feeSelectorReloadListener.loadingStateTriggerFlow
            .onEach { uiState.update(FeeSelectorLoadingTransformer) }
            .launchIn(modelScope)
    }

    private fun subscribeOnFeeCheckReloadTriggerUpdates() {
        feeSelectorCheckReloadListener.checkReloadTriggerFlow
            .onEach { checkLoadFee() }
            .launchIn(modelScope)
    }

    private fun checkLoadFee() {
        modelScope.launch {
            callLoadFee().fold(
                ifRight = { newFee ->
                    feeSelectorAlertFactory.getFeeUpdatedAlert(
                        newTransactionFee = newFee.transactionFee,
                        feeSelectorUM = uiState.value,
                        proceedAction = {
                            modelScope.launch {
                                feeSelectorCheckReloadTrigger.callbackCheckResult(true)
                            }
                        },
                        stopAction = {
                            modelScope.launch {
                                feeSelectorCheckReloadTrigger.callbackCheckResult(false)
                            }
                        },
                    )
                },
                ifLeft = { _ ->
                    feeSelectorCheckReloadTrigger.callbackCheckResult(false)
                    feeSelectorAlertFactory.getFeeUnreachableErrorState { loadFee(isReload = true) }
                },
            )
        }
    }

    @Suppress("UnreachableCode")
    private suspend fun callLoadFee(): Either<GetFeeError, LoadedFeeResult> {
        val extended = params.onLoadFeeExtended
        if (extended == null || !isGaslessEnabled) {
            return params.onLoadFee().map { LoadedFeeResult.Basic(it) }
        }

        val selectedTokenOrNull = (uiState.value as? FeeSelectorUM.Content)?.feeExtraInfo?.feeCryptoCurrencyStatus

        if (selectedTokenOrNull?.currency is CryptoCurrency.Coin) {
            return params.onLoadFee().flatMap { loadedFee ->
                val feeExtended = TransactionFeeExtended(
                    transactionFee = loadedFee,
                    feeTokenId = selectedTokenOrNull.currency.id,
                )
                populateExtendedFee(feeExtended)
            }
        }

        return extended(selectedTokenOrNull).fold(
            ifLeft = { error ->
                when (error) {
                    is GetFeeError.GaslessError.NotEnoughFunds -> error.left()
                    is GetFeeError.GaslessError -> {
                        // Something wrong with gasless fee, fallback to basic fee
                        shouldShowOnlySpeedOption.value = true
                        params.onLoadFee().map { LoadedFeeResult.Basic(it) }
                    }
                    else -> error.left()
                }
            },
            ifRight = { fee ->
                shouldShowOnlySpeedOption.value = false
                populateExtendedFee(fee)
            },
        )
    }

    private suspend fun populateExtendedFee(
        fee: TransactionFeeExtended,
    ): Either<GetFeeError, LoadedFeeResult.Extended> = either {
        val selectedToken = getSelectedTokenStatus(fee.feeTokenId).bind()
        val availableTokens = getAvailableFeeTokens().fold(
            ifLeft = { error ->
                TangemLogger.e("Failed to get available fee tokens: $error")
                if (selectedToken.currency !is CryptoCurrency.Coin) {
                    raise(error)
                }
                emptyList()
            },
            ifRight = { it },
        )
        LoadedFeeResult.Extended(
            fee = fee,
            selectedToken = selectedToken,
            availableTokens = availableTokens,
        )
    }

    private suspend fun getSelectedTokenStatus(tokenId: CryptoCurrency.ID): Either<GetFeeError, CryptoCurrencyStatus> =
        either {
            if (params.feeCryptoCurrencyStatus.currency.id != tokenId) {
                singleAccountStatusListSupplier.getSyncOrNull(params.userWalletId)
                    .getCryptoCurrencyStatus(currencyId = tokenId, network = null)
                    .getOrElse {
                        raise(GetFeeError.DataError(IllegalStateException("No token found for id: $tokenId")))
                    }
            } else {
                params.feeCryptoCurrencyStatus
            }
        }

    private suspend fun getAvailableFeeTokens(): Either<GetFeeError, List<CryptoCurrencyStatus>> = either {
        val userWallet = getUserWalletUseCase(params.userWalletId).mapLeft {
            GetFeeError.DataError(IllegalStateException("No wallet found for id: ${params.userWalletId}"))
        }.bind()

        getAvailableFeeTokensUseCase.invoke(
            userWallet = userWallet,
            network = params.cryptoCurrencyStatus.currency.network,
        ).bind()
    }

    sealed class LoadedFeeResult {
        data class Extended(
            val fee: TransactionFeeExtended,
            val selectedToken: CryptoCurrencyStatus?,
            val availableTokens: List<CryptoCurrencyStatus>,
        ) : LoadedFeeResult()

        data class Basic(val fee: TransactionFee) : LoadedFeeResult()

        val selectedTokenOrNull: CryptoCurrencyStatus?
            get() = when (this) {
                is Extended -> selectedToken
                is Basic -> null
            }

        val transactionFee: TransactionFee
            get() = when (this) {
                is Extended -> fee.transactionFee
                is Basic -> fee
            }
    }

    @AssistedFactory
    interface Factory {
        fun create(params: FeeSelectorParams, modelScope: CoroutineScope): FeeSelectorLogic
    }
}