package dev.privacyllc.period.core.notifications

import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.util.concurrent.TimeUnit

/**
 * Schedules the daily reminder check with WorkManager.
 *
 * §31: **no exact alarms.** A period reminder does not need alarm-clock
 * precision, and `SCHEDULE_EXACT_ALARM` is Play scrutiny bought for nothing —
 * so this is a periodic work request with a flex window, which the system is
 * free to move by minutes. Nobody notices a reminder arriving at 10:07 instead
 * of 10:00; everybody notices an app asking for an alarm permission.
 *
 * The work is `KEEP`-replaced on every reschedule rather than cancelled and
 * re-enqueued, so a forecast that moves twice in a minute does not produce two
 * pending workers.
 */
class ReminderScheduler(private val context: Context) {

    /**
     * (Re)schedule the daily check for [reminderTime].
     *
     * Called on every change that could move a reminder: the forecast moving, a
     * toggle changing, the reminder time changing. §31 lists rescheduling after
     * a confirmation or a forecast change explicitly, and it is the requirement
     * most likely to be missed — a "Not yet" moves the forecast, so a reminder
     * aimed at the old one is now aimed at the wrong day.
     */
    fun schedule(reminderTime: LocalTime, zone: ZoneId = ZoneId.systemDefault()) {
        val request = PeriodicWorkRequestBuilder<ReminderWorker>(1, TimeUnit.DAYS)
            .setInitialDelay(delayUntil(reminderTime, zone).toMinutes(), TimeUnit.MINUTES)
            .setConstraints(
                // No network, no charging, no idle. A reminder that waits for
                // Wi-Fi is a reminder that arrives on Tuesday.
                Constraints.Builder().build(),
            )
            .addTag(TAG)
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            WORK_NAME,
            ExistingPeriodicWorkPolicy.UPDATE,
            request,
        )
    }

    fun cancel() {
        WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
    }

    companion object {
        const val WORK_NAME = "period-daily-reminder"
        const val TAG = "reminder"

        /**
         * Time from now until the next [target] o'clock.
         *
         * Pure and internal so it can be tested without a clock or a device —
         * off-by-a-day here means a user's first reminder arrives tomorrow
         * instead of today, which is exactly the kind of thing nobody notices
         * until they are the user.
         */
        internal fun delayUntil(
            target: LocalTime,
            zone: ZoneId,
            now: LocalDateTime = LocalDateTime.now(zone),
        ): Duration {
            val todayAt = LocalDateTime.of(now.toLocalDate(), target)
            val next = if (todayAt.isAfter(now)) todayAt else LocalDateTime.of(
                now.toLocalDate().plusDays(1),
                target,
            )
            return Duration.between(now, next)
        }

        internal fun nextRunDate(target: LocalTime, now: LocalDateTime): LocalDate =
            if (LocalDateTime.of(now.toLocalDate(), target).isAfter(now)) {
                now.toLocalDate()
            } else {
                now.toLocalDate().plusDays(1)
            }
    }
}
