package io.novafoundation.nova.feature_multisig_operations.presentation.details.full

import io.novafoundation.nova.common.address.toHexWithPrefix
import io.novafoundation.nova.common.base.BaseViewModel
import io.novafoundation.nova.common.domain.onLoaded
import io.novafoundation.nova.common.mixin.copy.CopyTextLauncher
import io.novafoundation.nova.common.mixin.copy.showCopyCallHash
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.ellipsizeMiddle
import io.novafoundation.nova.common.utils.launchUnit
import io.novafoundation.nova.common.utils.withSafeLoading
import io.novafoundation.nova.common.view.bottomSheet.description.DescriptionBottomSheetLauncher
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountUIUseCase
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
import io.novafoundation.nova.feature_account_api.presenatation.actions.showAddressActions
import io.novafoundation.nova.feature_multisig_operations.R
import io.novafoundation.nova.feature_multisig_operations.domain.details.MultisigOperationDetailsInteractor
import io.novafoundation.nova.feature_multisig_operations.presentation.MultisigOperationsRouter
import io.novafoundation.nova.feature_multisig_operations.presentation.common.MultisigOperationPayload
import io.novafoundation.nova.feature_multisig_operations.presentation.common.toOperationId
import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryTokenUseCase
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatAmountToAmountModel
import io.novafoundation.nova.runtime.ext.fullId
import io.novafoundation.nova.runtime.ext.utilityAsset
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge

private const val CALL_HASH_SHOWN_SYMBOLS = 9

class MultisigOperationFullDetailsViewModel(
    private val router: MultisigOperationsRouter,
    private val resourceManager: ResourceManager,
    private val interactor: MultisigOperationDetailsInteractor,
    private val multisigOperationsService: MultisigPendingOperationsService,
    private val externalActions: ExternalActions.Presentation,
    private val payload: MultisigOperationPayload,
    private val accountUIUseCase: AccountUIUseCase,
    private val descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher,
    private val copyTextLauncher: CopyTextLauncher.Presentation,
    private val arbitraryTokenUseCase: ArbitraryTokenUseCase,
    private val amountFormatter: AmountFormatter
) : BaseViewModel(),
    ExternalActions by externalActions,
    CopyTextLauncher by copyTextLauncher,
    DescriptionBottomSheetLauncher by descriptionBottomSheetLauncher {

    fun backClicked() {
        router.back()
    }

    // Mirrors MultisigOperationDetailsViewModel's not-yet-submitted handling - this screen is
    // reachable (via "Call Details") for a first-signer deep-link operation too, since
    // callDetailsVisible there is based on `call != null`, which is always true here.
    private val notYetSubmittedOperationFlow = MutableStateFlow<PendingMultisigOperation?>(null)

    private val operationFlow = merge(
        multisigOperationsService.pendingOperationFlow(payload.toOperationId()).filterNotNull(),
        notYetSubmittedOperationFlow.filterNotNull()
    ).shareInBackground()

    init {
        checkOperationAvailability()
    }

    private fun checkOperationAvailability() = launchUnit {
        val operationId = payload.toOperationId()
        if (interactor.isOperationAvailable(operationId)) return@launchUnit

        val notSubmittedCallData = payload.notSubmittedCallData ?: return@launchUnit
        val builtOperation = runCatching {
            interactor.buildNotYetSubmittedOperation(operationId, notSubmittedCallData)
        }.getOrNull() ?: return@launchUnit

        notYetSubmittedOperationFlow.value = builtOperation
    }

    private val tokenFlow = operationFlow.map {
        arbitraryTokenUseCase.getToken(it.chain.utilityAsset.fullId)
    }.shareInBackground()

    val depositorAccountModel = operationFlow.map { operation ->
        operation.depositor?.let { accountUIUseCase.getAccountModel(it, operation.chain) }
    }.withSafeLoading()
        .shareInBackground()

    val depositAmount = combine(operationFlow, tokenFlow) { operation, token ->
        amountFormatter.formatAmountToAmountModel(operation.deposit, token)
    }.shareInBackground()

    private val callDataFlow = operationFlow.map { operation ->
        operation.call?.let { interactor.callDataAsString(it, operation.chain.id) }
    }.shareInBackground()

    val ellipsizedCallData = callDataFlow.map { it?.ellipsizeMiddle(CALL_HASH_SHOWN_SYMBOLS) }

    val formattedCall = operationFlow.map { operation ->
        operation.call?.let { interactor.callDetails(it) }
    }.shareInBackground()

    private val callHash = operationFlow.map { operation ->
        operation.callHash.toHexWithPrefix()
    }.shareInBackground()

    val ellipsizedCallHash = callHash.map {
        it.ellipsizeMiddle(CALL_HASH_SHOWN_SYMBOLS)
    }.shareInBackground()

    fun onDepositorClicked() = launchUnit {
        val chain = operationFlow.first().chain
        depositorAccountModel.first().onLoaded { accountModel ->
            // Null for a not-yet-submitted operation (nobody has deposited yet) - the depositor
            // row is hidden in that case (see showAccountWithLoading), so this click handler
            // shouldn't be reachable, but guard it explicitly rather than assume.
            accountModel?.let { externalActions.showAddressActions(it.address(), chain) }
        }
    }

    fun callDataClicked() = launchUnit {
        val callDataEllipsized = ellipsizedCallData.first() ?: return@launchUnit
        val callData = callDataFlow.first() ?: return@launchUnit
        copyTextLauncher.showCopyTextDialog(
            CopyTextLauncher.Payload(
                title = callDataEllipsized.toString(),
                textToCopy = callData,
                resourceManager.getString(R.string.common_copy_call_data),
                resourceManager.getString(R.string.common_share_call_data)
            )
        )
    }

    fun depositClicked() {
        launchDescriptionBottomSheet(
            titleRes = R.string.multisig_deposit,
            descriptionRes = R.string.multisig_deposit_description
        )
    }

    fun callHashClicked() = launchUnit {
        copyTextLauncher.showCopyCallHash(resourceManager, callHash.first())
    }
}
