//
// ReminderScheduler.swift
// Workouts
//
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//

import Foundation
import SwiftData
import UserNotifications
import OSLog

/// Keeps pending local notifications in sync with the schedules' reminder times.
/// `reconcile()` is an idempotent full resync: it wipes every pending request with
/// the app's `reminder-` identifier prefix and re-adds from the current cache — so
/// deleted schedules drop out naturally, and repeated calls are harmless. Trigger
/// math lives in the pure `ReminderPlanner`.
@MainActor
final class ReminderScheduler {
    private let container: ModelContainer
    private let log = Logger(subsystem: "dev.rzen.indie.Workouts", category: "reminders")
    private var reconcileTask: Task<Void, Never>?

    init(container: ModelContainer) {
        self.container = container
    }

    /// Coalesced entry point for cache-change storms: cancels any not-yet-run
    /// reconcile and schedules a fresh one after a short beat.
    func scheduleReconcile() {
        reconcileTask?.cancel()
        reconcileTask = Task { [weak self] in
            try? await Task.sleep(for: .milliseconds(300))
            guard !Task.isCancelled else { return }
            await self?.reconcile()
        }
    }

    /// Full resync of pending reminder notifications against the schedule cache.
    func reconcile() async {
        let plans = ReminderPlanner.plans(schedules: currentSources())
        let center = UNUserNotificationCenter.current()

        // Wipe-then-add: remove exactly our requests, then re-add the current set.
        let pending = await center.pendingNotificationRequests()
        let stale = pending.map(\.identifier).filter { $0.hasPrefix(ReminderPlanner.identifierPrefix) }
        if !stale.isEmpty {
            center.removePendingNotificationRequests(withIdentifiers: stale)
        }

        guard !plans.isEmpty else { return }

        // Ask permission lazily — only once there is actually something to remind
        // about, so users who never set reminders never see the prompt.
        guard await requestAuthorizationIfNeeded() else {
            log.info("reconcile: notifications not authorized — \(plans.count) reminder(s) not scheduled")
            return
        }

        for plan in plans {
            let content = UNMutableNotificationContent()
            content.title = plan.routineName
            content.body = "Scheduled workout — time to train."
            content.sound = .default
            let trigger = UNCalendarNotificationTrigger(dateMatching: plan.dateComponents,
                                                        repeats: plan.repeats)
            let request = UNNotificationRequest(identifier: plan.identifier,
                                                content: content, trigger: trigger)
            do {
                try await center.add(request)
            } catch {
                log.error("reconcile: failed to add \(plan.identifier): \(error.localizedDescription)")
            }
        }
        log.info("reconcile: scheduled \(plans.count) reminder request(s)")
    }

    /// Requests `.alert`/`.sound` authorization when it has never been asked for;
    /// returns whether reminders can be delivered afterward.
    func requestAuthorizationIfNeeded() async -> Bool {
        let center = UNUserNotificationCenter.current()
        let settings = await center.notificationSettings()
        switch settings.authorizationStatus {
        case .notDetermined:
            return (try? await center.requestAuthorization(options: [.alert, .sound])) ?? false
        case .authorized, .provisional, .ephemeral:
            return true
        case .denied:
            return false
        @unknown default:
            return false
        }
    }

    /// Snapshot the schedule cache into plain planner inputs.
    private func currentSources() -> [ReminderSource] {
        let schedules = (try? container.mainContext.fetch(FetchDescriptor<Schedule>())) ?? []
        return schedules.map { schedule in
            ReminderSource(id: schedule.id,
                           routineName: schedule.routineName,
                           recurrence: schedule.recurrenceEnum,
                           weekdays: schedule.weekdays,
                           date: schedule.date,
                           reminderMinutes: schedule.reminderMinutes)
        }
    }
}
