package dev.privacyllc.period.core.notifications

import android.app.PendingIntent
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dev.privacyllc.period.core.data.CycleRepository
import dev.privacyllc.period.core.datastore.UserPreferences
import dev.privacyllc.period.core.datastore.UserPreferencesRepository
import dev.privacyllc.period.domain.prediction.CycleStatusRules
import dev.privacyllc.period.domain.prediction.FertilityEstimate
import kotlinx.coroutines.flow.first
import java.time.Clock
import java.time.LocalDate

/**
 * The daily wake-up.
 *
 * It reads state and asks [ReminderRules] what to do — it does **no cycle
 * arithmetic of its own**, deliberately. A worker that recomputes "is a period
 * due" is a second implementation of `CycleStatusRules`, and the first time the
 * two disagree the user gets a notification that contradicts the screen it opens.
 *
 * Nothing here logs a cycle date. §45's rule applies to background work exactly
 * as much as to the UI, and a `Log.d` in a worker is the kind that survives to
 * production because nobody sees it in testing.
 */
@HiltWorker
class ReminderWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted params: WorkerParameters,
    private val repository: CycleRepository,
    private val preferences: UserPreferencesRepository,
    private val clock: Clock,
) : CoroutineWorker(appContext, params) {

    override suspend fun doWork(): Result {
        val prefs = preferences.preferences.first()
        val today = LocalDate.now(clock)

        val periods = repository.confirmedPeriods.first()
        val forecast = repository.forecast.first()
        val notYet = repository.notYetObservations.first()
        val fertility = FertilityEstimate.from(forecast)

        val status = CycleStatusRules.statusFor(
            periods = periods,
            forecast = forecast,
            today = today,
            fertileWindow = fertility?.fertileWindow,
            ovulation = fertility?.ovulationDate,
            originalForecastDate = notYet.minOfOrNull { it.date },
        )

        val decision = ReminderRules.decide(
            status = status,
            preferences = prefs.toReminderPreferences(),
            checkInsSoFar = preferences.checkInCount.first(),
            fertility = fertility,
            today = today,
        )

        val notifier = PeriodNotifier(applicationContext)

        when (decision) {
            ReminderDecision.Nothing -> return Result.success()

            ReminderDecision.StopAsking -> {
                // §30's exact sentiment, and it is the only notification in the
                // app that exists to say the app will now be quiet.
                notifier.notify(
                    text = NotificationText(
                        publicTitle = if (prefs.notificationPrivacy ==
                            dev.privacyllc.period.core.datastore.NotificationPrivacy.DIRECT
                        ) "We'll stop checking for now" else "Reminder",
                        publicBody = null,
                        privateTitle = "We'll stop checking for now",
                        privateBody = "Log your period whenever it begins.",
                        visibility = LockScreenVisibility.PRIVATE,
                    ),
                    privacy = prefs.notificationPrivacy,
                    contentIntent = openApp(),
                )
                // Counted, so the next wake-up sees a number past the limit and
                // sends nothing.
                preferences.recordCheckIn()
                return Result.success()
            }

            is ReminderDecision.Send -> {
                val text = NotificationCopy.textFor(decision.kind, prefs.notificationPrivacy, decision.daysUntil)
                val labels = NotificationCopy.actionLabels(decision.kind, prefs.notificationPrivacy)

                notifier.notify(
                    text = text,
                    privacy = prefs.notificationPrivacy,
                    contentIntent = openApp(),
                    actions = labels.mapIndexed { index, label ->
                        NotificationCompat.Action.Builder(
                            0,
                            label,
                            openApp(
                                if (index == 0) PeriodNotifier.ACTION_STARTED else PeriodNotifier.ACTION_NOT_YET,
                            ),
                        ).build()
                    },
                )

                // Only the "did it start" family counts toward the stopping
                // rule. A heads-up three days out is not the app pestering.
                if (decision.kind == ReminderKind.DID_IT_START ||
                    decision.kind == ReminderKind.PERIOD_EXPECTED_TODAY
                ) {
                    preferences.recordCheckIn()
                }
                return Result.success()
            }
        }
    }

    private fun openApp(action: String? = null): PendingIntent =
        PendingIntent.getActivity(
            applicationContext,
            action?.hashCode() ?: 0,
            PeriodNotifier.launchIntent(applicationContext, action),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )
}

/** The six toggles, as the rules want them. */
fun UserPreferences.toReminderPreferences() = ReminderPreferences(
    periodApproaching = periodApproachingEnabled,
    periodExpectedToday = periodExpectedTodayEnabled,
    didItStart = didItStartEnabled,
    periodEndCheckIn = periodEndCheckInEnabled,
    fertileWindowApproaching = fertileWindowReminderEnabled,
    ovulationEstimated = ovulationReminderEnabled,
)
