package com.tangem.features.swap.v2.impl.amount.model

import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.notifications.NotificationId
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.utils.parseBigDecimal
import com.tangem.datasource.local.swap.SwapBestRateAnimationStore
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.notifications.ShouldShowNotificationUseCase
import com.tangem.domain.quotes.GetCurrencyUSDQuoteUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.swap.models.*
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.transaction.models.AllowanceInfo
import com.tangem.domain.transaction.usecase.GetAllowanceInfoUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.send.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkListener
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent.SwapChooseProviderConfig
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceListener
import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
import com.tangem.features.swap.v2.impl.amount.analytics.SwapAmountAnalyticEvents
import com.tangem.features.swap.v2.impl.amount.analytics.SwapAmountAnalyticsSender
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapQuoteUMConverter
import com.tangem.features.swap.v2.impl.amount.model.transformers.*
import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent
import com.tangem.features.swap.v2.impl.common.SwapAlertFactory
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents.NoticeFixedRate.toAnalyticsRateType
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.coroutines.PeriodicTask
import com.tangem.utils.coroutines.SingleTaskScheduler
import com.tangem.utils.extensions.orZero
import com.tangem.utils.isNullOrZero
import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.transformer.update
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.util.Locale
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate

@Suppress("LargeClass", "LongParameterList")
@ModelScoped
internal class SwapAmountModel @Inject constructor(
    paramsContainer: ParamsContainer,
    override val dispatchers: CoroutineDispatcherProvider,
    private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
    private val selectInitialPairUseCase: SelectInitialPairUseCase,
    private val getSwapQuoteUseCase: GetSwapQuoteUseCase,
    private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
    private val getAllowanceInfoUseCase: GetAllowanceInfoUseCase,
    private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
    private val getUserCountryUseCase: GetUserCountryUseCase,
    private val swapBestRateAnimationStore: SwapBestRateAnimationStore,
    private val appRouter: AppRouter,
    private val swapAmountAlertFactory: SwapAmountAlertFactory,
    private val swapAlertFactory: SwapAlertFactory,
    private val swapAmountUpdateListener: SwapAmountUpdateListener,
    private val swapAmountReduceListener: SwapAmountReduceListener,
    private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
    private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase,
    private val analyticsEventHandler: AnalyticsEventHandler,
    private val getWalletsUseCase: GetWalletsUseCase,
    private val getCurrencyUSDQuoteUseCase: GetCurrencyUSDQuoteUseCase,
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {

    private val params: SwapAmountComponentParams = paramsContainer.require()
    private val swapDirection = params.swapDirection
    private var appCurrency = AppCurrency.Default
    private val userWallet = params.userWallet

    private val primaryCryptoCurrency: CryptoCurrency = params.primaryCryptoCurrencyStatusFlow.value.currency

    private var primaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
    private var primaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
    private var secondaryMaximumAmountBoundary: EnterAmountBoundary? = null
    private var secondaryMinimumAmountBoundary: EnterAmountBoundary? = null

    private var primaryFiatRateUSD: BigDecimal? = null
    private var secondaryFiatRateUSD: BigDecimal? = null

    private var userCountry: UserCountry = UserCountry.Other(Locale.getDefault().country)
    val bottomSheetNavigation: SlotNavigation<SwapChooseProviderConfig> = SlotNavigation()
    val rateInfoNavigation: SlotNavigation<ExpressRateType> = SlotNavigation()

    private var isShowBestRateAnimation: Boolean = false
    private var isAmountScreenOpenedSent: Boolean = false
    private val amountAnalyticsSender = SwapAmountAnalyticsSender(analyticsEventHandler)

    private var autoUpdateSubscriberJob: Job? = null

    val uiState: StateFlow<SwapAmountUM>
        field = MutableStateFlow(params.amountUM)

    private val amountDebouncer = Debouncer()
    private val quoteTaskScheduler = SingleTaskScheduler<Unit>()

    init {
        modelScope.launch {
            appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
            userCountry = getUserCountryUseCase.invokeSync().getOrNull()
                ?: UserCountry.Other(Locale.getDefault().country)
            isShowBestRateAnimation = swapBestRateAnimationStore.getSyncOrNull()
        }
        subscribeOnCryptoCurrencyStatusFlow()
        subscribeOnAmountUpdateTriggerUpdates()
        observeChooseSelectToken()
        subscribeOnAmountReduceToTriggerUpdates()
        subscribeOnAmountReduceByTriggerUpdates()
        subscribeOnAmountIgnoreReduceTriggerUpdates()
        subscribeOnReloadQuotesTriggerUpdates()
        subscribeOnBalanceHiddenUpdates()
    }

    fun onStart() {
        val initialDelay = if (params is SwapAmountComponentParams.AmountBlockParams) {
            BLOCK_INITIAL_QUOTE_DELAY
        } else {
            QUOTES_UPDATE_DELAY
        }
        quoteTaskScheduler.scheduleTask(
            scope = modelScope,
            task = loadQuotesTask(initialDelay = initialDelay),
        )
        subscribeOnAutoupdateEnabling()
    }

    fun onStop() {
        quoteTaskScheduler.cancelTask()
        autoUpdateSubscriberJob?.cancel()
    }

    override fun onDestroy() {
        quoteTaskScheduler.cancelTask()
        super.onDestroy()
    }

    fun updateState(amountUM: SwapAmountUM) {
        uiState.update { amountUM }
    }

    override fun onProviderResult(quoteUM: SwapQuoteUM) {
        analyticsEventHandler.send(
            SwapAmountAnalyticEvents.ProviderChosen(
                categoryName = params.analyticsCategoryName,
                providerName = quoteUM.provider?.name.orEmpty(),
            ),
        )
        uiState.transformerUpdate(
            SwapAmountSelectQuoteTransformer(
                quoteUM = quoteUM,
                secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
                secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
                isNeedApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
                isBalanceHidden = params.isBalanceHidingFlow.value,
                primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
                primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                primaryFiatRateUSD = primaryFiatRateUSD,
                secondaryFiatRateUSD = secondaryFiatRateUSD,
            ),
        )
    }

    override fun onExpandEditField(selectedAmountType: SwapAmountType) {
        val content = uiState.value as? SwapAmountUM.Content ?: return

        val newSwapRateType = when (selectedAmountType) {
            SwapAmountType.To -> {
                if (content.swapRateMode != SwapRateMode.FLOAT_ONLY) {
                    ExpressRateType.Fixed
                } else {
                    onSelectTokenClick()
                    return
                }
            }
            SwapAmountType.From -> {
                if (content.swapRateMode == SwapRateMode.FIXED_ONLY) {
                    ExpressRateType.Fixed
                } else if (content.swapRateMode == SwapRateMode.FLOAT_AND_FIXED) {
                    ExpressRateType.Float
                } else {
                    return
                }
            }
        }

        uiState.transformerUpdate(
            SwapAmountChangeAmountTypeTransformer(
                selectedAmountType = selectedAmountType,
                swapRateType = newSwapRateType,
                isBalanceHidden = params.isBalanceHidingFlow.value,
            ),
        )
        startLoadingQuotesTask(isSilentReload = false)
    }

    override fun onInfoClick() {
        val amountUM = uiState.value as? SwapAmountUM.Content ?: return
        val selectedProvider = amountUM.selectedQuote.provider ?: return

        swapAmountAlertFactory.priceImpactAlert(
            hasPriceImpact = amountUM.priceImpact != null,
            currencySymbol = amountUM.primaryCryptoCurrencyStatus.currency.symbol,
            provider = selectedProvider,
        )
    }

    override fun onAmountValueChange(value: String) {
        uiState.transformerUpdate(
            SwapAmountValueChangeTransformer(
                primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
                secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
                primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
                value = value,
                isBalanceHidden = params.isBalanceHidingFlow.value,
            ),
        )
        quoteTaskScheduler.cancelTask()
        amountDebouncer.debounce(
            coroutineScope = modelScope,
            waitMs = DEBOUNCE_AMOUNT_DELAY,
            destinationFunction = {
                startLoadingQuotesTask(isSilentReload = false)
            },
        )
    }

    override fun onAmountPasteTriggerDismiss() {
        uiState.transformerUpdate(SwapAmountPasteTransformer)
    }

    override fun onMaxValueClick() {
        analyticsEventHandler.send(
            CommonSendAmountAnalyticEvents.MaxAmountButtonClicked(
                categoryName = params.analyticsCategoryName,
                token = primaryCryptoCurrency.symbol,
                blockchain = primaryCryptoCurrency.network.name,
                source = params.analyticsSendSource,
            ),
        )
        uiState.transformerUpdate(
            SwapAmountValueMaxTransformer(
                primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
                secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
                primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
            ),
        )
        startLoadingQuotesTask(isSilentReload = false)
    }

    override fun onCurrencyChangeClick(isFiat: Boolean) {
        uiState.transformerUpdate(
            SwapAmountChangeCurrencyTransformer(
                isFiatSelected = isFiat,
            ),
        )
    }

    override fun onAmountNext() {
        val amountState = uiState.value.swapDirection.withSwapDirection(
            onDirect = { uiState.value.primaryAmount.amountField },
            onReverse = { uiState.value.secondaryAmount.amountField },
        ) as? AmountState.Data

        amountState?.amountTextField?.isFiatValue?.let { isFiatSelected ->
            analyticsEventHandler.send(
                CommonSendAmountAnalyticEvents.SelectedCurrency(
                    categoryName = params.analyticsCategoryName,
                    type = if (isFiatSelected) {
                        CommonSendAmountAnalyticEvents.SelectedCurrencyType.AppCurrency
                    } else {
                        CommonSendAmountAnalyticEvents.SelectedCurrencyType.Token
                    },
                ),
            )
        }
        saveResult()
    }

    override fun onSelectTokenClick() {
        val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
        modelScope.launch {
            val isShowSendViaSwapNotification = shouldShowNotificationUseCase(
                NotificationId.SendViaSwapTokenSelectorNotification.key,
            )
            val isEditMode = amountParams.route.isEditMode
            val selectedCurrency = (uiState.value as? SwapAmountUM.Content)?.secondaryCryptoCurrencyStatus?.currency
            appRouter.push(
                AppRoute.ChooseManagedTokens(
                    userWalletId = userWallet.walletId,
                    initialCurrency = primaryCryptoCurrency,
                    selectedCurrency = selectedCurrency.takeIf { isEditMode },
                    source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
                    shouldShowSendViaSwapNotification = isShowSendViaSwapNotification,
                    analyticsCategoryName = params.analyticsCategoryName,
                ),
            )
        }
    }

    override fun onRateClick() {
        val content = uiState.value as? SwapAmountUM.Content ?: return
        rateInfoNavigation.activate(content.swapRateType)
        val event = when (content.swapRateType) {
            ExpressRateType.Float -> SendWithSwapAnalyticEvents.NoticeFloatRate
            ExpressRateType.Fixed -> SendWithSwapAnalyticEvents.NoticeFixedRate
        }
        analyticsEventHandler.send(event)
    }

    override fun onSeparatorClick() {
        val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return

        modelScope.launch {
            if (amountParams.route.isEditMode) {
                swapAmountAlertFactory.showCloseSendWithSwapAlert {
                    params.callback.resetSendWithSwapNavigation(resetNavigation = true)
                    confirmSendWithSwapClose()
                }
            } else {
                confirmSendWithSwapClose()
                amountParams.callback.onAmountResult(uiState.value)
            }
        }
    }

    fun onFinishAnimation() {
        uiState.update {
            (it as? SwapAmountUM.Content)?.copy(isShowBestRateAnimation = false) ?: it
        }
    }

    override fun onProviderClick() {
        val amountUM = uiState.value as? SwapAmountUM.Content ?: return
        val selectedProvider = amountUM.selectedQuote.provider ?: return
        val secondaryStatus = amountUM.secondaryCryptoCurrencyStatus ?: return

        val (fromCryptoCurrency, toCryptoCurrency) = amountUM.swapDirection.withSwapDirection(
            onDirect = { amountUM.primaryCryptoCurrencyStatus.currency to secondaryStatus.currency },
            onReverse = { secondaryStatus.currency to amountUM.primaryCryptoCurrencyStatus.currency },
        )

        analyticsEventHandler.send(
            SwapAmountAnalyticEvents.ProviderSelectorClicked(
                categoryName = params.analyticsCategoryName,
            ),
        )

        bottomSheetNavigation.activate(
            SwapChooseProviderConfig(
                providers = amountUM.swapQuotes,
                fromCryptoCurrency = fromCryptoCurrency,
                toCryptoCurrency = toCryptoCurrency,
                amountType = amountUM.selectedAmountType,
                selectedProvider = selectedProvider,
                userCountry = userCountry,
            ),
        )
    }

    private fun subscribeOnBalanceHiddenUpdates() {
        params.isBalanceHidingFlow.onEach { isHidden ->
            uiState.transformerUpdate(
                SwapAmountBalanceHiddenTransformer(
                    isBalanceHidden = isHidden,
                ),
            )
        }.launchIn(modelScope)
    }

    private fun confirmSendWithSwapClose() {
        val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
        val amountField = uiState.value.swapDirection.withSwapDirection(
            onDirect = { uiState.value.primaryAmount },
            onReverse = { uiState.value.secondaryAmount },
        ).amountField as? AmountState.Data

        val primaryCryptoCurrencyStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
        if (primaryCryptoCurrencyStatus != null) {
            val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
            uiState.transformerUpdate(
                SwapAmountPrimaryReadyStateTransformer(
                    userWallet = userWallet,
                    primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
                    appCurrency = appCurrency,
                    swapDirection = swapDirection,
                    clickIntents = this,
                    isBalanceHidden = params.isBalanceHidingFlow.value,
                    isShowBestRateAnimation = isShowBestRateAnimation,
                    isSingleWallet = isOnlyOneWallet,
                    isAccountsMode = params.isAccountModeFlow.value,
                    account = params.accountFlow.value,
                ),
            )
        }

        val isEnterInFiatSelected = amountField?.amountTextField?.isFiatValue == true
        val lastAmount = if (isEnterInFiatSelected) {
            amountField.amountTextField.fiatValue
        } else {
            amountField?.amountTextField?.value
        }
        amountParams.callback.onSeparatorClick(
            lastAmount = lastAmount.orEmpty(),
            isEnterInFiatSelected = isEnterInFiatSelected,
        )
    }

    private fun subscribeOnCryptoCurrencyStatusFlow() {
        combine(
            flow = params.primaryCryptoCurrencyStatusFlow.distinctUntilChanged { old, new ->
                old.value.amount == new.value.amount
            }, // Check only balance changes,
            flow2 = params.accountFlow,
            flow3 = params.isAccountModeFlow,
        ) { primaryCurrencyStatus, account, isAccountsMode ->
            val secondaryStatus = (uiState.value as? SwapAmountUM.Content)?.secondaryCryptoCurrencyStatus
            initCurrencies(
                primaryStatus = primaryCurrencyStatus,
                secondaryStatus = secondaryStatus,
            )
            if (secondaryStatus != null) {
                uiState.transformerUpdate(
                    SwapAmountUpdateBalanceTransformer(
                        cryptoCurrencyStatus = primaryCurrencyStatus,
                        primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
                        primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                    ),
                )
            } else {
                val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
                uiState.transformerUpdate(
                    SwapAmountPrimaryReadyStateTransformer(
                        userWallet = userWallet,
                        primaryCryptoCurrencyStatus = primaryCurrencyStatus,
                        appCurrency = appCurrency,
                        swapDirection = swapDirection,
                        clickIntents = this,
                        isBalanceHidden = params.isBalanceHidingFlow.value,
                        isShowBestRateAnimation = isShowBestRateAnimation,
                        isSingleWallet = isOnlyOneWallet,
                        isAccountsMode = isAccountsMode,
                        account = account,
                    ),
                )
            }
        }.flowOn(dispatchers.default)
            .launchIn(modelScope)
    }

    private fun subscribeOnAmountUpdateTriggerUpdates() {
        swapAmountUpdateListener.updateAmountTriggerFlow
            .onEach { (amount, isEnterInFiat) ->
                if (uiState.value is SwapAmountUM.Content) {
                    onCurrencyChangeClick(isEnterInFiat)
                    onAmountValueChange(amount)
                    saveResult()
                }
            }
            .launchIn(modelScope)
    }

    private fun subscribeOnAmountReduceToTriggerUpdates() {
        swapAmountReduceListener.reduceToTriggerFlow
            .onEach { reduceTo ->
                uiState.update(
                    SwapAmountReduceToTransformer(
                        primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                        secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
                        reduceToValue = reduceTo,
                    ),
                )
                feeSelectorReloadTrigger.triggerLoadingState()
                startLoadingQuotesTask(isSilentReload = false)
            }
            .launchIn(modelScope)
    }

    private fun subscribeOnAmountReduceByTriggerUpdates() {
        swapAmountReduceListener.reduceByTriggerFlow
            .onEach { reduceBy ->
                uiState.update(
                    SwapAmountReduceByTransformer(
                        primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                        secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
                        reduceByData = reduceBy,
                    ),
                )
                feeSelectorReloadTrigger.triggerLoadingState()
                startLoadingQuotesTask(isSilentReload = false)
            }
            .launchIn(modelScope)
    }

    private fun subscribeOnAmountIgnoreReduceTriggerUpdates() {
        swapAmountReduceListener.ignoreReduceTriggerFlow
            .onEach { uiState.update(SwapAmountIgnoreReduceTransformer) }
            .launchIn(modelScope)
    }

    private fun subscribeOnReloadQuotesTriggerUpdates() {
        swapAmountUpdateListener.reloadQuotesTriggerFlow
            .onEach { startLoadingQuotesTask(isSilentReload = true) }
            .launchIn(modelScope)
    }

    private fun subscribeOnAutoupdateEnabling() {
        autoUpdateSubscriberJob = swapAmountUpdateListener.autoUpdateTriggerFlow
            .distinctUntilChanged()
            .onEach { isEnabled ->
                if (isEnabled) {
                    startLoadingQuotesTask(isSilentReload = true)
                } else {
                    quoteTaskScheduler.cancelTask()
                }
            }
            .launchIn(modelScope)
    }

    private fun observeChooseSelectToken() {
        swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
            .onEach { data ->
                (params as? SwapAmountComponentParams.AmountParams)?.callback?.resetSendWithSwapNavigation(
                    data.shouldResetNavigation,
                )
                ensurePrimaryReady()
                initPairs(data.swapCurrencies, data.cryptoCurrency)
                uiState.update { amountUM ->
                    if (amountUM is SwapAmountUM.Content) {
                        amountUM.copy(isPrimaryButtonEnabled = false)
                    } else {
                        amountUM
                    }
                }
            }
            .launchIn(modelScope)
    }

    /**
     * Forces the amount UI into the primary-ready [SwapAmountUM.Content] state using the current primary
     * status. Needed for the direct Send-with-Swap (Swap&Send) entry where the state can be [SwapAmountUM.Empty] and
     * the balance-`distinctUntilChanged` primary status flow won't re-emit to rebuild it. No-op when already Content.
     */
    private suspend fun ensurePrimaryReady() {
        if (uiState.value is SwapAmountUM.Content) return
        val primaryCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value
        initCurrencies(primaryStatus = primaryCurrencyStatus, secondaryStatus = null)
        val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
        uiState.transformerUpdate(
            SwapAmountPrimaryReadyStateTransformer(
                userWallet = userWallet,
                primaryCryptoCurrencyStatus = primaryCurrencyStatus,
                appCurrency = appCurrency,
                swapDirection = swapDirection,
                clickIntents = this,
                isBalanceHidden = params.isBalanceHidingFlow.value,
                isShowBestRateAnimation = isShowBestRateAnimation,
                isSingleWallet = isOnlyOneWallet,
                isAccountsMode = params.isAccountModeFlow.value,
                account = params.accountFlow.value,
            ),
        )
    }

    private fun initPairs(swapCurrencies: SwapCurrencies, secondaryCryptoCurrency: CryptoCurrency?) {
        modelScope.launch {
            val secondaryCurrency = selectInitialPairUseCase(
                primaryCryptoCurrency = primaryCryptoCurrency,
                secondaryCryptoCurrency = secondaryCryptoCurrency,
                userWallet = userWallet,
                swapCurrencies = swapCurrencies,
                swapDirection = params.swapDirection,
            )
            val secondaryStatus = secondaryCurrency?.currencyStatus
            val primaryStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
            if (secondaryStatus != null && primaryStatus != null) {
                val rawId = secondaryStatus.currency.id.rawCurrencyId
                secondaryFiatRateUSD = rawId?.let { id -> getCurrencyUSDQuoteUseCase(id) }
                initCurrencies(primaryStatus, secondaryStatus)
                val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
                uiState.transformerUpdate(
                    SwapAmountSecondaryReadyStateTransformer(
                        userWallet = userWallet,
                        swapCurrencies = swapCurrencies,
                        primaryCryptoCurrencyStatus = primaryStatus,
                        secondaryCryptoCurrencyStatus = secondaryStatus,
                        appCurrency = appCurrency,
                        swapDirection = swapDirection,
                        clickIntents = this@SwapAmountModel,
                        isBalanceHidden = params.isBalanceHidingFlow.value,
                        isShowBestRateAnimation = isShowBestRateAnimation,
                        isSingleWallet = isOnlyOneWallet,
                        isAccountsMode = params.isAccountModeFlow.value,
                        account = params.accountFlow.value,
                        providers = secondaryCurrency.providers,
                    ),
                )

                val currentState = uiState.value as? SwapAmountUM.Content
                if (currentState != null && currentState.swapRateMode != SwapRateMode.FLOAT_ONLY) {
                    computeAndSetSecondaryAmount(currentState)
                }
                sendAmountScreenOpenedIfNeeded(secondaryStatus)
                startLoadingQuotesTask(isSilentReload = false)
            } else {
                @Suppress("NullableToStringCall")
                TangemLogger.e(
                    """
                        Invalid cryptocurrencies status:
                        | Primary -> $primaryStatus
                        | Secondary -> $secondaryStatus
                    """.trimIndent(),
                    shouldSanitize = false,
                )
                showErrorAlert(errorMessage = null)
            }
        }
    }

    private fun sendAmountErrorAnalyticsIfNeeded(quotes: List<SwapQuoteUM>) {
        val content = uiState.value as? SwapAmountUM.Content ?: return
        val toCurrency = content.secondaryCryptoCurrencyStatus?.currency ?: return
        amountAnalyticsSender.sendErrorIfNeeded(
            quotes = quotes,
            selectedQuote = content.selectedQuote,
            fromToken = content.primaryCryptoCurrencyStatus.currency,
            toToken = toCurrency,
            hasInsufficientBalance = hasInsufficientBalance(content),
        )
    }

    private fun hasInsufficientBalance(content: SwapAmountUM.Content): Boolean {
        val primaryBalance = content.primaryCryptoCurrencyStatus.value.amount ?: return false
        val fromAmount = when (content.selectedAmountType) {
            SwapAmountType.To -> (content.selectedQuote as? SwapQuoteUM.Content)?.fromAmount
            SwapAmountType.From -> {
                val field = content.primaryAmount as? SwapAmountFieldUM.Content
                (field?.amountField as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
            }
        } ?: return false
        return fromAmount > primaryBalance
    }

    private fun sendAmountScreenOpenedIfNeeded(secondaryStatus: CryptoCurrencyStatus) {
        if (params !is SwapAmountComponentParams.AmountParams) return
        if (isAmountScreenOpenedSent) return
        isAmountScreenOpenedSent = true

        val content = uiState.value as? SwapAmountUM.Content ?: return

        analyticsEventHandler.send(
            SendWithSwapAnalyticEvents.AmountScreenOpened(
                rateType = content.swapRateType.toAnalyticsRateType(),
                fromToken = content.primaryCryptoCurrencyStatus.currency,
                toToken = secondaryStatus.currency,
            ),
        )
    }

    private suspend fun initCurrencies(primaryStatus: CryptoCurrencyStatus, secondaryStatus: CryptoCurrencyStatus?) {
        primaryMinimumAmountBoundary = EnterAmountBoundary(
            amount = getMinimumTransactionAmountSyncUseCase
                .invoke(
                    userWalletId = userWallet.walletId,
                    cryptoCurrencyStatus = primaryStatus,
                ).getOrNull().orZero(),
            fiatRate = primaryStatus.value.fiatRate,
            fiatAmount = primaryStatus.value.fiatAmount,
        )
        primaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(primaryStatus)

        val primaryRawId = primaryStatus.currency.id.rawCurrencyId
        primaryFiatRateUSD = primaryRawId?.let { id -> getCurrencyUSDQuoteUseCase(id) }

        if (secondaryStatus != null) {
            val secondaryRawId = secondaryStatus.currency.id.rawCurrencyId
            secondaryFiatRateUSD = secondaryRawId?.let { id -> getCurrencyUSDQuoteUseCase(id) }

            secondaryMinimumAmountBoundary = EnterAmountBoundary(
                amount = getMinimumTransactionAmountSyncUseCase
                    .invoke(
                        userWalletId = userWallet.walletId,
                        cryptoCurrencyStatus = secondaryStatus,
                    ).getOrNull().orZero(),
                fiatRate = secondaryStatus.value.fiatRate,
                fiatAmount = secondaryStatus.value.fiatAmount,
            )
            secondaryMaximumAmountBoundary = EnterAmountBoundary(
                amount = null,
                fiatRate = secondaryStatus.value.fiatRate,
                fiatAmount = null,
            )
        }
    }

    @Suppress("LongMethod")
    private fun loadQuotes(isSilentReload: Boolean) {
        val state = uiState.value as? SwapAmountUM.Content ?: return
        if (state.secondaryCryptoCurrencyStatus == null) return

        val (fromCryptoCurrency, toCryptoCurrency) = state.swapDirection.withSwapDirection(
            onDirect = { state.primaryCryptoCurrencyStatus.currency to state.secondaryCryptoCurrencyStatus.currency },
            onReverse = { state.secondaryCryptoCurrencyStatus.currency to state.primaryCryptoCurrencyStatus.currency },
        )

        val amountField = state.swapDirection.withSwapDirection(
            onDirect = {
                if (state.selectedAmountType == SwapAmountType.From) {
                    state.primaryAmount.amountField
                } else {
                    state.secondaryAmount.amountField
                }
            },
            onReverse = {
                if (state.selectedAmountType == SwapAmountType.From) {
                    state.secondaryAmount.amountField
                } else {
                    state.primaryAmount.amountField
                }
            },
        ) as? AmountState.Data

        val amountValue = amountField?.amountTextField?.cryptoAmount?.value.orZero()
        val isAmountScreen = params is SwapAmountComponentParams.AmountParams
        val isAmountError = amountField?.amountTextField?.isError == true || amountValue.isNullOrZero()
        if (isAmountScreen && isAmountError) {
            uiState.transformerUpdate(SwapQuoteEmptyStateTransformer)
            sendAmountErrorAnalyticsIfNeeded(quotes = emptyList())
            return
        }

        val rateType = when (state.selectedAmountType) {
            SwapAmountType.To -> ExpressRateType.Fixed
            SwapAmountType.From -> if (state.swapRateMode == SwapRateMode.FIXED_ONLY) {
                ExpressRateType.Fixed
            } else {
                ExpressRateType.Float
            }
        }

        if (!isSilentReload) uiState.transformerUpdate(SwapQuoteLoadingStateTransformer)

        modelScope.launch {
            val quotes = state.swapCurrencies.getGroupWithDirection(state.swapDirection).available
                .asSequence()
                .filter { swapCurrencyStatus -> swapCurrencyStatus.currencyStatus.currency.id == toCryptoCurrency.id }
                .flatMap(SwapCryptoCurrency::providers)
                .filter { provider -> provider.rateTypes.contains(rateType) }
                .toList()
                .map { provider ->
                    async {
                        getSwapQuoteUseCase(
                            userWallet = userWallet,
                            fromCryptoCurrency = fromCryptoCurrency,
                            toCryptoCurrency = toCryptoCurrency,
                            amount = amountValue,
                            amountType = state.selectedAmountType,
                            rateType = rateType,
                            provider = provider,
                        ).fold(
                            ifLeft = { error ->
                                SwapQuoteUM.Error(
                                    provider = provider,
                                    expressError = error,
                                )
                            },
                            ifRight = { quote: SwapQuoteModel ->
                                SwapQuoteUMConverter(
                                    primaryCurrency = fromCryptoCurrency,
                                    secondaryCurrency = toCryptoCurrency,
                                    swapDirection = swapDirection,
                                    allowanceContract = quote.allowanceContract,
                                    isApprovalNeeded = checkAllowance(state, quote),
                                    fromAmount = amountValue,
                                ).convert(
                                    SwapQuoteUMConverter.Data(
                                        quote = quote,
                                        provider = provider,
                                    ),
                                )
                            },
                        )
                    }
                }.awaitAll()

            uiState.transformerUpdate(
                SwapAmountSetQuotesTransformer(
                    quotes = quotes,
                    secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
                    secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
                    isSilentReload = isSilentReload,
                    isNeedApplyFcaRestrictions = userCountry.needApplyFCARestrictions(),
                    isBalanceHidden = params.isBalanceHidingFlow.value,
                    primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
                    primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                    primaryFiatRateUSD = primaryFiatRateUSD,
                    secondaryFiatRateUSD = secondaryFiatRateUSD,
                ),
            )
            if (params is SwapAmountComponentParams.AmountParams) {
                sendAmountErrorAnalyticsIfNeeded(quotes)
            }
            feeSelectorReloadTrigger.triggerUpdate()
        }
    }

    /**
     * Compute secondary amount from primary amount using fiat rate conversion
     *   secondaryAmount = primaryAmount × primaryFiatRate / secondaryFiatRate
     *
     */
    private fun computeAndSetSecondaryAmount(state: SwapAmountUM.Content) {
        val secondaryStatus = state.secondaryCryptoCurrencyStatus ?: return
        val primaryFiatRate = state.primaryCryptoCurrencyStatus.value.fiatRate ?: return
        val secondaryFiatRate = secondaryStatus.value.fiatRate ?: return
        if (secondaryFiatRate <= BigDecimal.ZERO) return

        val primaryAmountField = (state.primaryAmount as? SwapAmountFieldUM.Content)
            ?.amountField as? AmountState.Data ?: return
        val primaryAmount = primaryAmountField.amountTextField.cryptoAmount.value ?: return
        if (primaryAmount <= BigDecimal.ZERO) return

        val secondaryAmount = primaryAmount
            .multiply(primaryFiatRate)
            .divide(secondaryFiatRate, secondaryStatus.currency.decimals, java.math.RoundingMode.HALF_UP)

        val secondaryValue = secondaryAmount.parseBigDecimal(secondaryStatus.currency.decimals)

        uiState.transformerUpdate(
            SwapAmountValueChangeTransformer(
                primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
                secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
                primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
                secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
                value = secondaryValue,
                isBalanceHidden = params.isBalanceHidingFlow.value,
            ),
        )
    }

    private fun startLoadingQuotesTask(isSilentReload: Boolean) {
        quoteTaskScheduler.cancelTask()
        loadQuotes(isSilentReload = isSilentReload)
        quoteTaskScheduler.scheduleTask(
            scope = modelScope,
            task = loadQuotesTask(),
        )
    }

    private fun loadQuotesTask(initialDelay: Long = QUOTES_UPDATE_DELAY): PeriodicTask<Unit> {
        return PeriodicTask(
            delay = QUOTES_UPDATE_DELAY,
            initialDelay = initialDelay,
            task = {
                runCatching { loadQuotes(isSilentReload = true) }
            },
            onSuccess = { /* no-op */ },
            onError = { /* no-op */ },
        )
    }

    private suspend fun checkAllowance(state: SwapAmountUM.Content, quote: SwapQuoteModel): Boolean {
        val allowanceContract = quote.allowanceContract ?: return false
        val allowance = getAllowanceInfoUseCase(
            userWalletId = userWallet.walletId,
            cryptoCurrency = state.primaryCryptoCurrencyStatus.currency,
            spenderAddress = allowanceContract,
            requiredAmount = state.primaryCryptoCurrencyStatus.value.amount.orZero(),
        ).getOrNull()

        return allowance !is AllowanceInfo.Enough
    }

    private fun saveResult() {
        val params = params as? SwapAmountComponentParams.AmountParams ?: return
        params.callback.onAmountResult(uiState.value)
    }

    private fun showErrorAlert(errorMessage: String?) {
        swapAlertFactory.getGenericErrorState(
            expressError = ExpressError.UnknownError,
            onFailedTxEmailClick = {
                modelScope.launch {
                    swapAlertFactory.onFailedTxEmailClick(
                        userWallet = params.userWallet,
                        cryptoCurrency = primaryCryptoCurrency,
                        errorMessage = errorMessage,
                    )
                }
            },
            popBack = appRouter::pop,
        )
    }

    private companion object {
        const val DEBOUNCE_AMOUNT_DELAY = 500L
        const val QUOTES_UPDATE_DELAY = 10000L
        const val BLOCK_INITIAL_QUOTE_DELAY = 1000L
    }
}