/**
 * DailyNotificationRollingWindow.swift
 * 
 * iOS Rolling window safety for notification scheduling
 * Ensures today's notifications are always armed and tomorrow's are armed within iOS caps
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

import Foundation
import UserNotifications

/**
 * Manages rolling window safety for notification scheduling on iOS
 * 
 * This class implements the critical rolling window logic:
 * - Today's remaining notifications are always armed
 * - Tomorrow's notifications are armed only if within iOS capacity limits
 * - Automatic window maintenance as time progresses
 * - iOS-specific capacity management
 */
class DailyNotificationRollingWindow {
    
    // MARK: - Constants
    
    private static let TAG = "DailyNotificationRollingWindow"
    
    // iOS notification limits
    private static let IOS_MAX_PENDING_NOTIFICATIONS = 64
    private static let IOS_MAX_DAILY_NOTIFICATIONS = 20
    
    // Window maintenance intervals
    private static let WINDOW_MAINTENANCE_INTERVAL_SECONDS: TimeInterval = 15 * 60 // 15 minutes
    
    // MARK: - Properties
    
    private let ttlEnforcer: DailyNotificationTTLEnforcer
    private let database: DailyNotificationDatabase?
    private let useSharedStorage: Bool
    
    // Window state
    private var lastMaintenanceTime: Date = Date.distantPast
    private var currentPendingCount: Int = 0
    private var currentDailyCount: Int = 0
    
    // MARK: - Initialization
    
    /**
     * Initialize rolling window manager
     * 
     * @param ttlEnforcer TTL enforcement instance
     * @param database SQLite database (nil if using UserDefaults)
     * @param useSharedStorage Whether to use SQLite or UserDefaults
     */
    init(ttlEnforcer: DailyNotificationTTLEnforcer, 
         database: DailyNotificationDatabase?, 
         useSharedStorage: Bool) {
        self.ttlEnforcer = ttlEnforcer
        self.database = database
        self.useSharedStorage = useSharedStorage
        
        print("\(Self.TAG): Rolling window initialized for iOS")
    }
    
    // MARK: - Window Maintenance
    
    /**
     * Maintain the rolling window by ensuring proper notification coverage
     * 
     * This method should be called periodically to maintain the rolling window:
     * - Arms today's remaining notifications
     * - Arms tomorrow's notifications if within capacity limits
     * - Updates window state and statistics
     */
    func maintainRollingWindow() {
        do {
            let currentTime = Date()
            
            // Check if maintenance is needed
            if currentTime.timeIntervalSince(lastMaintenanceTime) < Self.WINDOW_MAINTENANCE_INTERVAL_SECONDS {
                print("\(Self.TAG): Window maintenance not needed yet")
                return
            }
            
            print("\(Self.TAG): Starting rolling window maintenance")
            
            // Update current state
            updateWindowState()
            
            // Arm today's remaining notifications
            armTodaysRemainingNotifications()
            
            // Arm tomorrow's notifications if within capacity
            armTomorrowsNotificationsIfWithinCapacity()
            
            // Update maintenance time
            lastMaintenanceTime = currentTime
            
            print("\(Self.TAG): Rolling window maintenance completed: pending=\(currentPendingCount), daily=\(currentDailyCount)")
            
        } catch {
            print("\(Self.TAG): Error during rolling window maintenance: \(error)")
        }
    }
    
    /**
     * Arm today's remaining notifications
     * 
     * Ensures all notifications for today that haven't fired yet are armed
     */
    private func armTodaysRemainingNotifications() {
        do {
            print("\(Self.TAG): Arming today's remaining notifications")
            
            // Get today's date
            let today = Date()
            let todayDate = formatDate(today)
            
            // Get all notifications for today
            let todaysNotifications = getNotificationsForDate(todayDate)
            
            var armedCount = 0
            var skippedCount = 0
            
            for notification in todaysNotifications {
                // Check if notification is in the future
                let scheduledTime = Date(timeIntervalSince1970: Double(notification.scheduledTime) / 1000.0)
                if scheduledTime > Date() {
                    
                    // Check TTL before arming
                    if !ttlEnforcer.validateBeforeArming(notification) {
                        print("\(Self.TAG): Skipping today's notification due to TTL: \(notification.id)")
                        skippedCount += 1
                        continue
                    }
                    
                    // Arm the notification
                    let armed = armNotification(notification)
                    if armed {
                        armedCount += 1
                        currentPendingCount += 1
                    } else {
                        print("\(Self.TAG): Failed to arm today's notification: \(notification.id)")
                    }
                }
            }
            
            print("\(Self.TAG): Today's notifications: armed=\(armedCount), skipped=\(skippedCount)")
            
        } catch {
            print("\(Self.TAG): Error arming today's remaining notifications: \(error)")
        }
    }
    
