package com.tangem.features.yield.supply.impl.active.model

import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.pluralReference
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.format
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.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.common.routing.AppRoute
import com.tangem.domain.stories.models.StoryContentIds
import com.tangem.domain.yield.supply.models.YieldBoostStatus
import com.tangem.domain.yield.supply.promo.usecase.GetYieldBoostStatusUseCase
import com.tangem.domain.yield.supply.usecase.*
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.core.res.R as CoreResR
import com.tangem.features.yield.supply.impl.YieldBoostStoryPreloader
import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM
import com.tangem.features.yield.supply.impl.active.model.transformers.YieldSupplyActiveFeeContentTransformer
import com.tangem.features.yield.supply.impl.active.model.transformers.YieldSupplyActiveMinAmountTransformer
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.transformer.update
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import javax.inject.Inject

@Suppress("LongParameterList", "LargeClass")
@ModelScoped
internal class YieldSupplyActiveModel @Inject constructor(
    paramsContainer: ParamsContainer,
    override val dispatchers: CoroutineDispatcherProvider,
    private val analyticsHandler: AnalyticsEventHandler,
    private val yieldSupplyGetProtocolBalanceUseCase: YieldSupplyGetProtocolBalanceUseCase,
    private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
    private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
    private val yieldSupplyGetCurrentFeeUseCase: YieldSupplyGetCurrentFeeUseCase,
    private val yieldSupplyGetMaxFeeUseCase: YieldSupplyGetMaxFeeUseCase,
    private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
    private val getUserWalletUseCase: GetUserWalletUseCase,
    private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
    private val urlOpener: UrlOpener,
    private val appRouter: AppRouter,
    private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
    private val getYieldBoostStatusUseCase: GetYieldBoostStatusUseCase,
    private val boostStoryPreloader: YieldBoostStoryPreloader,
) : Model(), YieldSupplyStopEarningComponent.ModelCallback,
    YieldSupplyApproveComponent.ModelCallback {

    private val params: YieldSupplyActiveComponent.Params = paramsContainer.require()

    val slotNavigation = SlotNavigation<YieldSupplyActiveRoute>()

    lateinit var userWallet: UserWallet

    val cryptoCurrencyStatusFlow = MutableStateFlow(
        CryptoCurrencyStatus(
            value = CryptoCurrencyStatus.Loading,
            currency = params.cryptoCurrency,
        ),
    )

    val balanceHiddenFlow = MutableStateFlow(false)
    val transactionInProgressFlow: StateFlow<Boolean>
        field = MutableStateFlow(false)

    private val userWalletId = params.userWalletId
    private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency
    private var appCurrency = AppCurrency.Default

    val uiState: StateFlow<YieldSupplyActiveContentUM>
        field = MutableStateFlow(
            YieldSupplyActiveContentUM(
                totalEarnings = stringReference("0"),
                availableBalance = null,
                providerTitle = resourceReference(R.string.yield_module_provider),
                subtitle = resourceReference(
                    id = R.string.yield_module_earn_sheet_provider_description,
                    formatArgs = wrappedList(cryptoCurrency.symbol, AAVEV3_PREFIX + cryptoCurrency.symbol),
                ),
                subtitleLink = resourceReference(R.string.common_read_more),
                notifications = persistentListOf(),
                minAmount = null,
                currentFee = null,
                feeDescription = null,
                isHighFee = false,
                minFeeDescription = null,
            ),
        )

    init {
        analyticsHandler.send(
            YieldSupplyAnalytics.EarnInProgressScreen(
                token = cryptoCurrency.symbol,
                blockchain = cryptoCurrency.network.name,
            ),
        )
        subscribeOnCurrencyStatusUpdates()
        loadBoostBlock()
        modelScope.launch(dispatchers.io) { boostStoryPreloader.preload() }

        modelScope.launch(dispatchers.default) {
            appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
            val protocolBalance = yieldSupplyGetProtocolBalanceUseCase(
                userWalletId = userWalletId,
                cryptoCurrency = cryptoCurrency,
            ).getOrNull()

            stringReference(
                protocolBalance.format {
                    crypto(
                        symbol = AAVEV3_PREFIX + cryptoCurrency.symbol,
                        decimals = cryptoCurrency.decimals,
                    )
                },
            )
        }
    }

    override fun onDismissClick() {
        if (!transactionInProgressFlow.value) {
            slotNavigation.dismiss()
        }
    }

    override fun onTransactionProgress(inProgress: Boolean) {
        transactionInProgressFlow.update { inProgress }
    }

    override fun onStopEarningTransactionSent() {
        transactionInProgressFlow.update { false }
        appRouter.pop()
    }

    override fun onTransactionSent() {
        transactionInProgressFlow.update { false }
        appRouter.pop()
    }

    fun onApprove() {
        slotNavigation.activate(YieldSupplyActiveRoute.Approve)
    }

    fun onStopEarning() {
        slotNavigation.activate(YieldSupplyActiveRoute.Exit)
    }

    fun onReadMoreClick() {
        modelScope.launch {
            urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.HowYieldModeWorks))
        }
    }

    private fun subscribeOnCurrencyStatusUpdates() {
        modelScope.launch {
            getUserWalletUseCase(params.userWalletId).fold(
                ifRight = { wallet ->
                    userWallet = wallet

                    singleAccountStatusListSupplier(params.userWalletId)
                        .map { it.getCryptoCurrencyStatus(currency = cryptoCurrency) }
                        .distinctUntilChanged()
                        .onEach { maybeCryptoCurrency ->
                            maybeCryptoCurrency.fold(
                                ifSome = { cryptoCurrencyStatus ->
                                    cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus }

                                    val protocolBalance =
                                        cryptoCurrencyStatus.value.yieldSupplyStatus?.effectiveProtocolBalance
                                            ?: yieldSupplyGetProtocolBalanceUseCase(
                                                userWalletId = userWalletId,
                                                cryptoCurrency = cryptoCurrency,
                                            ).getOrNull()

                                    loadApy()
                                    loadMinAmount()
                                    loadFees()

                                    uiState.update {
                                        it.copy(
                                            availableBalance = stringReference(
                                                protocolBalance.format {
                                                    crypto(
                                                        symbol = AAVEV3_PREFIX + cryptoCurrency.symbol,
                                                        decimals = cryptoCurrency.decimals,
                                                    )
                                                },
                                            ),
                                        )
                                    }
                                },
                                ifEmpty = {
                                    TangemLogger.w("No currency status found: ${cryptoCurrency.id}")
                                },
                            )
                        }
                        .flowOn(dispatchers.default)
                        .launchIn(modelScope)
                },
                ifLeft = { error ->
                    TangemLogger.w(error.toString())
                    return@launch
                },
            )
        }
    }

    private fun loadBoostBlock() {
        modelScope.launch(dispatchers.io) {
            val token = cryptoCurrency as? CryptoCurrency.Token ?: return@launch
            val cached = getYieldBoostStatusUseCase(userWalletId).getOrNull()
            val status = if (cached is YieldBoostStatus.Enrolled && cached.matches(token)) {
                cached
            } else {
                getYieldBoostStatusUseCase(userWalletId, forceRefresh = true).getOrNull()
            }
            if (status !is YieldBoostStatus.Enrolled || !status.matches(token)) return@launch

            val state = resolveBoostBlockState(
                qualificationEndDate = status.qualificationEndDate,
                now = Clock.System.now(),
            )
            val boostText = when (state) {
                is BoostBlockState.DaysLeft -> buildDaysLeftText(state.days)
                BoostBlockState.AwaitingPayout -> resourceReference(CoreResR.string.yield_promo_completed)
                BoostBlockState.Hidden -> return@launch
            }
            uiState.update { it.copy(boostText = boostText, onBoostClick = ::onBoostClick) }
        }
    }

    private fun onBoostClick() {
        appRouter.push(
            AppRoute.Stories(
                storyId = StoryContentIds.STORY_FIRST_TIME_YIELD_PROMO.id,
                nextScreen = null,
                screenSource = "YieldActive",
                shouldMarkAsSeenOnClose = false,
            ),
        )
    }

    private fun buildDaysLeftText(daysLeft: Int): TextReference = combinedReference(
        pluralReference(
            id = CoreResR.plurals.common_days,
            count = daysLeft,
            formatArgs = wrappedList(daysLeft),
        ),
        stringReference(" "),
        resourceReference(CoreResR.string.yield_promo_left_title),
    )

    private fun YieldBoostStatus.Enrolled.matches(token: CryptoCurrency.Token): Boolean {
        val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
        return contractAddress.equals(token.contractAddress, ignoreCase = shouldIgnoreCase) &&
            networkId == token.network.rawId
    }

    private fun loadApy() {
        val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
        modelScope.launch(dispatchers.default) {
            yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken).onRight { tokenStatus ->
                uiState.update {
                    it.copy(
                        apy = TextReference.Str("${tokenStatus.apy}%"),
                    )
                }
            }.onLeft {
                uiState.update {
                    it.copy(
                        apy = TextReference.Str(DASH_SIGN),
                    )
                }
                TangemLogger.e("Error loading token status")
            }
        }
    }

    private fun loadMinAmount() {
        modelScope.launch(dispatchers.default) {
            yieldSupplyMinAmountUseCase(
                userWalletId,
                cryptoCurrencyStatusFlow.value,
            ).onRight { minAmount ->
                val dustAmount = yieldSupplyGetDustMinAmountUseCase(
                    minAmountTokenCurrency = minAmount,
                    appCurrency = appCurrency,
                    tokenCryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
                )
                uiState.update(
                    YieldSupplyActiveMinAmountTransformer(
                        cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
                        appCurrency = appCurrency,
                        minAmount = minAmount,
                        dustMinAmount = dustAmount,
                        analyticsHandler = analyticsHandler,
                        onApprove = ::onApprove,
                    ),
                )
            }.onLeft {
                uiState.update {
                    it.copy(
                        minAmount = TextReference.Str(DASH_SIGN),
                        feeDescription = null,
                    )
                }
            }
        }
    }

    private fun loadFees() {
        modelScope.launch(dispatchers.default) {
            val cryptoStatus = cryptoCurrencyStatusFlow.value

            val currentFee = yieldSupplyGetCurrentFeeUseCase(
                userWalletId = userWalletId,
                cryptoCurrencyStatus = cryptoStatus,
            ).getOrNull()

            val maxFee = yieldSupplyGetMaxFeeUseCase(
                userWalletId = userWalletId,
                cryptoCurrencyStatus = cryptoStatus,
            ).getOrNull()

            if (currentFee != null && maxFee != null) {
                uiState.update(
                    YieldSupplyActiveFeeContentTransformer(
                        cryptoCurrencyStatus = cryptoStatus,
                        appCurrency = appCurrency,
                        feeValue = currentFee.value,
                        maxNetworkFee = maxFee,
                        analyticsHandler = analyticsHandler,
                    ),
                )
            } else {
                uiState.update {
                    it.copy(
                        currentFee = stringReference(DASH_SIGN),
                        feeDescription = null,
                        isHighFee = false,
                    )
                }
            }
        }
    }

    private companion object {
        const val AAVEV3_PREFIX = "a"
    }
}