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

import arrow.core.getOrElse
import com.tangem.utils.logging.TangemLogger
import com.tangem.common.routing.AppRoute
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.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
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.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.hasNotSuppliedAmount
import com.tangem.domain.models.currency.shouldShowNotSuppliedNotification
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.stories.models.StoryContentIds
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
import com.tangem.domain.yield.supply.promo.usecase.GetBoostedApyUseCase
import com.tangem.domain.yield.supply.promo.usecase.IsYieldBoostPromoEnabledForTokenUseCase
import com.tangem.domain.yield.supply.usecase.*
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.YieldBoostStoryPreloader
import com.tangem.common.ui.earn.EarnBlockUM
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.features.yield.supply.impl.main.model.converter.YieldSupplyToEarnBlockConverter
import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.update
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlin.properties.Delegates

@Suppress("LongParameterList", "LargeClass")
@ModelScoped
internal class YieldSupplyModel @Inject constructor(
    paramsContainer: ParamsContainer,
    override val dispatchers: CoroutineDispatcherProvider,
    private val analyticsEventsHandler: AnalyticsEventHandler,
    private val appRouter: AppRouter,
    private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
    private val getUserWalletUseCase: GetUserWalletUseCase,
    private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
    private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
    private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
    private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
    private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
    private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
    private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
    private val yieldSupplyEnterStatusFlowUseCase: YieldSupplyEnterStatusFlowUseCase,
    private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
    private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
    private val isYieldBoostPromoEnabledForTokenUseCase: IsYieldBoostPromoEnabledForTokenUseCase,
    private val getBoostedApyUseCase: GetBoostedApyUseCase,
    private val boostStoryPreloader: YieldBoostStoryPreloader,
) : Model(), YieldSupplyClickIntents {

    private val earnBlockConverter = YieldSupplyToEarnBlockConverter()
    private val params = paramsContainer.require<YieldSupplyComponent.Params>()

    val uiStateLegacy: StateFlow<YieldSupplyUM>
        field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)

    val uiState: StateFlow<EarnBlockUM?> = uiStateLegacy
        .map(earnBlockConverter::convert)
        .stateIn(modelScope, SharingStarted.Eagerly, initialValue = null)

    private val cryptoCurrency = params.cryptoCurrency
    private var appCurrency: AppCurrency = AppCurrency.Default
    var userWallet: UserWallet by Delegates.notNull()
    private var latestCryptoCurrencyStatus: CryptoCurrencyStatus? = null

    private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true)

    init {
        checkIfYieldSupplyIsAvailable()
        modelScope.launch(dispatchers.io) { boostStoryPreloader.preload() }
    }

    private fun checkIfYieldSupplyIsAvailable() {
        modelScope.launch(dispatchers.default) {
            appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
            val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
            if (isAvailable) {
                loadUserWalletData()
                singleNetworkStatusFetcher(
                    params = SingleNetworkStatusFetcher.Params(
                        userWalletId = params.userWalletId,
                        network = cryptoCurrency.network,
                    ),
                )
            }
        }
    }

    private fun loadUserWalletData() {
        modelScope.launch {
            getUserWalletUseCase(params.userWalletId).fold(
                ifRight = { wallet ->
                    userWallet = wallet
                    subscribeOnCurrencyStatusUpdates()
                },
                ifLeft = { error ->
                    TangemLogger.w(error.toString())
                    return@launch
                },
            )
        }
    }

    private fun subscribeOnCurrencyStatusUpdates() {
        combine(
            flow = singleAccountStatusListSupplier(params.userWalletId).map {
                it.getCryptoCurrencyStatus(currency = cryptoCurrency)
            },
            flow2 = yieldSupplyEnterStatusFlowUseCase(
                userWalletId = params.userWalletId,
                cryptoCurrency = cryptoCurrency,
            ),
        ) { maybeCryptoCurrency, _ ->
            maybeCryptoCurrency
        }
            .flowOn(dispatchers.io)
            .distinctUntilChanged()
            .onEach { maybeCryptoCurrency ->
                maybeCryptoCurrency.fold(
                    ifSome = { cryptoCurrencyStatus ->
                        latestCryptoCurrencyStatus = cryptoCurrencyStatus
                        if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) {
                            sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
                        }
                        onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus)
                    },
                    ifEmpty = {
                        TangemLogger.w("Unable to get crypto currency status: ${cryptoCurrency.id}")
                    },
                )
            }
            .launchIn(modelScope)
    }

    private suspend fun loadTokenStatus() {
        val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
        yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
            .onRight { tokenStatus ->
                val isPromoEnabled = isYieldBoostPromoEnabledForTokenUseCase(params.userWalletId, cryptoCurrencyToken)
                    .getOrElse { false }
                val boostedApy = if (isPromoEnabled) getBoostedApyUseCase(tokenStatus.apy) else null
                uiStateLegacy.update(
                    YieldSupplyTokenStatusSuccessTransformer(
                        tokenStatus = tokenStatus,
                        onStartEarningClick = ::onStartEarningClick,
                        onLearnMoreClick = ::onLearnMoreClick,
                        boostedApy = boostedApy,
                    ),
                )
            }.onLeft { error ->
                TangemLogger.e("Error", error)
                uiStateLegacy.update { YieldSupplyUM.Initial }
            }
    }

    override fun onStartEarningClick() {
        navigateToYieldSupplyEntry()
    }

    override fun onActiveClick() {
        navigateToYieldSupplyEntry()
    }

    override fun onLearnMoreClick() {
        appRouter.push(
            AppRoute.Stories(
                storyId = StoryContentIds.STORY_FIRST_TIME_YIELD_PROMO.id,
                nextScreen = buildYieldEntryRoute(),
                screenSource = "TokenDetails",
                shouldMarkAsSeenOnClose = false,
            ),
        )
    }

    private fun navigateToYieldSupplyEntry() {
        val route = buildYieldEntryRoute() ?: return
        appRouter.push(route)
    }

    private fun buildYieldEntryRoute(): AppRoute.YieldSupplyEntry? {
        val cryptoCurrencyStatus = latestCryptoCurrencyStatus ?: return null
        val apy = when (val yieldSupplyUM = uiStateLegacy.value) {
            is YieldSupplyUM.Available -> yieldSupplyUM.apy
            is YieldSupplyUM.Content -> yieldSupplyUM.apy
            else -> ""
        }
        return AppRoute.YieldSupplyEntry(
            userWalletId = params.userWalletId,
            cryptoCurrency = cryptoCurrencyStatus.currency,
            apy = apy,
        )
    }

    private suspend fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) {
        val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL
        val isProcessing = uiStateLegacy.value is YieldSupplyUM.Processing
        if (isCryptoCurrencyStatusFromCache && isProcessing) {
            return
        }

        val pendingStatus = yieldSupplyEnterStatusUseCase(
            userWalletId = userWallet.walletId,
            cryptoCurrencyStatus = cryptoCurrencyStatus,
        ).getOrNull()

        if (pendingStatus != null) {
            showProcessing(pendingStatus)
        } else {
            loadStatus(cryptoCurrencyStatus)
        }
    }

    private fun showProcessing(status: YieldSupplyPendingStatus) {
        uiStateLegacy.update {
            when (status) {
                is YieldSupplyPendingStatus.Enter -> YieldSupplyUM.Processing.Enter
                is YieldSupplyPendingStatus.Exit -> YieldSupplyUM.Processing.Exit
            }
        }
    }

    private suspend fun loadStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
        val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
        if (yieldSupplyStatus?.isActive == true) {
            loadActiveState(
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                yieldSupplyStatus = yieldSupplyStatus,
            )
        } else {
            loadTokenStatus()
        }
    }

    private suspend fun loadActiveState(
        cryptoCurrencyStatus: CryptoCurrencyStatus,
        yieldSupplyStatus: YieldSupplyStatus,
    ) {
        val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
        val shouldShowWarningIcon = !yieldSupplyStatus.isAllowedToSpend
        val isShowInfoIconPrevState = when (val state = uiStateLegacy.value) {
            is YieldSupplyUM.Content -> state.shouldShowInfoIcon
            else -> false
        }
        if (!yieldSupplyStatus.isAllowedToSpend) {
            analyticsEventsHandler.send(
                YieldSupplyAnalytics.NoticeApproveNeeded(
                    token = cryptoCurrency.symbol,
                    blockchain = cryptoCurrency.network.name,
                ),
            )
        }
        yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
            .onRight { tokenStatus ->
                uiStateLegacy.update {
                    YieldSupplyUM.Content(
                        title = resourceReference(
                            R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
                        ),
                        subtitle = resourceReference(
                            R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle,
                        ),
                        rewardsApy = combinedReference(
                            resourceReference(
                                R.string.yield_module_token_details_earn_notification_apy,
                            ),
                            stringReference(" ${tokenStatus.apy}%"),
                        ),
                        onClick = ::onActiveClick,
                        shouldShowWarningIcon = shouldShowWarningIcon,
                        shouldShowInfoIcon = isShowInfoIconPrevState,
                        apy = tokenStatus.apy.toString(),
                    )
                }
                computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
            }.onLeft { t ->
                TangemLogger.e("Error", t)
                uiStateLegacy.update {
                    YieldSupplyUM.Content(
                        title = resourceReference(
                            R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
                        ),
                        subtitle = resourceReference(
                            R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle,
                        ),
                        rewardsApy = TextReference.EMPTY,
                        onClick = ::onActiveClick,
                        shouldShowWarningIcon = shouldShowWarningIcon,
                        shouldShowInfoIcon = isShowInfoIconPrevState,
                        apy = "",
                    )
                }
                computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
            }
    }

    private fun computeAndApplyShowInfoIcon(cryptoCurrencyStatus: CryptoCurrencyStatus) {
        modelScope.launch(dispatchers.default) {
            val isShowInfoIcon = if (cryptoCurrencyStatus.hasNotSuppliedAmount()) {
                val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus)
                    .getOrNull()
                if (minAmount != null) {
                    val dustAmount = yieldSupplyGetDustMinAmountUseCase(
                        minAmountTokenCurrency = minAmount,
                        appCurrency = appCurrency,
                        tokenCryptoCurrencyStatus = cryptoCurrencyStatus,
                    )
                    cryptoCurrencyStatus.shouldShowNotSuppliedNotification(dustAmount)
                } else {
                    false
                }
            } else {
                false
            }
            uiStateLegacy.update { state ->
                when (state) {
                    is YieldSupplyUM.Content -> state.copy(shouldShowInfoIcon = isShowInfoIcon)
                    else -> state
                }
            }
        }
    }

    private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
        val token = cryptoCurrency as? CryptoCurrency.Token ?: return
        val address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: return
        modelScope.launch(dispatchers.default) {
            if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true) {
                yieldSupplyActivateUseCase(
                    userWalletId = userWallet.walletId,
                    cryptoCurrency = token,
                    address = address,
                )
            } else {
                yieldSupplyDeactivateUseCase(token, address)
            }
        }
    }
}