package com.tangem.feature.swap

import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.swap.converters.SavedSwapTransactionListConverter
import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn

internal class DefaultSwapTransactionRepository(
    private val appPreferencesStore: AppPreferencesStore,
    private val dispatchers: CoroutineDispatcherProvider,
    private val singleAccountListSupplier: SingleAccountListSupplier,
    responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
    networkFactory: NetworkFactory,
) : SwapTransactionRepository {

    private val converter by lazy(LazyThreadSafetyMode.NONE) {
        SavedSwapTransactionListConverter(
            responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
            networkFactory = networkFactory,
        )
    }
    private val userTokensResponseFactory = UserTokensResponseFactory()

    override suspend fun storeTransaction(
        fromUserWalletId: UserWalletId,
        toUserWalletId: UserWalletId,
        fromCryptoCurrency: CryptoCurrency,
        toCryptoCurrency: CryptoCurrency,
        fromAccount: Account?,
        toAccount: Account?,
        transaction: SavedSwapTransactionModel,
    ) {
        transaction.status?.let { status ->
            storeTransactionState(
                txId = transaction.txId,
                status = status,
                accountWithCurrency = null,
            )
        }
        appPreferencesStore.editData { mutablePreferences ->
            val savedTransactions: List<SavedSwapTransactionListModelInner>? = mutablePreferences.getObjectList(
                key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
            )
            val tokenTransactions = savedTransactions
                ?.firstOrNull { savedTx ->
                    savedTx.checkId(
                        fromUserWalletId = fromUserWalletId,
                        toUserWalletId = toUserWalletId,
                        fromCurrencyId = fromCryptoCurrency.id,
                        toCurrencyId = toCryptoCurrency.id,
                    )
                }
                ?.transactions
                ?.addOrReplace(
                    item = transaction,
                    predicate = { tx -> tx.txId == transaction.txId },
                ) ?: listOf(transaction)

            mutablePreferences.setObject(
                key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
                value = savedTransactions?.updateList(
                    fromUserWalletId = fromUserWalletId,
                    toUserWalletId = toUserWalletId,
                    fromCryptoCurrency = fromCryptoCurrency,
                    toCryptoCurrency = toCryptoCurrency,
                    fromAccount = fromAccount,
                    toAccount = toAccount,
                    transactions = tokenTransactions,
                ) ?: listOf(
                    converter.default(
                        fromUserWalletId = fromUserWalletId,
                        toUserWalletId = toUserWalletId,
                        fromCryptoCurrency = fromCryptoCurrency,
                        toCryptoCurrency = toCryptoCurrency,
                        fromAccount = fromAccount,
                        toAccount = toAccount,
                        tokenTransactions = listOf(transaction),
                    ),
                ),
            )
        }
    }

    override fun getTransactions(
        userWallet: UserWallet,
        cryptoCurrencyId: CryptoCurrency.ID,
    ): Flow<List<SavedSwapTransactionListModel>?> {
        return combine(
            flow = appPreferencesStore.getObjectList<SavedSwapTransactionListModelInner>(
                key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
            ),
            flow2 = appPreferencesStore.getObjectMap<ExchangeStatusModel>(
                key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
            ),
            flow3 = singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId)),
        ) { savedTransactions, txStatuses, accountList ->

            val currencyToTxs = savedTransactions?.filter { savedTx ->
                val isUserWallet = savedTx.toUserWalletId == userWallet.walletId.stringValue
                val isToCurrency = savedTx.toCryptoCurrencyId == cryptoCurrencyId.value
                isUserWallet && isToCurrency
            }

            val currencyFromTxs = savedTransactions?.filter { savedTx ->
                val isUserWallet = savedTx.fromUserWalletId == userWallet.walletId.stringValue
                val isFromCurrency = savedTx.fromCryptoCurrencyId == cryptoCurrencyId.value
                isUserWallet && isFromCurrency
            }

            val toTxs = currencyToTxs?.mapNotNull { savedTx ->
                converter.convertBack(
                    value = savedTx,
                    userWallet = userWallet,
                    accountList = accountList,
                    txStatuses = txStatuses,
                    onFilter = { tx -> tx.swapTxTypeDTO == SwapTxTypeDTO.Swap },
                )
            }.orEmpty()

            val fromTxs = currencyFromTxs?.mapNotNull { savedTx ->
                converter.convertBack(
                    value = savedTx,
                    userWallet = userWallet,
                    accountList = accountList,
                    txStatuses = txStatuses,
                )
            }.orEmpty()

            fromTxs + toTxs
        }
            .flowOn(dispatchers.default)
    }

    override suspend fun removeTransaction(userWalletId: UserWalletId, txId: String) {
        clearTransactionsStatuses(txId = txId)
        appPreferencesStore.editData { mutablePreferences ->
            val savedList: List<SavedSwapTransactionListModelInner>? = mutablePreferences.getObjectList(
                key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
            )
            val tokenTransactions = savedList
                ?.asSequence()
                ?.map { savedTx ->
                    savedTx.copy(transactions = savedTx.transactions.filterNot { tx -> tx.txId == txId })
                }?.filterNot { savedTx -> savedTx.transactions.isEmpty() }
                ?.toList()

            if (tokenTransactions.isNullOrEmpty()) {
                mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_KEY)
            } else {
                mutablePreferences.setObject(
                    key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
                    value = tokenTransactions,
                )
            }
        }
    }

    override suspend fun storeTransactionState(
        txId: String,
        status: ExchangeStatusModel,
        accountWithCurrency: Pair<AccountId?, CryptoCurrency>?,
    ) {
        appPreferencesStore.editData { mutablePreferences ->
            val savedMap = mutablePreferences.getObjectMap<ExchangeStatusModel>(
                key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
            )

            val updatesMap = savedMap.toMutableMap()
            updatesMap[txId] = status.copy(
                refundTokensResponse = accountWithCurrency?.let { (accountId, currency) ->
                    userTokensResponseFactory.createResponseToken(currency, accountId)
                },
            )

            mutablePreferences.setObjectMap(
                key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
                value = updatesMap,
            )
        }
    }

    override suspend fun getLastSwappedCryptoCurrencyId(userWalletId: UserWalletId): String? {
        val lastSwappedCurrencies = appPreferencesStore.getObjectListSync<SavedLastSwappedCryptoCurrency>(
            key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY,
        )

        return lastSwappedCurrencies.find { userWalletId.stringValue == it.userWalletId }?.cryptoCurrencyId
    }

    override suspend fun storeLastSwappedCryptoCurrencyId(
        userWalletId: UserWalletId,
        cryptoCurrencyId: CryptoCurrency.ID,
    ) {
        appPreferencesStore.editData { mutablePreferences ->
            val lastSwappedCryptoCurrencies: List<SavedLastSwappedCryptoCurrency>? = mutablePreferences.getObjectList(
                key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY,
            )

            val newList = if (lastSwappedCryptoCurrencies != null) {
                lastSwappedCryptoCurrencies.filter {
                    it.userWalletId != userWalletId.stringValue
                } + SavedLastSwappedCryptoCurrency(userWalletId.stringValue, cryptoCurrencyId.value)
            } else {
                listOf(SavedLastSwappedCryptoCurrency(userWalletId.stringValue, cryptoCurrencyId.value))
            }

            mutablePreferences.setObjectList(
                key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY,
                value = newList,
            )
        }
    }

    private fun SavedSwapTransactionListModelInner.checkId(
        fromUserWalletId: UserWalletId,
        toUserWalletId: UserWalletId,
        fromCurrencyId: CryptoCurrency.ID,
        toCurrencyId: CryptoCurrency.ID,
    ): Boolean {
        return this.fromUserWalletId == fromUserWalletId.stringValue &&
            this.toUserWalletId == toUserWalletId.stringValue &&
            toCryptoCurrencyId == toCurrencyId.value &&
            fromCryptoCurrencyId == fromCurrencyId.value
    }

    @Suppress("LongParameterList")
    private fun List<SavedSwapTransactionListModelInner>.updateList(
        fromUserWalletId: UserWalletId,
        toUserWalletId: UserWalletId,
        fromCryptoCurrency: CryptoCurrency,
        toCryptoCurrency: CryptoCurrency,
        fromAccount: Account?,
        toAccount: Account?,
        transactions: List<SavedSwapTransactionModel>,
    ): List<SavedSwapTransactionListModelInner> {
        return addOrReplace(
            item = converter.default(
                fromUserWalletId = fromUserWalletId,
                toUserWalletId = toUserWalletId,
                fromCryptoCurrency = fromCryptoCurrency,
                toCryptoCurrency = toCryptoCurrency,
                fromAccount = fromAccount,
                toAccount = toAccount,
                tokenTransactions = transactions,
            ),
            predicate = { savedTx ->
                savedTx.checkId(
                    fromUserWalletId = fromUserWalletId,
                    toUserWalletId = toUserWalletId,
                    fromCurrencyId = fromCryptoCurrency.id,
                    toCurrencyId = toCryptoCurrency.id,
                )
            },
        )
    }

    private suspend fun clearTransactionsStatuses(txId: String) {
        appPreferencesStore.editData { mutablePreferences ->
            val savedList = mutablePreferences.getObjectMap<ExchangeStatusModel>(
                key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
            )
            val editedList = savedList.filterNot { it.key == txId }

            if (editedList.isEmpty()) {
                mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY)
            } else {
                mutablePreferences.setObjectMap(
                    key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
                    value = editedList,
                )
            }
        }
    }
}