package com.tangem.features.pushnotifications.impl.model

import arrow.core.Either
import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.pushnotificationpreferences.IsPushNotificationFirstActivationDoneUseCase
import com.tangem.domain.pushnotificationpreferences.MarkPushNotificationFirstActivationDoneUseCase
import com.tangem.domain.pushnotificationpreferences.SetAllWalletPushNotificationPreferencesUseCase
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
import com.tangem.domain.wallets.usecase.SetNotificationsEnabledUseCase
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
import com.tangem.features.pushnotifications.api.PushNotificationsParams
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.features.pushnotifications.impl.domain.GetPushNotificationsDoubleAskVariantUseCase
import com.tangem.features.pushnotifications.impl.domain.DoubleAskVariant
import com.tangem.features.pushnotificationsettings.PushNotificationSettingsFeatureToggles
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

@OptIn(ExperimentalCoroutinesApi::class)
internal class PushNotificationsModelTest {

    private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase = mockk(relaxed = true)
    private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase = mockk(relaxed = true)
    private val appRouter: AppRouter = mockk(relaxed = true)
    private val analyticHandler: AnalyticsEventHandler = mockk(relaxed = true)
    private val notificationsRepository: NotificationsRepository = mockk(relaxed = true)
    private val pushNotificationSettingsFeatureToggles: PushNotificationSettingsFeatureToggles = mockk(relaxed = true)
    private val setAllWalletPushNotificationPreferences: SetAllWalletPushNotificationPreferencesUseCase =
        mockk(relaxed = true)
    private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxed = true)
    private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase = mockk(relaxed = true)
    private val isPushNotificationFirstActivationDone: IsPushNotificationFirstActivationDoneUseCase =
        mockk(relaxed = true)
    private val markPushNotificationFirstActivationDone: MarkPushNotificationFirstActivationDoneUseCase =
        mockk(relaxed = true)
    private val getDoubleAskVariantUseCase: GetPushNotificationsDoubleAskVariantUseCase = mockk()
    private val modelCallbacks: PushNotificationsModelCallbacks = mockk(relaxed = true)

    @BeforeEach
    fun setUp() {
        coEvery { getDoubleAskVariantUseCase() } returns DoubleAskVariant.Off
    }

    @Test
    fun `GIVEN onboarding treatment WHEN onLaterClick THEN double ask shown and not proceeded`() = runTest {
        coEvery { getDoubleAskVariantUseCase() } returns DoubleAskVariant.On
        val model = createModel(testScope = this)
        advanceUntilIdle()

        model.onLaterClick()
        advanceUntilIdle()

        assertThat(model.isDoubleAskSheetShown.value).isTrue()
        verify {
            analyticHandler.send(
                match<PushNotificationAnalyticEvents.WarningScreenShown> {
                    it.variant == DoubleAskVariant.On.key
                },
            )
        }
        coVerify(exactly = 0) { neverRequestPermissionUseCase(any()) }
        verify(exactly = 0) { modelCallbacks.onDenySystemPermission() }
    }

    @Test
    fun `GIVEN onboarding control WHEN onLaterClick THEN proceeds without double ask`() = runTest {
        coEvery { getDoubleAskVariantUseCase() } returns DoubleAskVariant.Off
        val model = createModel(testScope = this)
        advanceUntilIdle()

        model.onLaterClick()
        advanceUntilIdle()

        assertThat(model.isDoubleAskSheetShown.value).isFalse()
        coVerify { neverRequestPermissionUseCase(any()) }
        coVerify { neverToInitiallyAskPermissionUseCase(any()) }
        verify { modelCallbacks.onDenySystemPermission() }
        verify(exactly = 0) {
            analyticHandler.send(match<PushNotificationAnalyticEvents.WarningScreenShown> { true })
        }
    }

    @Test
    fun `GIVEN main bottom sheet WHEN onLaterClick THEN double ask not shown and variant not resolved`() = runTest {
        val model = createModel(
            testScope = this,
            isBottomSheet = true,
            source = AppRoute.PushNotification.Source.Main,
        )
        advanceUntilIdle()

        model.onLaterClick()
        advanceUntilIdle()

        assertThat(model.isDoubleAskSheetShown.value).isFalse()
        coVerify(exactly = 0) { getDoubleAskVariantUseCase() }
        verify { modelCallbacks.onDenySystemPermission() }
    }

    @Test
    fun `GIVEN double ask shown WHEN onDoubleAskEnableClick THEN enable tapped sent and not proceeded`() = runTest {
        coEvery { getDoubleAskVariantUseCase() } returns DoubleAskVariant.On
        val model = createModel(testScope = this)
        advanceUntilIdle()
        model.onLaterClick()
        advanceUntilIdle()

        model.onDoubleAskEnableClick()
        advanceUntilIdle()

        verify {
            analyticHandler.send(match<PushNotificationAnalyticEvents.WarningScreenEnableTapped> { true })
        }
        coVerify { notificationsRepository.setUserAllowToSubscribeOnPushNotifications(true) }
        verify(exactly = 0) { modelCallbacks.onDenySystemPermission() }
    }

    @Test
    fun `GIVEN double ask shown WHEN onDoubleAskSkipClick THEN event sent and proceeded`() = runTest {
        coEvery { getDoubleAskVariantUseCase() } returns DoubleAskVariant.On
        val model = createModel(testScope = this)
        advanceUntilIdle()
        model.onLaterClick()
        advanceUntilIdle()

        model.onDoubleAskSkipClick()
        advanceUntilIdle()

        verify {
            analyticHandler.send(match<PushNotificationAnalyticEvents.WarningScreenSkipTapped> { true })
        }
        coVerify { neverRequestPermissionUseCase(any()) }
        verify { modelCallbacks.onDenySystemPermission() }
    }

    @Test
    fun `GIVEN double ask shown WHEN onDoubleAskDismiss THEN sheet hidden and not proceeded`() = runTest {
        coEvery { getDoubleAskVariantUseCase() } returns DoubleAskVariant.On
        val model = createModel(testScope = this)
        advanceUntilIdle()
        model.onLaterClick()
        advanceUntilIdle()

        model.onDoubleAskDismiss()
        advanceUntilIdle()

        assertThat(model.isDoubleAskSheetShown.value).isFalse()
        verify(exactly = 0) { modelCallbacks.onDenySystemPermission() }
        verify(exactly = 0) {
            analyticHandler.send(match<PushNotificationAnalyticEvents.WarningScreenSkipTapped> { true })
        }
    }

    @Test
    fun `GIVEN settings enabled WHEN onAllowPermission THEN fresh wallets get all-three and done wallets skipped`() =
        runTest {
            every { pushNotificationSettingsFeatureToggles.isPushNotificationSettingsEnabled } returns true
            val fresh = UserWalletId("aa")
            val done = UserWalletId("bb")
            coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(wallet(fresh), wallet(done))
            coEvery { isPushNotificationFirstActivationDone(fresh) } returns false
            coEvery { isPushNotificationFirstActivationDone(done) } returns true
            coEvery { setNotificationsEnabledUseCase(any(), any()) } returns Either.Right(Unit)
            coEvery {
                setAllWalletPushNotificationPreferences(any(), any(), any(), any())
            } returns Either.Right(Unit)
            val model = createModel(testScope = this)
            advanceUntilIdle()

            model.onAllowPermission()
            advanceUntilIdle()

            // Fresh wallet: tokens (re-subscription) before preferences, then flag marked.
            coVerifyOrder {
                setNotificationsEnabledUseCase(fresh, isEnabled = true)
                setAllWalletPushNotificationPreferences(fresh, true, true, true)
            }
            coVerify(exactly = 1) { markPushNotificationFirstActivationDone(fresh) }
            // Already-activated wallet: skipped entirely.
            coVerify(exactly = 0) { setAllWalletPushNotificationPreferences(done, any(), any(), any()) }
            coVerify(exactly = 0) { setNotificationsEnabledUseCase(done, any()) }
            coVerify(exactly = 0) { markPushNotificationFirstActivationDone(done) }
        }

    @Test
    fun `GIVEN settings enabled WHEN onDenyPermission THEN first-activation flag marked for all wallets`() = runTest {
        every { pushNotificationSettingsFeatureToggles.isPushNotificationSettingsEnabled } returns true
        val w1 = UserWalletId("aa")
        val w2 = UserWalletId("bb")
        coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(wallet(w1), wallet(w2))
        val model = createModel(testScope = this)
        advanceUntilIdle()

        model.onDenyPermission()
        advanceUntilIdle()

        coVerify(exactly = 1) { markPushNotificationFirstActivationDone(w1) }
        coVerify(exactly = 1) { markPushNotificationFirstActivationDone(w2) }
        // Deny only fixes the flag — it must not enable any category or re-subscribe tokens.
        coVerify(exactly = 0) { setAllWalletPushNotificationPreferences(any(), any(), any(), any()) }
        coVerify(exactly = 0) { setNotificationsEnabledUseCase(any(), any()) }
    }

    @Test
    fun `GIVEN settings disabled WHEN onAllowPermission THEN no first-activation writes`() = runTest {
        every { pushNotificationSettingsFeatureToggles.isPushNotificationSettingsEnabled } returns false
        // Non-empty wallet list + not-done + successful stubs so that, if the toggle gate were removed,
        // applyFirstActivationRule WOULD iterate and write — making the exactly=0 assertions actually protect the gate.
        coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(wallet(UserWalletId("aa")))
        coEvery { isPushNotificationFirstActivationDone(any()) } returns false
        coEvery { setNotificationsEnabledUseCase(any(), any()) } returns Either.Right(Unit)
        coEvery { setAllWalletPushNotificationPreferences(any(), any(), any(), any()) } returns Either.Right(Unit)
        val model = createModel(testScope = this)
        advanceUntilIdle()

        model.onAllowPermission()
        advanceUntilIdle()

        coVerify(exactly = 0) { setAllWalletPushNotificationPreferences(any(), any(), any(), any()) }
        coVerify(exactly = 0) { setNotificationsEnabledUseCase(any(), any()) }
        coVerify(exactly = 0) { markPushNotificationFirstActivationDone(any()) }
    }

    private fun wallet(id: UserWalletId): UserWallet = mockk { every { walletId } returns id }

    private fun createModel(
        testScope: TestScope,
        isBottomSheet: Boolean = false,
        source: AppRoute.PushNotification.Source = AppRoute.PushNotification.Source.Onboarding,
        paramsContainer: ParamsContainer = MutableParamsContainer(
            value = PushNotificationsParams(
                isBottomSheet = isBottomSheet,
                nextRoute = null,
                modelCallbacks = modelCallbacks,
                source = source,
            ),
        ),
    ): PushNotificationsModel {
        return PushNotificationsModel(
            paramsContainer = paramsContainer,
            dispatchers = testScope.createTestingCoroutineDispatcherProvider(),
            neverRequestPermissionUseCase = neverRequestPermissionUseCase,
            neverToInitiallyAskPermissionUseCase = neverToInitiallyAskPermissionUseCase,
            appRouter = appRouter,
            analyticHandler = analyticHandler,
            notificationsRepository = notificationsRepository,
            pushNotificationSettingsFeatureToggles = pushNotificationSettingsFeatureToggles,
            setAllWalletPushNotificationPreferences = setAllWalletPushNotificationPreferences,
            userWalletsListRepository = userWalletsListRepository,
            setNotificationsEnabledUseCase = setNotificationsEnabledUseCase,
            isPushNotificationFirstActivationDone = isPushNotificationFirstActivationDone,
            markPushNotificationFirstActivationDone = markPushNotificationFirstActivationDone,
            getPushNotificationsDoubleAskVariantUseCase = getDoubleAskVariantUseCase,
        )
    }

    private fun TestScope.createTestingCoroutineDispatcherProvider(): TestingCoroutineDispatcherProvider {
        val testDispatcher = StandardTestDispatcher(testScheduler)
        return TestingCoroutineDispatcherProvider(
            main = testDispatcher,
            mainImmediate = testDispatcher,
            io = testDispatcher,
            default = testDispatcher,
            single = testDispatcher,
        )
    }
}