package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model

import arrow.core.getOrElse
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.domain.swap.models.SwapDataModel
import com.tangem.domain.swap.models.SwapDataTransactionModel
import com.tangem.domain.swap.models.SwapTxType
import com.tangem.domain.swap.usecase.GetSwapDataUseCase
import com.tangem.domain.swap.usecase.SwapTransactionSentUseCase
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.transaction.usecase.gasless.CreateAndSendGaslessTransactionUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils
import com.tangem.features.swap.v2.impl.common.ConfirmData
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.logging.TangemLogger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.math.BigDecimal

@Suppress("LongParameterList")
internal class SwapTransactionSender @AssistedInject constructor(
    private val getSwapDataUseCase: GetSwapDataUseCase,
    private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
    private val swapTransactionSentUseCase: SwapTransactionSentUseCase,
    private val sendTransactionUseCase: SendTransactionUseCase,
    private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase,
    @Assisted private val userWallet: UserWallet,
) {

    suspend fun sendTransaction(
        confirmData: ConfirmData,
        isAmountSubtractAvailable: Boolean,
        onSendSuccess: (String, Long, SwapDataModel) -> Unit,
        onExpressError: (ExpressError) -> Unit,
        onSendError: (SendTransactionError?) -> Unit,
        expressOperationType: ExpressOperationType,
        feeExtended: TransactionFeeExtended?,
    ) {
        val provider = confirmData.quote?.provider ?: return

        when (val providerType = provider.type) {
            ExpressProviderType.CEX -> onCexTransaction(
                confirmData = confirmData,
                isAmountSubtractAvailable = isAmountSubtractAvailable,
                onExpressError = onExpressError,
                onSendSuccess = onSendSuccess,
                onSendError = onSendError,
                expressOperationType = expressOperationType,
                feeExtended = feeExtended,
            )
            ExpressProviderType.DEX,
            ExpressProviderType.DEX_BRIDGE,
            ExpressProviderType.ONRAMP,
            -> {
                TangemLogger.i(
                    messageString = "Provider $providerType is not supported in Send With Swap",
                    shouldSanitize = false,
                )
                onExpressError(ExpressError.UnknownError)
            }
        }
    }

    private suspend fun onCexTransaction(
        confirmData: ConfirmData,
        isAmountSubtractAvailable: Boolean,
        onSendSuccess: (String, Long, SwapDataModel) -> Unit,
        onExpressError: (ExpressError) -> Unit,
        onSendError: (SendTransactionError?) -> Unit,
        expressOperationType: ExpressOperationType,
        feeExtended: TransactionFeeExtended?,
    ) {
        val fromStatus = confirmData.fromCryptoCurrencyStatus ?: return
        val toStatus = confirmData.toCryptoCurrencyStatus ?: return
        val fromAccount = confirmData.fromAccount
        val provider = confirmData.quote?.provider ?: return
        val rateType = confirmData.rateType ?: return
        val feeValue = confirmData.fee?.amount?.value ?: return
        val destination = confirmData.enteredDestination ?: return

        val swapDataRequestAmount = when (confirmData.amountType) {
            SwapAmountType.From -> {
                val amountValue = confirmData.enteredFromAmount ?: return
                val subtracted = FeeCalculationUtils.checkAndCalculateSubtractedAmount(
                    isAmountSubtractAvailable = isAmountSubtractAvailable,
                    cryptoCurrencyStatus = fromStatus,
                    amountValue = amountValue,
                    feeValue = feeValue,
                    reduceAmountBy = confirmData.reduceAmountBy,
                )
                subtracted
            }
            SwapAmountType.To -> {
                val amountValue = confirmData.enteredToAmount ?: return
                amountValue
            }
        }

        val fromTransactionAmount = when (confirmData.amountType) {
            SwapAmountType.From -> swapDataRequestAmount
            SwapAmountType.To -> confirmData.enteredFromAmount ?: return
        }

        val swapData = getSwapDataUseCase(
            userWallet = userWallet,
            fromCryptoCurrencyStatus = fromStatus,
            amount = swapDataRequestAmount,
            amountType = confirmData.amountType,
            toCryptoCurrency = toStatus.currency,
            toAddress = destination,
            toExtraId = confirmData.enteredMemo,
            expressProvider = provider,
            rateType = rateType,
            expressOperationType = expressOperationType,
            quoteId = (confirmData.quote as? SwapQuoteUM.Content)?.quoteId,
        ).getOrElse { error -> onExpressError(error); return }

        createAndSendCexTransaction(
            fromAmount = fromTransactionAmount,
            fromStatus = fromStatus,
            fromAccount = fromAccount,
            toStatus = toStatus,
            fee = confirmData.fee,
            provider = provider,
            swapData = swapData,
            onSendSuccess = onSendSuccess,
            onExpressError = onExpressError,
            onSendError = onSendError,
            feeExtended = feeExtended,
        )
    }

    private suspend fun createAndSendCexTransaction(
        fromAmount: BigDecimal,
        fromStatus: CryptoCurrencyStatus,
        toStatus: CryptoCurrencyStatus,
        fromAccount: Account?,
        fee: Fee,
        feeExtended: TransactionFeeExtended?,
        provider: ExpressProvider,
        swapData: SwapDataModel,
        onSendSuccess: (String, Long, SwapDataModel) -> Unit,
        onExpressError: (ExpressError) -> Unit,
        onSendError: (SendTransactionError?) -> Unit,
    ) {
        val swapTransaction = if (swapData.transaction is SwapDataTransactionModel.CEX) {
            swapData.transaction
        } else {
            onExpressError(ExpressError.UnknownError)
            return
        }

        val txData = createTransferTransactionUseCase(
            amount = fromAmount.convertToSdkAmount(fromStatus),
            fee = fee,
            memo = swapTransaction.txExtraId,
            destination = swapTransaction.txTo,
            userWalletId = userWallet.walletId,
            network = fromStatus.currency.network,
        ).getOrElse { error ->
            TangemLogger.e("Failed to create swap CEX tx data", error)
            onSendError(SendTransactionError.UnknownError(Exception(error)))
            return
        }

        if (txData.extras == null && swapTransaction.txExtraId != null) {
            onExpressError(ExpressError.UnknownError)
            return
        }

        val ethereumCallData = (txData.extras as? EthereumTransactionExtras)?.callData
        val payInAddress = if (ethereumCallData is EthereumYieldSupplySendCallData) {
            ethereumCallData.destinationAddress
        } else {
            txData.destinationAddress
        }

        val isFeeInTokenCurrency = feeExtended?.transactionFee?.normal is Fee.Ethereum.TokenCurrency

        val result = if (feeExtended != null && isFeeInTokenCurrency) {
            createAndSendGaslessTransactionUseCase(
                userWallet = userWallet,
                transactionData = txData,
                fee = feeExtended,
            )
        } else {
            sendTransactionUseCase(
                txData = txData,
                userWallet = userWallet,
                network = fromStatus.currency.network,
            )
        }
        result.fold(
            ifLeft = { onSendError(it) },
            ifRight = { txHash ->
                val timestamp = System.currentTimeMillis()
                swapTransactionSentUseCase.invoke(
                    fromUserWallet = userWallet,
                    toUserWallet = userWallet,
                    fromCryptoCurrencyStatus = fromStatus,
                    toCryptoCurrencyStatus = toStatus,
                    fromAccount = fromAccount,
                    toAccount = null,
                    swapDataTransactionModel = swapTransaction,
                    provider = provider,
                    txHash = txHash,
                    payInAddress = payInAddress,
                    timestamp = timestamp,
                    swapTxType = SwapTxType.SendWithSwap,
                )
                onSendSuccess(txHash, timestamp, swapData)
            },
        )
    }

    @AssistedFactory
    interface Factory {
        fun create(userWallet: UserWallet): SwapTransactionSender
    }
}