package com.tangem.features.commonfeatures.impl.addtoportfolio.model

import com.tangem.common.ui.addtoken.AddTokenUM
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.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase
import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedNetwork
import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedPortfolio
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.AddTokenComponent
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddTokenUiBuilder.Companion.toggleProgress
import com.tangem.features.wallet.utils.UserWalletImageFetcher
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.operations.attestation.ArtworkSize
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject

@ModelScoped
@Suppress("LongParameterList")
@OptIn(ExperimentalCoroutinesApi::class)
internal class AddTokenModel @Inject constructor(
    paramsContainer: ParamsContainer,
    private val walletImageFetcher: UserWalletImageFetcher,
    private val uiBuilder: AddTokenUiBuilder,
    private val messageSender: UiMessageSender,
    private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase,
    override val dispatchers: CoroutineDispatcherProvider,
    private val analyticsEventHandler: AnalyticsEventHandler,
    private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
    private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
    private val checkCurrencyUnsupportedDelegate: CheckCurrencyUnsupportedDelegate,
) : Model() {

    private val params = paramsContainer.require<AddTokenComponent.Params>()
    private val addTokenJob = JobHolder()

    val uiState: StateFlow<AddTokenUM?>
        field = MutableStateFlow(value = null)

    init {
        val walletImageFlow = params.selectedPortfolio
            .map { it.userWallet }
            .distinctUntilChanged()
            .flatMapLatest { walletImageFetcher.walletImage(it, ArtworkSize.SMALL) }

        combine(
            flow = params.selectedNetwork.distinctUntilChanged(),
            flow2 = params.selectedPortfolio.distinctUntilChanged(),
            flow3 = walletImageFlow,
            transform = { selectedNetwork, selectedPortfolio, walletImage ->
                addTokenJob.join()
                val isTangemIconVisible = needColdWalletInteraction(selectedNetwork, selectedPortfolio)
                uiBuilder.updateContent(
                    selectedPortfolio = selectedPortfolio,
                    selectedNetwork = selectedNetwork,
                    walletImage = walletImage,
                    isTangemIconVisible = isTangemIconVisible,
                    onConfirmClick = { onAddClick(selectedNetwork, selectedPortfolio).saveIn(addTokenJob) },
                )
            },
        )
            .onEach { newUI -> uiState.value = newUI }
            .flowOn(dispatchers.default)
            .launchIn(modelScope)
    }

    private fun onAddClick(selectedNetwork: SelectedNetwork, selectedPortfolio: SelectedPortfolio) =
        modelScope.launch(dispatchers.default) {
            val um = uiState.value ?: return@launch

            val cryptoCurrency = selectedNetwork.cryptoCurrency
            val account = selectedPortfolio.account.account.account
            val accountId = account.accountId
            val isMainNetwork = selectedNetwork.selectedNetwork.contractAddress == null

            val unsupportedCurrency = checkCurrencyUnsupportedDelegate.checkCurrencyUnsupportedState(
                userWalletId = accountId.userWalletId,
                rawNetworkId = selectedNetwork.selectedNetwork.networkId,
                isMainNetwork = isMainNetwork,
            )

            if (unsupportedCurrency != null) return@launch

            uiState.value = um.toggleProgress(true)
            val blockchainNames = listOf(selectedNetwork.selectedNetwork)
                .mapNotNull { BlockchainUtils.getNetworkInfo(it.networkId)?.name }
            val analyticsEventBuilder = params.eventBuilder
            analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioContinue(blockchainNames))
            analyticsEventHandler.send(analyticsEventBuilder.addButtonClick())

            manageCryptoCurrenciesUseCase.invokeAndAwait(accountId = accountId, add = listOf(cryptoCurrency))
                .onLeft { throwable ->
                    processError(error = throwable)
                    uiState.value = um.toggleProgress(false)
                    return@launch
                }

            val status = getAccountCurrencyStatusUseCase(
                userWalletId = accountId.userWalletId,
                currencyId = cryptoCurrency.id,
                network = cryptoCurrency.network,
            )
                .firstOrNull()
            if (status == null) {
                processError(error = null)
            } else {
                params.callbacks.onTokenAdded(status.status)
            }
            uiState.value = um.toggleProgress(false)
        }

    private suspend fun needColdWalletInteraction(
        selectedNetwork: SelectedNetwork,
        selectedPortfolio: SelectedPortfolio,
    ): Boolean = coldWalletAndHasMissedDerivationsUseCase.invoke(
        userWalletId = selectedPortfolio.userWallet.walletId,
        networksWithDerivationPath = mapOf(
            selectedNetwork.selectedNetwork.networkId to selectedNetwork.cryptoCurrency.network.derivationPath.value,
        ),
    )

    private fun processError(error: Throwable?) {
        val message = error?.message?.let { stringReference(it) }
            ?: resourceReference(R.string.common_something_went_wrong)
        messageSender.send(ToastMessage(message = message))
    }
}