package dev.privacyllc.period.feature.onboarding

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.privacyllc.period.core.data.CycleRepository
import dev.privacyllc.period.core.datastore.NotificationPrivacy
import dev.privacyllc.period.core.datastore.UserPreferencesRepository
import dev.privacyllc.period.domain.cycle.PeriodRecordSource
import dev.privacyllc.period.domain.prediction.Prediction
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.time.Clock
import java.time.LocalDate
import javax.inject.Inject

/**
 * The seven steps of PRODUCT_PLAN.md §19.
 *
 * An enum rather than a page index, so a step can be skipped or reordered
 * without arithmetic — [Step.PREVIOUS_HISTORY] is skippable by design.
 */
enum class Step {
    WELCOME,
    LAST_PERIOD,
    PERIOD_END,
    PREVIOUS_HISTORY,
    PRIVACY_PROMISE,
    NOTIFICATION_PRIVACY,
    FIRST_FORECAST,
}

data class OnboardingUiState(
    val step: Step = Step.WELCOME,
    val today: LocalDate = LocalDate.ofEpochDay(0),
    val lastPeriodStart: LocalDate? = null,
    val lastPeriodEnd: LocalDate? = null,
    /** Earlier starts the user chose to add. Newest first. */
    val previousStarts: List<LocalDate> = emptyList(),
    val notificationPrivacy: NotificationPrivacy = NotificationPrivacy.DISCREET,
    val forecast: Prediction? = null,
    val saving: Boolean = false,
    val message: String? = null,
) {
    val canContinueFromLastPeriod: Boolean get() = lastPeriodStart != null
}

@HiltViewModel
class OnboardingViewModel @Inject constructor(
    private val repository: CycleRepository,
    private val preferences: UserPreferencesRepository,
    private val clock: Clock,
) : ViewModel() {

    private val _state = MutableStateFlow(OnboardingUiState(today = LocalDate.now(clock)))
    val state: StateFlow<OnboardingUiState> = _state.asStateFlow()

    /** Nothing here may take the app down before the user has even used it. */
    private val handler = CoroutineExceptionHandler { _, e ->
        _state.value = _state.value.copy(
            saving = false,
            message = "Could not save that (${e::class.simpleName}). Nothing was changed.",
        )
    }

    fun back() {
        val steps = Step.entries
        val i = steps.indexOf(_state.value.step)
        if (i > 0) _state.value = _state.value.copy(step = steps[i - 1], message = null)
    }

    fun messageShown() { _state.value = _state.value.copy(message = null) }

    fun toWelcomeNext() = go(Step.LAST_PERIOD)

    fun setLastPeriodStart(date: LocalDate) {
        // A start in the future is not something to correct silently; it is
        // simply not offered — the picker is bounded rather than validated.
        _state.value = _state.value.copy(lastPeriodStart = date)
    }

    fun confirmLastPeriod() = go(Step.PERIOD_END)

    /** "Still going" and "I'm not sure" both mean no end date — never an invented one. */
    fun setPeriodEnd(date: LocalDate?) {
        _state.value = _state.value.copy(lastPeriodEnd = date)
        go(Step.PREVIOUS_HISTORY)
    }

    fun addPreviousStart(date: LocalDate) {
        val current = _state.value
        if (date in current.previousStarts || date == current.lastPeriodStart) {
            _state.value = current.copy(message = "That day is already added.")
            return
        }
        _state.value = current.copy(
            previousStarts = (current.previousStarts + date).sortedDescending(),
            message = null,
        )
    }

    fun removePreviousStart(date: LocalDate) {
        _state.value = _state.value.copy(previousStarts = _state.value.previousStarts - date)
    }

    fun donePreviousHistory() = go(Step.PRIVACY_PROMISE)

    fun donePrivacyPromise() = go(Step.NOTIFICATION_PRIVACY)

    fun setNotificationPrivacy(value: NotificationPrivacy) {
        _state.value = _state.value.copy(notificationPrivacy = value)
    }

    /**
     * Everything the user entered, written at once, then the forecast.
     *
     * Deliberately the last moment rather than as they go: somebody who
     * abandons onboarding halfway has not asked this app to remember anything
     * about them yet.
     */
    fun finishSetup() {
        val current = _state.value
        val start = current.lastPeriodStart ?: return
        _state.value = current.copy(saving = true)

        viewModelScope.launch(handler) {
            // Oldest first, so each confirmation scores against a history that
            // already contains everything before it.
            current.previousStarts.sorted().forEach {
                repository.confirmPeriodStart(it, PeriodRecordSource.HISTORICAL_ENTRY)
            }
            repository.confirmPeriodStart(start, PeriodRecordSource.MANUAL)
            current.lastPeriodEnd?.let { end ->
                repository.confirmedPeriods.first()
                    .firstOrNull { it.startDate == start }
                    ?.let { repository.setPeriodEnd(it.id, end) }
            }

            preferences.setNotificationPrivacy(current.notificationPrivacy)

            _state.value = _state.value.copy(
                saving = false,
                forecast = repository.forecast.first(),
                step = Step.FIRST_FORECAST,
            )
        }
    }

    /**
     * The flag that decides where the app starts, set only here.
     *
     * Written at the end rather than at the beginning so a half-finished
     * onboarding is resumed rather than skipped — the alternative loses whatever
     * the user typed and drops them on an empty Today screen.
     */
    fun complete(onDone: () -> Unit) {
        viewModelScope.launch(handler) {
            preferences.setOnboardingCompleted(true)
            onDone()
        }
    }

    private fun go(step: Step) { _state.value = _state.value.copy(step = step, message = null) }
}
