package dev.privacyllc.period.feature.settings

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.privacyllc.period.core.datastore.NotificationPrivacy
import dev.privacyllc.period.core.datastore.UserPreferences
import dev.privacyllc.period.core.datastore.UserPreferencesRepository
import dev.privacyllc.period.core.notifications.ReminderScheduler
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.time.LocalTime
import javax.inject.Inject

/**
 * §29's six independent toggles, the reminder time, and §28's privacy mode.
 *
 * The designed Settings screen is Batch 06. This is the plumbing plus enough
 * surface to change a setting and see the reminder change — deliberately plain,
 * and it says so on screen.
 */
@HiltViewModel
class NotificationSettingsViewModel @Inject constructor(
    private val preferences: UserPreferencesRepository,
    private val scheduler: ReminderScheduler,
) : ViewModel() {

    val state: StateFlow<UserPreferences> =
        preferences.preferences.stateIn(
            viewModelScope,
            SharingStarted.WhileSubscribed(5_000),
            UserPreferences.Defaults,
        )

    /**
     * Set when a toggle is switched on without the notification permission.
     *
     * §31: ask at an appropriate moment, not blindly on first launch. This is
     * that moment — the user has just said they want a reminder, so the dialog
     * is an answer to something they did rather than an interruption before
     * they have seen the app.
     */
    private val _needsPermission = MutableStateFlow(false)
    val needsPermission: StateFlow<Boolean> = _needsPermission.asStateFlow()

    fun permissionHandled() { _needsPermission.value = false }

    private val handler = CoroutineExceptionHandler { _, _ -> }

    private fun update(requestPermissionIfEnabling: Boolean = true, block: suspend () -> Unit) =
        viewModelScope.launch(handler) {
            block()
            if (requestPermissionIfEnabling) _needsPermission.value = true
            reschedule()
        }

    fun setPrivacy(value: NotificationPrivacy) =
        update(requestPermissionIfEnabling = false) { preferences.setNotificationPrivacy(value) }

    fun setPeriodApproaching(on: Boolean) = update(on) { preferences.setPeriodApproachingEnabled(on) }
    fun setPeriodExpectedToday(on: Boolean) = update(on) { preferences.setPeriodExpectedTodayEnabled(on) }
    fun setDidItStart(on: Boolean) = update(on) { preferences.setDidItStartEnabled(on) }
    fun setPeriodEndCheckIn(on: Boolean) = update(on) { preferences.setPeriodEndCheckInEnabled(on) }
    fun setFertileWindow(on: Boolean) = update(on) { preferences.setFertileWindowReminderEnabled(on) }
    fun setOvulation(on: Boolean) = update(on) { preferences.setOvulationReminderEnabled(on) }

    fun setReminderTime(time: LocalTime) =
        update(requestPermissionIfEnabling = false) { preferences.setReminderTime(time) }

    /**
     * Re-enqueue whenever anything that decides a reminder changes.
     *
     * §31 asks for rescheduling after a confirmation or a forecast change, and
     * the same argument covers a settings change: work already queued was
     * queued against the old answer.
     *
     * Cancelled outright when every toggle is off, rather than left running to
     * wake up and decide there is nothing to do — a background job that exists
     * only to do nothing is a battery cost with no user.
     */
    private suspend fun reschedule() {
        val prefs = preferences.preferences.first()
        val anyEnabled = prefs.periodApproachingEnabled ||
            prefs.periodExpectedTodayEnabled ||
            prefs.didItStartEnabled ||
            prefs.periodEndCheckInEnabled ||
            prefs.fertileWindowReminderEnabled ||
            prefs.ovulationReminderEnabled

        if (anyEnabled) scheduler.schedule(prefs.reminderTime) else scheduler.cancel()
    }

    /** Whether anything at all is switched on, for the screen's summary line. */
    val anyReminderOn: StateFlow<Boolean> =
        preferences.preferences.map {
            it.periodApproachingEnabled || it.periodExpectedTodayEnabled || it.didItStartEnabled ||
                it.periodEndCheckInEnabled || it.fertileWindowReminderEnabled || it.ovulationReminderEnabled
        }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), true)
}
