package com.tangem.features.nft.collections.model

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.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.fields.InputManager
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.nft.RefreshAllNFTUseCase
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.nft.models.NFTCollections
import com.tangem.features.nft.collections.NFTCollectionsComponent
import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM
import com.tangem.features.nft.collections.entity.NFTCollectionsUM
import com.tangem.features.nft.collections.entity.transformer.*
import com.tangem.features.nft.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

@Suppress("LongParameterList")
@ModelScoped
internal class NFTCollectionsModel @Inject constructor(
    override val dispatchers: CoroutineDispatcherProvider,
    private val searchManager: InputManager,
    private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
    private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase,
    private val refreshAllNFTUseCase: RefreshAllNFTUseCase,
    private val accountsFeatureToggles: AccountsFeatureToggles,
    private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
    paramsContainer: ParamsContainer,
) : Model() {

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

    private val _state = MutableStateFlow(
        value = NFTCollectionsStateUM(
            onBackClick = params.onBackClick,
            content = NFTCollectionsUM.Loading(
                search = SearchBarUM(
                    placeholderText = resourceReference(R.string.common_search),
                    query = "",
                    isActive = false,
                    onQueryChange = { },
                    onActiveChange = { },
                ),
                onReceiveClick = params.onReceiveClick,
            ),
            pullToRefreshConfig = PullToRefreshConfig(
                isRefreshing = false,
                onRefresh = { onRefresh() },
            ),
        ),
    )
    val state: StateFlow<NFTCollectionsStateUM> get() = _state

    private val collectionIdProvider: NFTCollection.() -> String = {
        "${network.name}_${network.derivationPath.value}_$id"
    }

    init {
        if (accountsFeatureToggles.isFeatureEnabled) {
            subscribeToNFTCollectionsNew()
        } else {
            subscribeToNFTCollections()
        }
    }

    private fun subscribeToNFTCollections() {
        combine(
            flow = getNFTCollectionsUseCase(params.userWalletId),
            flow2 = searchManager.query.distinctUntilChanged(),
        ) { nftCollections, query ->
            _state.update {
                UpdateDataStateTransformer(
                    nftCollections = nftCollections.filter(query),
                    onReceiveClick = {
                        params.onReceiveClick()
                    },
                    onRetryClick = ::onRefresh,
                    onExpandCollectionClick = ::onExpandCollectionClick,
                    onRetryAssetsClick = ::onRetryAssetsClick,
                    onAssetClick = { asset, collection ->
                        params.onAssetClick(asset, collection)
                    },
                    initialSearchBarFactory = ::getInitialSearchBar,
                    collectionIdProvider = collectionIdProvider,
                ).transform(it)
            }
        }
            .onStart { onRefresh() }
            .launchIn(modelScope)
    }

    private fun subscribeToNFTCollectionsNew() {
        combine(
            flow = getNFTCollectionsUseCase.invokeForAccounts(params.userWalletId),
            flow2 = searchManager.query.distinctUntilChanged(),
            flow3 = isAccountsModeEnabledUseCase(),
        ) { nftCollections, query, isAccountMode ->
            val filteredNFTs = nftCollections.collections
                .mapValues { (_, nfts) -> nfts.filter(query) }

            _state.update {
                UpdateDataStateTransformer(
                    nftCollections = listOf(),
                    isAccountMode = isAccountMode,
                    walletNFTCollections = nftCollections.copy(collections = filteredNFTs),
                    onReceiveClick = {
                        params.onReceiveClick()
                    },
                    onRetryClick = ::onRefresh,
                    onExpandCollectionClick = ::onExpandCollectionClick,
                    onRetryAssetsClick = ::onRetryAssetsClick,
                    onAssetClick = { asset, collection ->
                        params.onAssetClick(asset, collection)
                    },
                    initialSearchBarFactory = ::getInitialSearchBar,
                    collectionIdProvider = collectionIdProvider,
                ).transform(it)
            }
        }
            .onStart { onRefresh() }
            .launchIn(modelScope)
    }

    private fun List<NFTCollections>.filter(query: String): List<NFTCollections> = map {
        it.copy(
            content = when (val content = it.content) {
                is NFTCollections.Content.Collections -> content.copy(
                    collections = content.collections.orEmpty().filter {
                        val assetsFulfillQuery = if (query.isEmpty()) {
                            true
                        } else {
                            when (val assets = it.assets) {
                                is NFTCollection.Assets.Empty,
                                is NFTCollection.Assets.Failed,
                                is NFTCollection.Assets.Loading,
                                -> false
                                is NFTCollection.Assets.Value -> {
                                    assets.items.any { asset ->
                                        asset.name?.lowercase()?.contains(query.lowercase()) == true
                                    }
                                }
                            }
                        }

                        val collectionFulfillQuery =
                            query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true

                        collectionFulfillQuery || assetsFulfillQuery
                    },
                )
                is NFTCollections.Content.Error -> it.content
            },
        )
    }

    private fun onRefresh() {
        modelScope.launch {
            _state.update { ChangeRefreshingStateTransformer(true).transform(it) }
            try {
                refreshAllNFTUseCase(params.userWalletId)
                    .onLeft { Timber.e(it) }
            } finally {
                _state.update { ChangeRefreshingStateTransformer(false).transform(it) }
            }
        }
    }

    private fun onSearchQueryChange(newQuery: String) {
        modelScope.launch {
            _state.update { UpdateSearchQueryTransformer(newQuery).transform(it) }

            searchManager.update(newQuery)
        }
    }

    private fun getInitialSearchBar(): SearchBarUM = SearchBarUM(
        placeholderText = resourceReference(R.string.common_search),
        query = "",
        isActive = false,
        onQueryChange = ::onSearchQueryChange,
        onActiveChange = ::toggleSearchBar,
    )

    private fun toggleSearchBar(isActive: Boolean) {
        _state.update {
            ToggleSearchBarTransformer(isActive).transform(it)
        }
    }

    private fun onExpandCollectionClick(collection: NFTCollection) {
        _state.update {
            ChangeCollectionExpandedStateTransformer(
                collection = collection,
                collectionIdProvider = collectionIdProvider,
                onFirstExpanded = { onFirstExpanded(collection) },
            ).transform(it)
        }
    }

    private fun onFirstExpanded(collection: NFTCollection) {
        loadCollectionAssets(collection)
    }

    private fun onRetryAssetsClick(collection: NFTCollection) {
        loadCollectionAssets(collection)
    }

    private fun loadCollectionAssets(collection: NFTCollection) {
        modelScope.launch {
            fetchNFTCollectionAssetsUseCase(
                userWalletId = params.userWalletId,
                network = collection.network,
                collectionId = collection.id,
            )
        }
    }
}