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

import androidx.compose.animation.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.footers.SendingText
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
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.SwapDirection
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.api.entity.PredefinedValues
import com.tangem.features.send.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorBlockComponent
import com.tangem.features.send.api.subcomponents.feeSelector.params.FeeSelectorParams.*
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsComponent
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
import com.tangem.features.swap.v2.impl.common.SwapUtils.SEND_WITH_SWAP_PROVIDER_TYPES
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui.SendWithSwapConfirmContent
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import com.tangem.utils.StringsSigns
import com.tangem.utils.extensions.orZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*

internal class SendWithSwapConfirmComponent @AssistedInject constructor(
    @Assisted private val appComponentContext: AppComponentContext,
    @Assisted private val params: Params,
    sendDestinationBlockComponentFactory: SendDestinationBlockComponent.Factory,
    feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
    sendNotificationsComponentFactory: SendNotificationsComponent.Factory,
    private val urlOpener: UrlOpener,
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {

    private val model: SendWithSwapConfirmModel = getOrCreateModel(params = params)

    private val blockClickEnableFlow = MutableStateFlow(false)

    private val amountBlockComponent = SwapAmountBlockComponent(
        appComponentContext = child("sendWithSwapConfirmAmountBlock"),
        params = SwapAmountComponentParams.AmountBlockParams(
            amountUM = model.uiState.value.amountUM,
            analyticsCategoryName = params.analyticsCategoryName,
            userWallet = params.userWallet,
            blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
            primaryCryptoCurrencyStatusFlow = params.primaryCryptoCurrencyStatusFlow,
            secondaryCryptoCurrency = model.secondaryCurrency,
            isBalanceHidingFlow = params.isBalanceHidingFlow,
            swapDirection = params.swapDirection,
            filterProviderTypes = SEND_WITH_SWAP_PROVIDER_TYPES,
            analyticsSendSource = params.analyticsSendSource,
            accountFlow = params.accountFlow,
            isAccountModeFlow = params.isAccountModeFlow,
        ),
        onResult = model::onAmountResult,
        onClick = model::showEditAmount,
    )

    private val sendDestinationBlockComponent = sendDestinationBlockComponentFactory.create(
        context = child("sendWithSwapConfirmDestinationBlock"),
        params = SendDestinationComponentParams.DestinationBlockParams(
            state = model.uiState.value.destinationUM,
            analyticsCategoryName = params.analyticsCategoryName,
            analyticsSendSource = params.analyticsSendSource,
            userWalletId = params.userWallet.walletId,
            blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
            cryptoCurrency = model.secondaryCurrency,
            predefinedValues = PredefinedValues.Empty,
            isAllowSelfSend = true,
            isAddContactAvailable = true,
        ),
        // No feedback: the read-only block is driven one-way by the model.uiState collector ([REDACTED_TASK_KEY]).
        onResult = {},
        onClick = model::showEditDestination,
    )

    private val feeSelectorBlockComponent = feeSelectorBlockComponentFactory.create(
        context = child("sendWithSwapConfirmFeeBlock"),
        params = FeeSelectorBlockParams(
            state = model.uiState.value.feeSelectorUM,
            onLoadFee = model::loadFee,
            onLoadFeeExtended = model::loadFeeExtended,
            feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
            cryptoCurrencyStatus = model.primaryCurrencyStatus,
            feeStateConfiguration = FeeStateConfiguration.ExcludeLow,
            feeDisplaySource = FeeDisplaySource.Screen,
            analyticsCategoryName = params.analyticsCategoryName,
            analyticsSendSource = params.analyticsSendSource,
            userWalletId = params.userWallet.walletId,
            bottomSheetShown = model::onFeeBottomSheetShown,
        ),
        onResult = model::onFeeResult,
    )

    private val sendNotificationsComponent = sendNotificationsComponentFactory.create(
        context = appComponentContext.childByContext(child("sendWithSwapConfirmSendNotifications")),
        params = SendNotificationsComponent.Params(
            analyticsCategoryName = params.analyticsCategoryName,
            userWalletId = params.userWallet.walletId,
            cryptoCurrencyStatus = model.primaryCurrencyStatus,
            appCurrency = params.appCurrency,
            callback = model,
            notificationData = SendNotificationsComponent.Params.NotificationData(
                /**
                 * Null when destination is unknown at this point (e.g. CEX swap — address is only known
                 * after receiving exchange-data). For DEX / DEX_BRIDGE, the address is known upfront.
                 */
                destinationAddress = null,
                memo = null,
                amountValue = model.confirmData.enteredFromAmount.orZero(),
                reduceAmountBy = model.confirmData.reduceAmountBy.orZero(),
                isIgnoreReduce = model.confirmData.isIgnoreReduce,
                fee = model.confirmData.fee,
                feeError = model.confirmData.feeError,
                feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
            ),
        ),
    )

    private val swapNotificationsComponent = SwapNotificationsComponent(
        appComponentContext = appComponentContext.childByContext(child("sendWithSwapConfirmSwapNotifications")),
        params = SwapNotificationsComponent.Params(
            swapNotificationData = SwapNotificationsComponent.Params.SwapNotificationData(
                expressError = (model.confirmData.quote as? SwapQuoteUM.Error)?.expressError,
                fromCryptoCurrency = model.confirmData.fromCryptoCurrencyStatus?.currency,
                destinationAddress = model.confirmData.enteredDestination.orEmpty(),
                memo = model.confirmData.enteredMemo,
                toCryptoCurrencyStatus = model.confirmData.toCryptoCurrencyStatus,
                userWalletId = params.userWallet.walletId,
                enteredFromAmount = model.confirmData.enteredFromAmount,
                fromCryptoCurrencyStatus = model.confirmData.fromCryptoCurrencyStatus,
                priceImpact = model.confirmData.priceImpact,
                amountType = model.confirmData.amountType,
            ),
        ),
    )

    init {
        model.uiState.onEach { state ->
            val confirmUM = state.confirmUM as? ConfirmUM.Content
            blockClickEnableFlow.value = confirmUM?.isTransactionInProcess == false
        }.launchIn(componentScope)

        // Single source of truth: the block always mirrors the model's authoritative destinationUM.
        model.uiState
            .map { it.destinationUM }
            .distinctUntilChanged()
            .onEach(sendDestinationBlockComponent::updateState)
            .launchIn(componentScope)
    }

    fun updateState(sendWithSwapUM: SendWithSwapUM) {
        amountBlockComponent.updateState(sendWithSwapUM.amountUM)
        feeSelectorBlockComponent.updateState(sendWithSwapUM.feeSelectorUM)
        model.updateState(sendWithSwapUM)
    }

    // Re-sync destination from parent on Confirm entry, bypassing the edit-mode gate; Empty only occurs on
    // reset (which leaves Confirm), so only Content is applied ([REDACTED_TASK_KEY]).
    fun updateDestinationState(destinationUM: DestinationUM) {
        if (destinationUM is DestinationUM.Content && destinationUM != model.uiState.value.destinationUM) {
            model.onDestinationResult(destinationUM)
        }
    }

    @Composable
    override fun Title() {
        AppBarWithBackButtonAndIcon(
            text = stringResourceSafe(R.string.send_with_swap_confirm_title),
            onBackClick = router::pop,
            backIconRes = R.drawable.ic_back_24,
            backgroundColor = TangemTheme.colors.background.tertiary,
            modifier = Modifier.height(TangemTheme.dimens.size56),
        )
    }

    @Composable
    override fun Content(modifier: Modifier) {
        val sendWithSwapUM by model.uiState.collectAsStateWithLifecycle()
        val sendNotificationsUM by sendNotificationsComponent.state.collectAsStateWithLifecycle()
        val swapNotificationsUM by swapNotificationsComponent.state.collectAsStateWithLifecycle()

        SendWithSwapConfirmContent(
            sendWithSwapUM = sendWithSwapUM,
            amountBlockComponent = amountBlockComponent,
            sendDestinationBlockComponent = sendDestinationBlockComponent,
            feeSelectorBlockComponent = feeSelectorBlockComponent,
            sendNotificationsComponent = sendNotificationsComponent,
            sendNotificationsUM = sendNotificationsUM,
            swapNotificationsComponent = swapNotificationsComponent,
            swapNotificationsUM = swapNotificationsUM,
            modifier = modifier,
        )
    }

    @Composable
    override fun Footer() {
        val state by model.uiState.collectAsStateWithLifecycle()
        Column {
            val confirmUMContent = state.confirmUM as? ConfirmUM.Content
            AnimatedVisibility(
                visible = confirmUMContent != null,
                enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
                exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
            ) {
                val sendFooter = confirmUMContent?.sendingFooter ?: TextReference.EMPTY
                val legalFooter = getAnnotatedStringForLegals(
                    tosUM = confirmUMContent?.tosUM,
                    sendFooter = sendFooter,
                    onClick = urlOpener::openUrl,
                )
                val footerText = remember(sendFooter, legalFooter) {
                    if (sendFooter != TextReference.EMPTY || legalFooter != TextReference.EMPTY) {
                        combinedReference(sendFooter, legalFooter)
                    } else {
                        TextReference.EMPTY
                    }
                }
                SendingText(footerText = footerText)
            }
            NavigationPrimaryButton(
                primaryButton = model.primaryButtonUM(state.confirmUM),
                modifier = Modifier.padding(
                    start = 16.dp,
                    end = 16.dp,
                    bottom = 16.dp,
                ),
            )
        }
    }

    @Composable
    private fun getAnnotatedStringForLegals(
        tosUM: ConfirmUM.Content.TosUM?,
        sendFooter: TextReference,
        onClick: (String) -> Unit,
    ): TextReference {
        if (tosUM == null) return TextReference.EMPTY
        val tos = tosUM.tosLink
        val policy = tosUM.policyLink
        return if (tos != null && policy != null) {
            val tosTitle = tos.title.resolveReference()
            val policyTitle = policy.title.resolveReference()
            val fullString = stringResourceSafe(id = R.string.express_legal_two_placeholders, tosTitle, policyTitle)
            val tosIndex = fullString.indexOf(tosTitle)
            val policyIndex = fullString.indexOf(policyTitle)

            annotatedReference {
                if (!sendFooter.resolveReference().endsWith(StringsSigns.POINT_SIGN)) {
                    append(StringsSigns.POINT_SIGN)
                }
                appendSpace()
                append(fullString.substring(0, tosIndex))
                withLink(
                    link = LinkAnnotation.Clickable(
                        tag = "TOS_TAG",
                        linkInteractionListener = { onClick(tos.link) },
                    ),
                    block = {
                        appendColored(
                            text = fullString.substring(tosIndex, tosIndex + tosTitle.length),
                            color = TangemTheme.colors.text.accent,
                        )
                    },
                )
                append(fullString.substring(tosIndex + tosTitle.length, policyIndex))
                withLink(
                    link = LinkAnnotation.Clickable(
                        tag = "POLICY_TAG",
                        linkInteractionListener = { onClick(policy.link) },
                    ),
                    block = {
                        appendColored(
                            text = fullString.substring(policyIndex, policyIndex + policyTitle.length),
                            color = TangemTheme.colors.text.accent,
                        )
                    },
                )
            }
        } else {
            val legal = requireNotNull(tos ?: policy) { "tos or policy must not be null" }
            val legalTitle = legal.title.resolveReference()
            val fullString = stringResourceSafe(id = R.string.express_legal_one_placeholder, legalTitle)
            val legalIndex = fullString.indexOf(legalTitle)

            annotatedReference {
                append(fullString.substring(0, legalIndex))
                withLink(
                    link = LinkAnnotation.Clickable(
                        tag = "LEGAL_TAG",
                        linkInteractionListener = { onClick(legal.link) },
                    ),
                    block = {
                        appendColored(
                            text = fullString.substring(legalIndex, legalIndex + legalTitle.length),
                            color = TangemTheme.colors.text.accent,
                        )
                    },
                )
            }
        }
    }

    data class Params(
        val sendWithSwapUM: SendWithSwapUM,
        val analyticsCategoryName: String,
        val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
        val userWallet: UserWallet,
        val appCurrency: AppCurrency,
        val swapDirection: SwapDirection,
        val isBalanceHidingFlow: StateFlow<Boolean>,
        val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
        val primaryFeePaidCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>, // doesn't change if select gasless fee
        val accountFlow: StateFlow<Account?>,
        val isAccountModeFlow: StateFlow<Boolean>,
        val callback: ModelCallback,
    )

    @AssistedFactory
    interface Factory {
        fun create(appComponentContext: AppComponentContext, params: Params): SendWithSwapConfirmComponent
    }

    interface ModelCallback {
        fun onResult(sendWithSwapUM: SendWithSwapUM)
    }
}