package com.yorvana.data.preferences

import android.content.Context
import android.content.SharedPreferences
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.google.common.truth.Truth.assertThat
import com.yorvana.data.model.OdometerUnit
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test

class AppPreferencesImplTest {
    private val context = mockk<Context>()
    private val dataStore = mockk<DataStore<Preferences>>()
    private val sharedPrefs = mockk<SharedPreferences>(relaxed = true)
    private val sharedPrefsEditor = mockk<SharedPreferences.Editor>(relaxed = true)
    private lateinit var appPreferences: AppPreferencesImpl

    @Before
    fun setUp() {
        every { context.getSharedPreferences("app_preferences_sync", Context.MODE_PRIVATE) } returns sharedPrefs
        every { sharedPrefs.edit() } returns sharedPrefsEditor
        every { sharedPrefsEditor.putBoolean(any(), any()) } returns sharedPrefsEditor
        every { dataStore.data } returns flowOf(emptyPreferences())
        appPreferences = AppPreferencesImpl(context, dataStore)
    }

    @After
    fun tearDown() {
    }

    @Test
    fun `preferences should emit default values when store is empty`() =
        runTest {
            every { dataStore.data } returns flowOf(emptyPreferences())

            val prefs = appPreferences.preferences.first()
            assertThat(prefs.vaultUriString).isNull()
            assertThat(prefs.defaultOdometerUnit).isEqualTo(OdometerUnit.KM)
            assertThat(prefs.defaultCurrency).isEqualTo("USD")
            assertThat(prefs.isPremiumCached).isFalse()
            assertThat(prefs.debugBillingOverrideMode).isEqualTo(DebugBillingOverrideMode.NONE)
            assertThat(prefs.crashReportingEnabled).isFalse()
            assertThat(prefs.crashReportingConsentPromptShownVersion).isEqualTo(0)
        }

    @Test
    fun `preferences should emit stored values`() =
        runTest {
            val storedPrefs = mockk<Preferences>()
            every { storedPrefs[stringPreferencesKey("vault_uri")] } returns "content://vault"
            every { storedPrefs[stringPreferencesKey("odometer_unit")] } returns "MI"
            every { storedPrefs[stringPreferencesKey("currency")] } returns "EUR"
            every { storedPrefs[booleanPreferencesKey("is_premium_cached")] } returns true
            every { storedPrefs[stringPreferencesKey("debug_billing_override_mode")] } returns "FORCE_PREMIUM"
            every { storedPrefs[booleanPreferencesKey("crash_reporting_enabled")] } returns true
            every { storedPrefs[intPreferencesKey("crash_reporting_consent_prompt_shown_version")] } returns 1
            every { dataStore.data } returns flowOf(storedPrefs)
            appPreferences = AppPreferencesImpl(context, dataStore)

            val prefs = appPreferences.preferences.first()
            assertThat(prefs.vaultUriString).isEqualTo("content://vault")
            assertThat(prefs.defaultOdometerUnit).isEqualTo(OdometerUnit.MI)
            assertThat(prefs.defaultCurrency).isEqualTo("EUR")
            assertThat(prefs.isPremiumCached).isTrue()
            assertThat(prefs.debugBillingOverrideMode).isEqualTo(DebugBillingOverrideMode.FORCE_PREMIUM)
            assertThat(prefs.crashReportingEnabled).isTrue()
            assertThat(prefs.crashReportingConsentPromptShownVersion).isEqualTo(CURRENT_CONSENT_VERSION)
        }

    @Test
    fun `setVaultUri should update store`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setVaultUri("new-uri")
            coVerify { dataStore.updateData(any()) }
        }

    @Test
    fun `setDefaultOdometerUnit should update store`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setDefaultOdometerUnit(OdometerUnit.MI)
            coVerify { dataStore.updateData(any()) }
        }

    @Test
    fun `setDefaultCurrency should update store`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setDefaultCurrency("GBP")
            coVerify { dataStore.updateData(any()) }
        }

    @Test
    fun `setIsPremiumCached should update store`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setIsPremiumCached(true)
            coVerify { dataStore.updateData(any()) }
        }

    @Test
    fun `setDebugBillingOverrideMode should update store`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setDebugBillingOverrideMode(DebugBillingOverrideMode.FORCE_PREMIUM)
            coVerify { dataStore.updateData(any()) }
        }

    @Test
    fun `setCrashReportingEnabled should update store and shared prefs`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setCrashReportingEnabled(true)
            coVerify { dataStore.updateData(any()) }
            coVerify { sharedPrefsEditor.putBoolean("crash_reporting_enabled", true) }
        }

    @Test
    fun `setCrashReportingConsentPromptShownVersion should update store`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.setCrashReportingConsentPromptShownVersion(1)
            coVerify { dataStore.updateData(any()) }
        }

    @Test
    fun `recordConsentDecision should write enabled state and consent version`() =
        runTest {
            coEvery { dataStore.updateData(any()) } returns mockk()
            appPreferences.recordConsentDecision(true)
            coVerify { sharedPrefsEditor.putBoolean("crash_reporting_enabled", true) }
            coVerify(exactly = 1) { dataStore.updateData(any()) }
        }

    @Test
    fun `isCrashReportingEnabledSync should read from shared prefs`() {
        every { sharedPrefs.getBoolean("crash_reporting_enabled", false) } returns true
        assertThat(appPreferences.isCrashReportingEnabledSync()).isTrue()
    }

    private fun emptyPreferences(): Preferences =
        mockk<Preferences> {
            every { get(any<Preferences.Key<*>>()) } returns null
        }
}