    /**
     * Arm tomorrow's notifications if within capacity limits
     * 
     * Only arms tomorrow's notifications if we're within iOS capacity limits
     */
    private func armTomorrowsNotificationsIfWithinCapacity() {
        do {
            print("\(Self.TAG): Checking capacity for tomorrow's notifications")
            
            // Check if we're within capacity limits
            if !isWithinCapacityLimits() {
                print("\(Self.TAG): At capacity limit, skipping tomorrow's notifications")
                return
            }
            
            // Get tomorrow's date
            let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date()
            let tomorrowDate = formatDate(tomorrow)
            
            // Get all notifications for tomorrow
            let tomorrowsNotifications = getNotificationsForDate(tomorrowDate)
            
            var armedCount = 0
            var skippedCount = 0
            
            for notification in tomorrowsNotifications {
                // Check TTL before arming
                if !ttlEnforcer.validateBeforeArming(notification) {
                    print("\(Self.TAG): Skipping tomorrow's notification due to TTL: \(notification.id)")
                    skippedCount += 1
                    continue
                }
                
                // Arm the notification
                let armed = armNotification(notification)
                if armed {
                    armedCount += 1
                    currentPendingCount += 1
                    currentDailyCount += 1
                } else {
                    print("\(Self.TAG): Failed to arm tomorrow's notification: \(notification.id)")
                }
                
                // Check capacity after each arm
                if !isWithinCapacityLimits() {
                    print("\(Self.TAG): Reached capacity limit while arming tomorrow's notifications")
                    break
                }
            }
            
            print("\(Self.TAG): Tomorrow's notifications: armed=\(armedCount), skipped=\(skippedCount)")
            
        } catch {
            print("\(Self.TAG): Error arming tomorrow's notifications: \(error)")
        }
    }
    
    /**
     * Check if we're within iOS capacity limits
     * 
     * @return true if within limits
     */
    private func isWithinCapacityLimits() -> Bool {
        let withinPendingLimit = currentPendingCount < Self.IOS_MAX_PENDING_NOTIFICATIONS
        let withinDailyLimit = currentDailyCount < Self.IOS_MAX_DAILY_NOTIFICATIONS
        
        print("\(Self.TAG): Capacity check: pending=\(currentPendingCount)/\(Self.IOS_MAX_PENDING_NOTIFICATIONS), daily=\(currentDailyCount)/\(Self.IOS_MAX_DAILY_NOTIFICATIONS), within=\(withinPendingLimit && withinDailyLimit)")
        
        return withinPendingLimit && withinDailyLimit
    }
    
    /**
     * Update window state by counting current notifications
     */
    private func updateWindowState() {
        do {
            print("\(Self.TAG): Updating window state")
            
            // Count pending notifications
            currentPendingCount = countPendingNotifications()
            
            // Count today's notifications
            let today = Date()
            let todayDate = formatDate(today)
            currentDailyCount = countNotificationsForDate(todayDate)
            
            print("\(Self.TAG): Window state updated: pending=\(currentPendingCount), daily=\(currentDailyCount)")
            
        } catch {
            print("\(Self.TAG): Error updating window state: \(error)")
        }
    }
    
    // MARK: - Notification Management
    
