package dev.privacyllc.period.notifications

import dev.privacyllc.period.core.data.CycleRepository
import dev.privacyllc.period.core.datastore.UserPreferencesRepository
import dev.privacyllc.period.core.notifications.ReminderAction
import dev.privacyllc.period.domain.cycle.PeriodRecordSource
import kotlinx.coroutines.flow.first
import java.time.Clock
import java.time.LocalDate
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Turns a notification action into the same thing the in-app button does.
 *
 * That equivalence is the whole point, and it is easy to get wrong: two paths
 * into one piece of state usually means two slightly different pieces of state.
 * Answering "Not yet" from a lock screen must produce exactly the
 * `NotYetObservation` the Today screen's button produces, or the forecast the
 * user sees when they open the app will not match the one the notification was
 * about.
 *
 * §31 also asks for sensible handling before details are exposed: tapping an
 * action opens the app, which is behind whatever device lock the user has. The
 * action itself records the answer; nothing is displayed until the app is
 * actually in front of them.
 */
@Singleton
class NotificationActionHandler @Inject constructor(
    private val repository: CycleRepository,
    private val preferences: UserPreferencesRepository,
    private val clock: Clock,
) {

    /** Returns true when the action was recognised and applied. */
    suspend fun handle(action: String?): Boolean {
        val today = LocalDate.now(clock)
        // Anything this app did not write — including the action strings used
        // before the buttons carried their own meaning — writes nothing. A
        // notification already sitting in somebody's shade at upgrade time
        // still opens the app; it just does not guess what she meant.
        return when (ReminderAction.fromWireName(action)) {
            ReminderAction.STARTED -> {
                // The question is answered. Nothing resets a counter here: the
                // count is stored against the period it was asked about, so this
                // new start makes the old count read as zero on its own.
                repository.confirmPeriodStart(today, PeriodRecordSource.NOTIFICATION_CONFIRMATION)
                true
            }

            ReminderAction.NOT_YET -> {
                // Exactly what the Today screen's "Not yet" does — same call,
                // same censoring observation, same re-conditioned forecast.
                repository.recordNotYet(today)
                true
            }

            ReminderAction.ENDED -> {
                // Closes the period that is running. NEVER opens one: this
                // button answers "is it over?", and the write that used to sit
                // here answered "did it start?" — inserting a fresh period in
                // the middle of the one it was asking about.
                //
                // Nothing to close is not a failure. She may have ended it in
                // the app between the notification being posted and being
                // tapped, and the honest response to an answered question is
                // silence rather than a second record.
                val open = repository.confirmedPeriods.first()
                    .lastOrNull { it.endDate == null && !it.startDate.isAfter(today) }
                open?.let { repository.setPeriodEnd(it.id, today) }
                true
            }

            ReminderAction.STILL_GOING -> {
                // Deliberately writes nothing. "Still going" is the state the
                // record is already in, and the Today screen's equivalent
                // (setPeriodEnd(id, null)) is a no-op on an open period — one
                // that would still move `updatedAt` and read, in the history,
                // as an edit the user never made.
                true
            }

            null -> false
        }
    }
}
