package dev.privacyllc.period.core.datastore

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
import java.time.LocalTime

/**
 * Typed access to [UserPreferences].
 *
 * Takes the [DataStore] rather than a `Context` so it can be exercised on the
 * JVM against a temporary file — no emulator, no Robolectric. The Android
 * instance is supplied by DI at the app layer, which is the only place that
 * should know where a file lives.
 */
class UserPreferencesRepository(
    private val dataStore: DataStore<Preferences>,
) {

    /**
     * Never throws at the collector.
     *
     * A corrupt or unreadable preferences file must not take the app down: the
     * cycle history is elsewhere and is what the user actually came for, so the
     * honest failure mode is to fall back to defaults and keep working. Note
     * that only [IOException] is swallowed — a programming error still surfaces.
     */
    val preferences: Flow<UserPreferences> = dataStore.data
        .catch { cause -> if (cause is IOException) emit(EMPTY) else throw cause }
        .map(::decode)

    suspend fun setNotificationPrivacy(value: NotificationPrivacy) = edit {
        it[Keys.NotificationPrivacy] = value.name
    }

    suspend fun setReminderTime(value: LocalTime) = edit {
        it[Keys.ReminderMinuteOfDay] = value.hour * 60 + value.minute
    }

    suspend fun setPeriodApproachingEnabled(value: Boolean) = edit { it[Keys.PeriodApproaching] = value }
    suspend fun setPeriodExpectedTodayEnabled(value: Boolean) = edit { it[Keys.PeriodExpectedToday] = value }
    suspend fun setDidItStartEnabled(value: Boolean) = edit { it[Keys.DidItStart] = value }
    suspend fun setPeriodEndCheckInEnabled(value: Boolean) = edit { it[Keys.PeriodEndCheckIn] = value }
    suspend fun setFertileWindowReminderEnabled(value: Boolean) = edit { it[Keys.FertileReminder] = value }
    suspend fun setOvulationReminderEnabled(value: Boolean) = edit { it[Keys.OvulationReminder] = value }
    suspend fun setBiometricLockEnabled(value: Boolean) = edit { it[Keys.BiometricLock] = value }
    suspend fun setTheme(value: AppTheme) = edit { it[Keys.Theme] = value.name }
    suspend fun setOnboardingCompleted(value: Boolean) = edit { it[Keys.OnboardingCompleted] = value }

    /**
     * How many times the app has asked "did it start?" without being told yes.
     *
     * State rather than a setting, and it lives here because it has to survive a
     * reboot — §30's stopping rule is meaningless if the count resets whenever
     * the process dies. Reset when a period is confirmed, which is the event
     * that makes the question moot.
     */
    val checkInCount: Flow<Int> = dataStore.data
        .catch { cause -> if (cause is IOException) emit(EMPTY) else throw cause }
        .map { it[Keys.CheckInCount] ?: 0 }

    suspend fun recordCheckIn() = edit { it[Keys.CheckInCount] = (it[Keys.CheckInCount] ?: 0) + 1 }

    suspend fun resetCheckIns() = edit { it[Keys.CheckInCount] = 0 }

    /**
     * Entitlement, mirrored from Google Play.
     *
     * Play is the source of truth; this is a local cache so the UI does not
     * have to wait on the network to know whether to reserve banner space.
     */
    suspend fun setAdsRemoved(value: Boolean) = edit { it[Keys.AdsRemoved] = value }

    /**
     * Reset the settings, deliberately **not** part of Delete My Data.
     *
     * Wiping the health history must not silently return notification privacy
     * to a default the user did not choose — that would hand back a weaker
     * setting than the one they were relying on, at the exact moment they were
     * exercising a privacy control.
     */
    suspend fun resetToDefaults() = edit { it.clear() }

    private suspend fun edit(block: (androidx.datastore.preferences.core.MutablePreferences) -> Unit) {
        dataStore.edit(block)
    }

    private fun decode(p: Preferences) = UserPreferences(
        notificationPrivacy = p[Keys.NotificationPrivacy]
            ?.let { name -> NotificationPrivacy.entries.firstOrNull { it.name == name } }
            ?: UserPreferences.Defaults.notificationPrivacy,
        reminderTime = p[Keys.ReminderMinuteOfDay]
            ?.takeIf { it in 0 until MINUTES_PER_DAY }
            ?.let { LocalTime.of(it / 60, it % 60) }
            ?: UserPreferences.Defaults.reminderTime,
        periodApproachingEnabled = p[Keys.PeriodApproaching] ?: UserPreferences.Defaults.periodApproachingEnabled,
        periodExpectedTodayEnabled = p[Keys.PeriodExpectedToday] ?: UserPreferences.Defaults.periodExpectedTodayEnabled,
        didItStartEnabled = p[Keys.DidItStart] ?: UserPreferences.Defaults.didItStartEnabled,
        periodEndCheckInEnabled = p[Keys.PeriodEndCheckIn] ?: UserPreferences.Defaults.periodEndCheckInEnabled,
        fertileWindowReminderEnabled = p[Keys.FertileReminder] ?: UserPreferences.Defaults.fertileWindowReminderEnabled,
        ovulationReminderEnabled = p[Keys.OvulationReminder] ?: UserPreferences.Defaults.ovulationReminderEnabled,
        biometricLockEnabled = p[Keys.BiometricLock] ?: UserPreferences.Defaults.biometricLockEnabled,
        theme = p[Keys.Theme]
            ?.let { name -> AppTheme.entries.firstOrNull { it.name == name } }
            ?: UserPreferences.Defaults.theme,
        adsRemoved = p[Keys.AdsRemoved] ?: UserPreferences.Defaults.adsRemoved,
        onboardingCompleted = p[Keys.OnboardingCompleted] ?: UserPreferences.Defaults.onboardingCompleted,
    )

    private object Keys {
        val NotificationPrivacy = stringPreferencesKey("notification_privacy")
        val ReminderMinuteOfDay = intPreferencesKey("reminder_minute_of_day")
        val PeriodApproaching = booleanPreferencesKey("reminder_period_approaching")
        val PeriodExpectedToday = booleanPreferencesKey("reminder_period_expected_today")
        val DidItStart = booleanPreferencesKey("reminder_did_it_start")
        val PeriodEndCheckIn = booleanPreferencesKey("reminder_period_end_check_in")
        val CheckInCount = intPreferencesKey("check_in_count")
        val FertileReminder = booleanPreferencesKey("fertile_window_reminder_enabled")
        val OvulationReminder = booleanPreferencesKey("ovulation_reminder_enabled")
        val BiometricLock = booleanPreferencesKey("biometric_lock_enabled")
        val Theme = stringPreferencesKey("theme")
        val AdsRemoved = booleanPreferencesKey("ads_removed")
        val OnboardingCompleted = booleanPreferencesKey("onboarding_completed")
    }

    private companion object {
        const val MINUTES_PER_DAY = 24 * 60
        val EMPTY: Preferences = androidx.datastore.preferences.core.emptyPreferences()
    }
}