    /**
     * Arm a notification using UNUserNotificationCenter
     * 
     * @param notification Notification to arm
     * @return true if successfully armed
     */
    private func armNotification(_ notification: NotificationContent) -> Bool {
        do {
            let content = UNMutableNotificationContent()
            content.title = notification.title ?? "Daily Notification"
            content.body = notification.body ?? "Your daily notification is ready"
            content.sound = UNNotificationSound.default
            
            // Create trigger for scheduled time
            let scheduledTime = Date(timeIntervalSince1970: Double(notification.scheduledTime) / 1000.0)
            let trigger = UNCalendarNotificationTrigger(dateMatching: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: scheduledTime), repeats: false)
            
            // Create request
            let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
            
            // Schedule notification
            UNUserNotificationCenter.current().add(request) { error in
                if let error = error {
                    print("\(Self.TAG): Failed to arm notification \(notification.id): \(error)")
                } else {
                    print("\(Self.TAG): Successfully armed notification: \(notification.id)")
                }
            }
            
            return true
            
        } catch {
            print("\(Self.TAG): Error arming notification \(notification.id): \(error)")
            return false
        }
    }
    
    // MARK: - Data Access
    
    /**
     * Fetch pending notification requests synchronously
     * 
     * @param timeoutSeconds Timeout in seconds
     * @return Array of pending notification requests
     */
    private func fetchPendingRequestsSync(timeoutSeconds: TimeInterval) -> [UNNotificationRequest] {
        let sem = DispatchSemaphore(value: 0)
        var result: [UNNotificationRequest] = []

        UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
            result = requests
            sem.signal()
        }

        _ = sem.wait(timeout: .now() + timeoutSeconds)
        return result
    }
    
    /**
     * Count pending notifications
     * 
     * @return Number of pending notifications
     */
    private func countPendingNotifications() -> Int {
        do {
            let requests = fetchPendingRequestsSync(timeoutSeconds: 2.0)
            return requests.count
        } catch {
            print("\(Self.TAG): Error counting pending notifications: \(error)")
            return 0
        }
    }
    
    /**
     * Count notifications for a specific date
     * 
     * @param date Date in YYYY-MM-DD format
     * @return Number of notifications for the date
     */
    private func countNotificationsForDate(_ date: String) -> Int {
        do {
            let requests = fetchPendingRequestsSync(timeoutSeconds: 2.0)

            var count = 0
            for req in requests {
                guard let trigger = req.trigger as? UNCalendarNotificationTrigger else { continue }
                guard let nextDate = trigger.nextTriggerDate() else { continue }
                if formatDate(nextDate) == date {
                    count += 1
                }
            }

            return count
        } catch {
            print("\(Self.TAG): Error counting notifications for date: \(date), error: \(error)")
            return 0
        }
    }
    
    /**
     * Get notifications for a specific date
     * 
     * @param date Date in YYYY-MM-DD format
     * @return List of notifications for the date
     */
    private func getNotificationsForDate(_ date: String) -> [NotificationContent] {
        do {
            let requests = fetchPendingRequestsSync(timeoutSeconds: 2.0)

            var results: [NotificationContent] = []
            for req in requests {
                guard let trigger = req.trigger as? UNCalendarNotificationTrigger else { continue }
                guard let nextDate = trigger.nextTriggerDate() else { continue }
                if formatDate(nextDate) != date { continue }

                // We cannot reconstruct full NotificationContent from UNNotificationRequest reliably,
                // so this returns minimal stubs primarily for internal rolling-window inspection.
                let id = req.identifier
                let scheduledMs = Int64(nextDate.timeIntervalSince1970 * 1000.0)

                let fetchedMs: Int64
                if let fetchedAt = req.content.userInfo["fetched_at"] as? Int64 {
                    fetchedMs = fetchedAt
                } else if let fetchedAt = req.content.userInfo["fetched_at"] as? Int {
                    fetchedMs = Int64(fetchedAt)
                } else {
                    fetchedMs = scheduledMs
                }

                let stub = NotificationContent(
                    id: id,
                    title: req.content.title,
                    body: req.content.body,
                    scheduledTime: scheduledMs,
                    fetchedAt: fetchedMs,
                    url: nil,
                    payload: nil,
                    etag: nil
                )
                results.append(stub)
            }

            return results
        } catch {
            print("\(Self.TAG): Error getting notifications for date: \(date), error: \(error)")
            return []
        }
    }
    
    /**
     * Format date as YYYY-MM-DD
     * 
     * @param date Date to format
     * @return Formatted date string
     */
    private func formatDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.string(from: date)
    }
    
    // MARK: - Public Methods
    
    /**
     * Get rolling window statistics
     * 
     * @return Statistics string
     */
    func getRollingWindowStats() -> String {
        do {
            return String(format: "Rolling window stats: pending=%d/%d, daily=%d/%d, platform=iOS", 
                         currentPendingCount, Self.IOS_MAX_PENDING_NOTIFICATIONS, 
                         currentDailyCount, Self.IOS_MAX_DAILY_NOTIFICATIONS)
            
        } catch {
            print("\(Self.TAG): Error getting rolling window stats: \(error)")
            return "Error retrieving rolling window statistics"
        }
    }
    
    /**
     * Force window maintenance (for testing or manual triggers)
     */
    func forceMaintenance() {
        print("\(Self.TAG): Forcing rolling window maintenance")
        lastMaintenanceTime = Date.distantPast // Reset maintenance time
        maintainRollingWindow()
    }
    
    /**
     * Check if window maintenance is needed
     * 
     * @return true if maintenance is needed
     */
    func isMaintenanceNeeded() -> Bool {
        let currentTime = Date()
        return currentTime.timeIntervalSince(lastMaintenanceTime) >= Self.WINDOW_MAINTENANCE_INTERVAL_SECONDS
    }
    
    /**
     * Get time until next maintenance
     * 
     * @return Seconds until next maintenance
     */
    func getTimeUntilNextMaintenance() -> TimeInterval {
        let currentTime = Date()
        let nextMaintenanceTime = lastMaintenanceTime.addingTimeInterval(Self.WINDOW_MAINTENANCE_INTERVAL_SECONDS)
        return max(0, nextMaintenanceTime.timeIntervalSince(currentTime))
    }
}
