package com.tangem.feature.wallet.utils

import arrow.core.Either
import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetWalletTotalBalanceUseCaseV2
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.lce
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.wallet.impl.R
import com.tangem.features.wallet.utils.UserWalletImageFetcher
import com.tangem.features.wallet.utils.UserWalletsFetcher
import com.tangem.operations.attestation.ArtworkSize
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*

@Suppress("LongParameterList")
internal class DefaultUserWalletsFetcher @AssistedInject constructor(
    getWalletsUseCase: GetWalletsUseCase,
    private val accountsFeatureToggles: AccountsFeatureToggles,
    private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
    private val getWalletTotalBalanceUseCaseV2: GetWalletTotalBalanceUseCaseV2,
    private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
    private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
    @Assisted private val onWalletClick: (UserWalletId) -> Unit,
    @Assisted private val messageSender: UiMessageSender,
    @Assisted("onlyMultiCurrency") private val onlyMultiCurrency: Boolean,
    @Assisted("isAuthMode") private val isAuthMode: Boolean,
    @Assisted("isClickableIfLocked") private val isClickableIfLocked: Boolean,
    private val userWalletImageFetcher: UserWalletImageFetcher,
    dispatchers: CoroutineDispatcherProvider,
) : UserWalletsFetcher {

    private val walletsFlow =
        if (onlyMultiCurrency) getWalletsUseCase().map { it.filter { it.isMultiCurrency } } else getWalletsUseCase()

    @OptIn(ExperimentalCoroutinesApi::class)
    override val userWallets: Flow<ImmutableList<UserWalletItemUM>> = walletsFlow.transformLatest { wallets ->
        val uiModels = UserWalletItemUMConverter(
            onClick = onWalletClick,
            isAuthMode = isAuthMode,
            isClickableIfLocked = isClickableIfLocked,
        ).convertList(wallets)
            .toImmutableList()

        emit(uiModels)

        combine(
            flow = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
            flow2 = getBalanceHidingSettingsUseCase().distinctUntilChanged(),
            flow3 = getTotalBalanceFlow(wallets),
            flow4 = userWalletImageFetcher.walletsImage(wallets, ArtworkSize.SMALL),
        ) { maybeAppCurrency, balanceHidingSettings, maybeBalances, artworks ->
            createUiModels(
                wallets = wallets,
                maybeAppCurrency = maybeAppCurrency,
                maybeBalances = maybeBalances,
                balanceHidingSettings = balanceHidingSettings,
                artworks = artworks,
            )
        }
            .collectLatest { lceItems: Lce<Error, ImmutableList<UserWalletItemUM>> ->
                when (lceItems) {
                    is Lce.Content<ImmutableList<UserWalletItemUM>> -> emit(lceItems.content)
                    is Lce.Error<Error> -> {
                        val message = resourceReference(R.string.common_unknown_error)
                        messageSender.send(SnackbarMessage(message))
                    }
                    is Lce.Loading<*> -> Unit
                }
            }
    }
        .flowOn(dispatchers.default)

    private fun getTotalBalanceFlow(
        wallets: List<UserWallet>,
    ): Flow<Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>> {
        val walletIds = wallets.map(UserWallet::walletId)

        return if (isAuthMode) {
            // We should not load balances in auth mode
            flowOf(Lce.Loading(walletIds.associateWith { TotalFiatBalance.Loading }))
        } else {
            if (accountsFeatureToggles.isFeatureEnabled) {
                getWalletTotalBalanceUseCaseV2(userWalletIds = walletIds)
            } else {
                getWalletTotalBalanceUseCase(walletIds).distinctUntilChanged()
            }
        }
    }

    private fun createUiModels(
        wallets: List<UserWallet>,
        maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
        maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
        balanceHidingSettings: BalanceHidingSettings,
        artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
    ): Lce<Error, ImmutableList<UserWalletItemUM>> = lce {
        val balances = withError(
            transform = { Error.UnableToGetBalances },
            block = {
                maybeBalances.bindOrNull().orEmpty()
                    .filterKeys { userWalletId -> wallets.any { it.walletId == userWalletId } }
                    .mapKeys { entry -> wallets.first { it.walletId == entry.key } }
            },
        )

        val appCurrency = withError(
            transform = { Error.UnableToGetAppCurrency },
            block = { maybeAppCurrency.toLce().bind() },
        )

        balances
            .map { (userWallet, balance) ->
                UserWalletItemUMConverter(
                    onClick = { onWalletClick(it) },
                    appCurrency = appCurrency,
                    balance = balance,
                    isBalanceHidden = balanceHidingSettings.isBalanceHidden,
                    artwork = artworks[userWallet.walletId],
                    endIcon = if (isAuthMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
                        UserWalletItemUM.EndIcon.Warning
                    } else {
                        UserWalletItemUM.EndIcon.None
                    },
                    isAuthMode = isAuthMode,
                    isClickableIfLocked = isClickableIfLocked,
                )
                    .convert(userWallet)
            }
            .toImmutableList()
    }

    sealed class Error {

        data object UnableToGetAppCurrency : Error()

        data object UnableToGetBalances : Error()
    }

    @AssistedFactory
    interface Factory : UserWalletsFetcher.Factory {
        override fun create(
            messageSender: UiMessageSender,
            @Assisted("onlyMultiCurrency") onlyMultiCurrency: Boolean,
            @Assisted("isAuthMode") isAuthMode: Boolean,
            @Assisted("isClickableIfLocked") isClickableIfLocked: Boolean,
            onWalletClick: (UserWalletId) -> Unit,
        ): DefaultUserWalletsFetcher
    }
}