package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model

import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
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.domain.card.BackupValidator
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.scan.isRing
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.models.wallet.requireHotWallet
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.SyncWalletWithRemoteUseCase
import com.tangem.domain.wallets.usecase.UpdateWalletUseCase
import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui.state.MultiWalletFinalizeUM
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.FinalizeStage.*
import com.tangem.features.onboarding.v2.util.ResetCardsComponent
import com.tangem.operations.backup.BackupService
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.sdk.api.BackupServiceHolder
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.StringsSigns
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

@Suppress("LongParameterList", "LargeClass")
@Stable
@ModelScoped
internal class MultiWalletFinalizeModel @Inject constructor(
    paramsContainer: ParamsContainer,
    override val dispatchers: CoroutineDispatcherProvider,
    private val backupServiceHolder: BackupServiceHolder,
    private val tangemSdkManager: TangemSdkManager,
    private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
    private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
    private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
    private val saveWalletUseCase: SaveWalletUseCase,
    private val getUserWalletsUseCase: GetWalletsUseCase,
    private val updateWalletUseCase: UpdateWalletUseCase,
    private val syncWalletWithRemoteUseCase: SyncWalletWithRemoteUseCase,
    private val cardRepository: CardRepository,
    private val onboardingRepository: OnboardingRepository,
    private val walletsRepository: WalletsRepository,
    private val uiMessageSender: UiMessageSender,
    private val backupValidator: BackupValidator,
    private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {

    private val params = paramsContainer.require<MultiWalletChildParams>()
    private val multiWalletState get() = params.multiWalletState
    private val _uiState = MutableStateFlow(getInitialState())

    private val backupCardIds = backupServiceHolder.backupService.get()?.backupCardIds.orEmpty()

    private var hasWalletBackupError = false
    private var hasRing = false

    val uiState = _uiState.asStateFlow()
    val onBackFlow = MutableSharedFlow<Unit>()
    val onEvent = MutableSharedFlow<MultiWalletFinalizeComponent.Event>()
    val resetCardsModelCallbacks = ResetCardsModelCallbacks()
    val startFullResetFlow = MutableSharedFlow<UserWallet.Cold>()

    init {
        modelScope.launch {
            // sets proper artwork state for initial step
            // (if we start from backup cards, we need to show proper artwork) ([REDACTED_TASK_KEY])
            when (getInitialStep()) {
                MultiWalletFinalizeUM.Step.Primary -> { /* state is already set */
                }
                MultiWalletFinalizeUM.Step.BackupDevice1 -> {
                    onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
                }
                MultiWalletFinalizeUM.Step.BackupDevice2 -> {
                    onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
                    onEvent.emit(MultiWalletFinalizeComponent.Event.TwoBackupCardsAdded)
                }
            }
        }
    }

    fun onBack() {
        if (uiState.value.scanPrimary) {
            modelScope.launch { onBackFlow.emit(Unit) }
        } else {
            uiMessageSender.send(CantLeaveBackupDialog)
        }
    }

    private fun getInitialState(): MultiWalletFinalizeUM {
        val backupService = backupServiceHolder.backupService.get() ?: return MultiWalletFinalizeUM()
        val initialStep = getInitialStep()

        val batchId = when (initialStep) {
            MultiWalletFinalizeUM.Step.Primary -> backupService.primaryCardBatchId
            MultiWalletFinalizeUM.Step.BackupDevice1 -> backupService.backupCardsBatchIds.getOrNull(0)
            MultiWalletFinalizeUM.Step.BackupDevice2 -> backupService.backupCardsBatchIds.getOrNull(1)
        }

        val cardId = when (initialStep) {
            MultiWalletFinalizeUM.Step.Primary -> backupService.primaryCardId
            MultiWalletFinalizeUM.Step.BackupDevice1 -> backupService.backupCardIds.getOrNull(0)
            MultiWalletFinalizeUM.Step.BackupDevice2 -> backupService.backupCardIds.getOrNull(1)
        }

        return MultiWalletFinalizeUM(
            isRing = batchId?.let { isRing(it) } == true,
            onScanClick = ::onLinkClick,
            scanPrimary = initialStep == MultiWalletFinalizeUM.Step.Primary,
            cardNumber = cardId?.lastMasked().orEmpty(),
            step = initialStep,
        )
    }

    private fun getInitialStep(): MultiWalletFinalizeUM.Step {
        val startFromFinalize =
            params.multiWalletState.value.startFromFinalize ?: return MultiWalletFinalizeUM.Step.Primary

        return when (startFromFinalize) {
            ScanPrimaryCard -> MultiWalletFinalizeUM.Step.Primary
            ScanBackupFirstCard -> MultiWalletFinalizeUM.Step.BackupDevice1
            ScanBackupSecondCard -> MultiWalletFinalizeUM.Step.BackupDevice2
        }
    }

    private fun onLinkClick() {
        when (_uiState.value.step) {
            MultiWalletFinalizeUM.Step.Primary -> {
                writePrimaryCard()
            }
            MultiWalletFinalizeUM.Step.BackupDevice1 -> {
                writeBackupCard(cardIndex = 0)
            }
            MultiWalletFinalizeUM.Step.BackupDevice2 -> {
                writeBackupCard(cardIndex = 1)
            }
        }
    }

    private fun writePrimaryCard() {
        val backupService = backupServiceHolder.backupService.get() ?: return
        val primaryCardBatchId = backupService.primaryCardBatchId ?: return
        val isRing = isRing(primaryCardBatchId)
        val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
        if (isRing) hasRing = true

        tangemSdkManager.changeProductType(isRing)
        backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
            when (result) {
                is CompletionResult.Success -> {
                    modelScope.launch {
                        onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
                        // save scan response to preferences to be able
                        // to continue finalize process after app restart
                        onboardingRepository.saveUnfinishedFinalizeOnboarding(
                            scanResponse = multiWalletState.value.currentScanResponse,
                        )
                    }
                    _uiState.update { st ->
                        st.copy(
                            step = MultiWalletFinalizeUM.Step.BackupDevice1,
                            isRing = backupService.backupCardsBatchIds.getOrNull(0)?.let(::isRing) == true,
                            scanPrimary = false,
                            cardNumber = backupService.backupCardIds.firstOrNull()?.lastMasked().orEmpty(),
                        )
                    }
                }
                is CompletionResult.Failure -> Unit
            }
            tangemSdkManager.clearProductType()
        }
    }

    private fun writeBackupCard(cardIndex: Int) {
        val backupService = backupServiceHolder.backupService.get() ?: return
        val backupCardBatchId = backupService.backupCardsBatchIds.getOrNull(cardIndex) ?: return
        val isRing = isRing(backupCardBatchId)
        val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
        if (isRing) hasRing = true

        tangemSdkManager.changeProductType(isRing)
        backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
            when (result) {
                is CompletionResult.Success -> {
                    if (backupValidator.isValidBackupStatus(CardDTO(result.data)).not()) {
                        hasWalletBackupError = true
                    }

                    if (backupService.currentState == BackupService.State.Finished) {
                        finishBackup()
                    } else {
                        modelScope.launch { onEvent.emit(MultiWalletFinalizeComponent.Event.TwoBackupCardsAdded) }
                        _uiState.update { st ->
                            st.copy(
                                step = MultiWalletFinalizeUM.Step.BackupDevice2,
                                isRing = backupService.backupCardsBatchIds
                                    .getOrNull(cardIndex + 1)?.let { isRing(it) } == true,
                                cardNumber = backupService.backupCardIds.getOrNull(cardIndex + 1)
                                    ?.lastMasked().orEmpty(),
                            )
                        }
                    }
                }
                is CompletionResult.Failure -> {
                    if (result.error is TangemSdkError.WalletAlreadyCreated) {
                        // show should reset dialog
                        handleActivationError()
                    }
                }
            }

            tangemSdkManager.clearProductType()
        }
    }

    @Suppress("LongMethod")
    private fun finishBackup() {
        modelScope.launch {
            setLoading(true)
            val scanResponse = params.multiWalletState.value.currentScanResponse
            val userWalletCreated = createUserWallet(scanResponse)

            // Validate wallet before saving
            // If something went wrong - start full reset flow
            if (hasWalletBackupError || !backupValidator.isValidFull(scanResponse.card)) {
                startFullResetFlow.emit(
                    userWalletCreated.copy(
                        scanResponse = scanResponse.updateScanResponseAfterBackup(),
                    ),
                )
                return@launch
            }

            val userWallet = when (params.parentParams.mode) {
                OnboardingMultiWalletComponent.Mode.Onboarding,
                OnboardingMultiWalletComponent.Mode.ContinueFinalize,
                is OnboardingMultiWalletComponent.Mode.AddressSync,
                -> {
                    saveWalletUseCase.invoke(
                        userWallet = userWalletCreated.copy(
                            scanResponse = scanResponse.updateScanResponseAfterBackup(),
                        ),
                        canOverride = true,
                        analyticsSource = AnalyticsParam.ScreensSources.Onboarding,
                    )
                    userWalletCreated
                }
                OnboardingMultiWalletComponent.Mode.AddBackup -> {
                    val userWallet = getUserWalletsUseCase.invokeSync()
                        .firstOrNull { userWallet ->
                            userWallet is UserWallet.Cold &&
                                userWallet.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId
                        }
                        ?: userWalletCreated

                    updateWalletUseCase.invoke(
                        userWalletId = userWallet.walletId,
                        update = { wallet ->
                            wallet.requireColdWallet().copy(
                                scanResponse = scanResponse.updateScanResponseAfterBackup(),
                            )
                        },
                    )

                    userWallet
                }
                is OnboardingMultiWalletComponent.Mode.UpgradeHotWallet -> {
                    updateWalletUseCase.invoke(
                        userWalletId = userWalletCreated.walletId,
                        update = { hotUserWallet ->
                            val wallet = hotUserWallet.requireHotWallet()
                            userWalletCreated.copy(
                                name = wallet.name,
                                scanResponse = scanResponse
                                    .updateScanResponseAfterBackup()
                                    .updateWithHotWallet(wallet),
                            )
                        },
                    ).onRight {
                        launch(NonCancellable) {
                            runSuspendCatching { walletsRepository.upgradeWallet(userWalletCreated.walletId) }
                        }
                    }.getOrElse {
                        error("Failed to upgrade to cold wallet. Error: $it")
                    }
                }
            }.requireColdWallet()

            if (hasRing) {
                walletsRepository.setHasWalletsWithRing(userWallet.walletId)
            }

            // save user wallet for manage tokens screen
            params.multiWalletState.update {
                it.copy(resultUserWallet = userWallet)
            }

            launch(NonCancellable) {
                syncWalletWithRemoteUseCase(userWalletId = userWallet.walletId)
            }

            // user wallet is fully created and saved, remove scan response from preferences
            // to prevent showing finalize screen dialog on next app start
            onboardingRepository.clearUnfinishedFinalizeOnboarding()

            cardRepository.finishCardActivation(
                cardId = scanResponse.card.cardId,
                hasBackupError = hasWalletBackupError,
            )
            backupServiceHolder.backupService.get()?.discardSavedBackup()
            onEvent.emit(MultiWalletFinalizeComponent.Event.ThreeBackupCardsAdded)
        }
    }

    private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet.Cold {
        return requireNotNull(
            value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse)
                .backupCardsIds(backupCardIds.toSet())
                .hasBackupError(hasWalletBackupError)
                .build(),
            lazyMessage = { "User wallet not created" },
        )
    }

    @Suppress("MagicNumber")
    private fun ScanResponse.updateScanResponseAfterBackup(): ScanResponse {
        val cardsCount = if (params.multiWalletState.value.isThreeCards) 2 else 1

        val card = card.copy(
            backupStatus = CardDTO.BackupStatus.Active(cardCount = cardsCount),
            isAccessCodeSet = true,
        )
        return copy(card = card)
    }

    private fun ScanResponse.updateWithHotWallet(hotWallet: UserWallet.Hot): ScanResponse {
        return copy(
            derivedKeys = derivedKeys + hotWallet.wallets?.associate {
                ByteArrayKey(it.publicKey) to ExtendedPublicKeysMap(it.derivedKeys)
            }.orEmpty(),
        )
    }

    private fun handleActivationError() {
        _uiState.update { st ->
            st.copy(
                dialog = resetCardDialog(
                    onConfirm = ::navigateToSupportScreen,
                    dismiss = { _uiState.update { it.copy(dialog = null) } },
                    onDismissButtonClick = ::resetCard,
                ),
            )
        }
    }

    private fun resetCard() {
        val scanResponse = params.parentParams.scanResponse

        modelScope.launch {
            tangemSdkManager.resetToFactorySettings(
                cardId = scanResponse.card.cardId,
                allowsRequestAccessCodeFromRepository = true,
            )
        }
    }

    private fun navigateToSupportScreen() {
        modelScope.launch {
            val cardInfo =
                getWalletMetaInfoUseCase(multiWalletState.value.currentScanResponse).getOrNull() ?: return@launch
            analyticsEventHandler.send(Basic.ButtonSupport(source = AnalyticsParam.ScreensSources.Onboarding))
            sendFeedbackEmailUseCase(FeedbackEmailType.BackupProblem(cardInfo))
        }
    }

    private fun String.lastMasked(): String {
        val space = StringsSigns.NON_BREAKING_SPACE
        fun String.splitBySpace() = reversed().chunked(size = 4).joinToString(separator = "$space").reversed()
        val last4 = takeLast(n = 4).splitBySpace()
        val mask = "$space*$space*$space*$space"
        return mask + last4
    }

    private fun setLoading(isLoading: Boolean) {
        _uiState.update { it.copy(isButtonLoading = isLoading) }
    }

    inner class ResetCardsModelCallbacks : ResetCardsComponent.ModelCallbacks {

        override fun onCancel() {
            modelScope.launch {
                onEvent.emit(MultiWalletFinalizeComponent.Event.ExitFromFlow)
            }
        }

        override fun onComplete() {
            modelScope.launch {
                onboardingRepository.clearUnfinishedFinalizeOnboarding()
                backupServiceHolder.backupService.get()?.discardSavedBackup()
                onEvent.emit(MultiWalletFinalizeComponent.Event.ExitFromFlow)
            }
        }
    }
}