//
//  DailyNotificationPlugin.swift
//  DailyNotificationPlugin
//
//  Created by Matthew Raymer on 2025-09-22
//  Copyright © 2025 TimeSafari. All rights reserved.
//

import Foundation
import Capacitor
import UserNotifications
import BackgroundTasks
import CoreData

/**
 * iOS implementation of Daily Notification Plugin
 * Implements BGTaskScheduler + UNUserNotificationCenter for dual scheduling
 * 
 * @author Matthew Raymer
 * @version 1.1.0
 * @created 2025-09-22 09:22:32 UTC
 */
@objc(DailyNotificationPlugin)
public class DailyNotificationPlugin: CAPPlugin {
    
    let notificationCenter = UNUserNotificationCenter.current()
    private let backgroundTaskScheduler = BGTaskScheduler.shared
    // Note: PersistenceController available for Phase 2+ CoreData integration if needed
    
    // Background task identifiers
    private let fetchTaskIdentifier = "com.timesafari.dailynotification.fetch"
    private let notifyTaskIdentifier = "com.timesafari.dailynotification.notify"
    
    // Phase 1: Storage and Scheduler components
    var storage: DailyNotificationStorage?
    var scheduler: DailyNotificationScheduler?
    
    // Phase 1: Reactivation manager for recovery
    var reactivationManager: DailyNotificationReactivationManager?
    
    // Phase 1: Concurrency actor for thread-safe state access
    @available(iOS 13.0, *)
    var stateActor: DailyNotificationStateActor?
    
    override public func load() {
        NSLog("DNP-DEBUG: DailyNotificationPlugin.load() called - Capacitor discovered the plugin!")
        super.load()
        setupBackgroundTasks()
        
        // Initialize Phase 1 components
        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let defaultPath = documentsPath.appendingPathComponent("daily_notifications.db").path
        let database = DailyNotificationDatabase(path: defaultPath)
        storage = DailyNotificationStorage(databasePath: database.getPath())
        scheduler = DailyNotificationScheduler()
        
        // Initialize reactivation manager for recovery
        reactivationManager = DailyNotificationReactivationManager(
            database: database,
            storage: storage!,
            scheduler: scheduler!
        )
        
        // Initialize state actor for thread-safe access
        if #available(iOS 13.0, *) {
            stateActor = DailyNotificationStateActor(
                database: database,
                storage: storage!
            )
        }
        
        // Perform recovery on app launch (async, non-blocking)
        reactivationManager?.performRecovery()
        
        // Register for notification delivery events (Notification Center pattern)
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleNotificationDelivery(_:)),
            name: NSNotification.Name("DailyNotificationDelivered"),
            object: nil
        )
        
        NSLog("DNP-DEBUG: DailyNotificationPlugin.load() completed - initialization done")
        print("DNP-PLUGIN: Daily Notification Plugin loaded on iOS")
    }
    
    // MARK: - Configuration Methods
    
    /**
     * Configure the plugin with database and storage options
     * 
     * Matches Android configure() functionality:
     * - dbPath: Custom database path (optional)
     * - storage: "shared" or "tiered" (default: "tiered")
     * - ttlSeconds: Time-to-live for cached content (optional)
     * - prefetchLeadMinutes: Minutes before notification to prefetch (optional)
     * - maxNotificationsPerDay: Maximum notifications per day (optional)
     * - retentionDays: Days to retain notification history (optional)
     * - activeDidIntegration: Phase 1 activeDid configuration (optional)
     * 
     * @param call Plugin call containing configuration parameters
     */
    @objc func configure(_ call: CAPPluginCall) {
        // Capacitor passes the options object directly as call data
        // Read parameters directly from call (matching Android implementation)
        print("DNP-PLUGIN: Configuring plugin with new options")
        
        do {
            // Get configuration options directly from call (matching Android)
            let dbPath = call.getString("dbPath")
            let storageMode = call.getString("storage") ?? "tiered"
            let ttlSeconds = call.getInt("ttlSeconds")
            let prefetchLeadMinutes = call.getInt("prefetchLeadMinutes")
            let maxNotificationsPerDay = call.getInt("maxNotificationsPerDay")
            let retentionDays = call.getInt("retentionDays")
            
            // Phase 1: Process activeDidIntegration configuration (deferred to Phase 3)
            if let activeDidConfig = call.getObject("activeDidIntegration") {
                print("DNP-PLUGIN: activeDidIntegration config received (Phase 3 feature)")
                // TODO: Implement activeDidIntegration configuration in Phase 3
            }
            
            // Update storage mode
            let useSharedStorage = storageMode == "shared"
            
            // Set database path
            let finalDbPath: String
            if let dbPath = dbPath, !dbPath.isEmpty {
                finalDbPath = dbPath
                print("DNP-PLUGIN: Database path set to: \(finalDbPath)")
            } else {
                // Use default database path
                let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
                finalDbPath = documentsPath.appendingPathComponent("daily_notifications.db").path
                print("DNP-PLUGIN: Using default database path: \(finalDbPath)")
            }
            
            // Reinitialize storage with new database path if needed
            if let currentStorage = storage {
                // Check if path changed
                if currentStorage.getDatabasePath() != finalDbPath {
                    storage = DailyNotificationStorage(databasePath: finalDbPath)
                    print("DNP-PLUGIN: Storage reinitialized with new database path")
                }
            } else {
                storage = DailyNotificationStorage(databasePath: finalDbPath)
            }
            
            // Store configuration in storage
            storeConfiguration(
                ttlSeconds: ttlSeconds,
                prefetchLeadMinutes: prefetchLeadMinutes,
                maxNotificationsPerDay: maxNotificationsPerDay,
                retentionDays: retentionDays,
                storageMode: storageMode,
                dbPath: finalDbPath
            )
            
            print("DNP-PLUGIN: Plugin configuration completed successfully")
            call.resolve()
            
        } catch {
            print("DNP-PLUGIN: Error configuring plugin: \(error)")
            call.reject("Configuration failed: \(error.localizedDescription)")
        }
    }
    
    /**
     * Configure native fetcher with API credentials (cross-platform)
     * 
     * Matches Android configureNativeFetcher() functionality:
     * - Stores configuration in database for persistence
     * - Supports both jwtToken and jwtSecret for backward compatibility
     * - Note: iOS native fetcher interface not yet implemented, but configuration is stored
     * 
     * @param call Plugin call containing configuration parameters
     */
    @objc func configureNativeFetcher(_ call: CAPPluginCall) {
        guard let options = call.options else {
            call.reject("Options are required")
            return
        }
        
        guard let apiBaseUrl = options["apiBaseUrl"] as? String else {
            call.reject("apiBaseUrl is required")
            return
        }
        
        guard let activeDid = options["activeDid"] as? String else {
            call.reject("activeDid is required")
            return
        }
        
        // Support both jwtToken and jwtSecret for backward compatibility
        let jwtToken = (options["jwtToken"] as? String) ?? (options["jwtSecret"] as? String)
        guard let jwtToken = jwtToken else {
            call.reject("jwtToken or jwtSecret is required")
            return
        }
        
        print("DNP-PLUGIN: Configuring native fetcher: apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...")
        
        // Store configuration in database for persistence across app restarts
        // Note: iOS native fetcher interface not yet implemented, but we store config for future use
        let configId = "native_fetcher_config"
        let configValue: [String: Any] = [
            "apiBaseUrl": apiBaseUrl,
            "activeDid": activeDid,
            "jwtToken": jwtToken
        ]
        
        // Convert to JSON string for storage
        guard let jsonData = try? JSONSerialization.data(withJSONObject: configValue, options: []),
              let jsonString = String(data: jsonData, encoding: .utf8) else {
            call.reject("Failed to serialize configuration")
            return
        }
        
        // Store configuration in UserDefaults for now
        // This matches Android's approach of storing in database, but uses UserDefaults for simplicity
        // Can be enhanced later to use CoreData when native fetcher interface is implemented
        let configKey = "native_fetcher_config"
        UserDefaults.standard.set(jsonString, forKey: configKey)
        print("DNP-PLUGIN: Native fetcher configuration stored successfully")
        call.resolve()
    }
    
    /**
     * Store configuration values
     * 
     * @param ttlSeconds TTL in seconds
     * @param prefetchLeadMinutes Prefetch lead time in minutes
     * @param maxNotificationsPerDay Maximum notifications per day
     * @param retentionDays Retention period in days
     * @param storageMode Storage mode ("shared" or "tiered")
     * @param dbPath Database path
     */
    private func storeConfiguration(
        ttlSeconds: Int?,
        prefetchLeadMinutes: Int?,
        maxNotificationsPerDay: Int?,
        retentionDays: Int?,
        storageMode: String,
        dbPath: String
    ) {
        var config: [String: Any] = [
            "storageMode": storageMode,
            "dbPath": dbPath
        ]
        
        if let ttlSeconds = ttlSeconds {
            config["ttlSeconds"] = ttlSeconds
        }
        
        if let prefetchLeadMinutes = prefetchLeadMinutes {
            config["prefetchLeadMinutes"] = prefetchLeadMinutes
        }
        
        if let maxNotificationsPerDay = maxNotificationsPerDay {
            config["maxNotificationsPerDay"] = maxNotificationsPerDay
        }
        
        if let retentionDays = retentionDays {
            config["retentionDays"] = retentionDays
        }
        
        storage?.saveSettings(config)
        print("DNP-PLUGIN: Configuration stored successfully")
    }
    
    // MARK: - Dual Scheduling Methods
    
    @objc func scheduleContentFetch(_ call: CAPPluginCall) {
        guard let config = call.getObject("config") else {
            call.reject("Content fetch config required")
            return
        }
        
        print("DNP-PLUGIN: Scheduling content fetch")
        
        do {
            try scheduleBackgroundFetch(config: config)
            call.resolve()
        } catch {
            print("DNP-PLUGIN: Failed to schedule content fetch: \(error)")
            call.reject("Content fetch scheduling failed: \(error.localizedDescription)")
        }
    }
    
    @objc func scheduleUserNotification(_ call: CAPPluginCall) {
        guard let config = call.getObject("config") else {
            call.reject("User notification config required")
            return
        }
        
        print("DNP-PLUGIN: Scheduling user notification")
        
        do {
            try scheduleUserNotification(config: config)
            call.resolve()
        } catch {
            print("DNP-PLUGIN: Failed to schedule user notification: \(error)")
            call.reject("User notification scheduling failed: \(error.localizedDescription)")
        }
    }
    
    @objc func scheduleDualNotification(_ call: CAPPluginCall) {
        guard let config = call.getObject("config"),
              let contentFetchConfig = config["contentFetch"] as? [String: Any],
              let userNotificationConfig = config["userNotification"] as? [String: Any] else {
            call.reject("Dual notification config required")
            return
        }
        
        print("DNP-PLUGIN: Scheduling dual notification")
        
        do {
            try scheduleBackgroundFetch(config: contentFetchConfig)
            try scheduleUserNotification(config: userNotificationConfig)
            call.resolve()
        } catch {
            print("DNP-PLUGIN: Failed to schedule dual notification: \(error)")
            call.reject("Dual notification scheduling failed: \(error.localizedDescription)")
        }
    }
    
    @objc func getDualScheduleStatus(_ call: CAPPluginCall) {
        Task {
            do {
                let status = try await getHealthStatus()
                DispatchQueue.main.async {
                    call.resolve(status)
                }
            } catch {
                DispatchQueue.main.async {
                    print("DNP-PLUGIN: Failed to get dual schedule status: \(error)")
                    call.reject("Status retrieval failed: \(error.localizedDescription)")
                }
            }
        }
    }
    
    /**
     * Get health status for dual scheduling system
     * 
     * @return Health status dictionary
     */
    private func getHealthStatus() async throws -> [String: Any] {
        guard let scheduler = scheduler else {
            throw NSError(domain: "DailyNotificationPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Scheduler not initialized"])
        }
        
        let pendingCount = await scheduler.getPendingNotificationCount()
        let isEnabled = await scheduler.checkPermissionStatus() == .authorized
        
        // Get last notification via state actor
        var lastNotification: NotificationContent?
        if #available(iOS 13.0, *) {
            if let stateActor = await self.stateActor {
                lastNotification = await stateActor.getLastNotification()
            } else {
                lastNotification = self.storage?.getLastNotification()
            }
        } else {
            lastNotification = self.storage?.getLastNotification()
        }
        
        return [
            "contentFetch": [
                "isEnabled": true,
                "isScheduled": pendingCount > 0,
                "lastFetchTime": lastNotification?.fetchedAt ?? 0,
                "nextFetchTime": 0,
                "pendingFetches": pendingCount
            ],
            "userNotification": [
                "isEnabled": isEnabled,
                "isScheduled": pendingCount > 0,
                "lastNotificationTime": lastNotification?.scheduledTime ?? 0,
                "nextNotificationTime": 0,
                "pendingNotifications": pendingCount
            ],
            "relationship": [
                "isLinked": true,
                "contentAvailable": lastNotification != nil,
                "lastLinkTime": lastNotification?.fetchedAt ?? 0
            ],
            "overall": [
                "isActive": isEnabled && pendingCount > 0,
                "lastActivity": lastNotification?.scheduledTime ?? 0,
                "errorCount": 0,
                "successRate": 1.0
            ]
        ]
    }
    
    // MARK: - Private Implementation Methods
    
    private func setupBackgroundTasks() {
        // Register background fetch task
        backgroundTaskScheduler.register(forTaskWithIdentifier: fetchTaskIdentifier, using: nil) { task in
            self.handleBackgroundFetch(task: task as! BGAppRefreshTask)
        }
        
        // Register background processing task
        backgroundTaskScheduler.register(forTaskWithIdentifier: notifyTaskIdentifier, using: nil) { task in
            self.handleBackgroundNotify(task: task as! BGProcessingTask)
        }
        
        // Phase 1: Check for missed BGTask on app launch
        checkForMissedBGTask()
    }
    
    /**
     * Handle background fetch task
     * 
     * Phase 1: Dummy fetcher - returns static content
     * Phase 3: Will be replaced with JWT-signed fetcher
     * 
     * Enhanced with:
     * - Recovery logic (verify scheduled notifications)
     * - Next task scheduling
     * - Graceful expiration handling
     * 
     * @param task BGAppRefreshTask
     */
    private func handleBackgroundFetch(task: BGAppRefreshTask) {
        print("DNP-FETCH: Background fetch task started")
        
        // Enhanced expiration handler with graceful cleanup
        var taskCompleted = false
        task.expirationHandler = {
            guard !taskCompleted else { return }
            print("DNP-FETCH: Background fetch task expired - performing graceful cleanup")
            
            // Cancel any ongoing operations
            // Note: In production, you might want to cancel URLSession tasks here
            
            task.setTaskCompleted(success: false)
            taskCompleted = true
        }
        
        // Phase 1: Dummy content fetch (no network)
        // TODO: Phase 3 - Replace with JWT-signed fetcher
        let dummyContent = NotificationContent(
            id: "dummy_\(Date().timeIntervalSince1970)",
            title: "Daily Update",
            body: "Your daily notification is ready",
            scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000), // 5 min from now
            fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
            url: nil,
            payload: nil,
            etag: nil
        )
        
        // Save content to storage via state actor (thread-safe)
        Task {
            do {
                if #available(iOS 13.0, *) {
                    if let stateActor = await self.stateActor {
                        await stateActor.saveNotificationContent(dummyContent)
                        
                        // Mark successful run
                        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
                        await stateActor.saveLastSuccessfulRun(timestamp: currentTime)
                    } else {
                        // Fallback to direct storage access
                        self.storage?.saveNotificationContent(dummyContent)
                        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
                        self.storage?.saveLastSuccessfulRun(timestamp: currentTime)
                    }
                } else {
                    // Fallback for iOS < 13
                    self.storage?.saveNotificationContent(dummyContent)
                    let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
                    self.storage?.saveLastSuccessfulRun(timestamp: currentTime)
                }
                
                // Phase 3.3: Recovery logic - verify scheduled notifications
                // Check if notifications are still scheduled after fetch
                if let reactivationManager = self.reactivationManager {
                    // Perform lightweight verification (non-blocking)
                    Task {
                        do {
                            let verificationResult = try await reactivationManager.verifyFutureNotifications()
                            if verificationResult.notificationsMissing > 0 {
                                print("DNP-FETCH: Recovery - found \(verificationResult.notificationsMissing) missing notifications, will reschedule on next app launch")
                                // Note: Full recovery happens on app launch, not in background task
                            }
                        } catch {
                            // Non-fatal: Log but don't fail task
                            print("DNP-FETCH: Recovery verification failed (non-fatal): \(error.localizedDescription)")
                        }
                    }
                }
                
                // Phase 3.3: Schedule next background task
                // Calculate next fetch time based on notification schedule
                if let nextScheduledTime = self.getNextScheduledNotificationTime() {
                    self.scheduleBackgroundFetch(scheduledTime: nextScheduledTime)
                    print("DNP-FETCH: Next background fetch scheduled")
                } else {
                    print("DNP-FETCH: No future notifications found, skipping next task schedule")
                }
                
                guard !taskCompleted else { return }
                task.setTaskCompleted(success: true)
                taskCompleted = true
                print("DNP-FETCH: Background fetch task completed successfully")
                
            } catch {
                print("DNP-FETCH: Background fetch task failed: \(error.localizedDescription)")
                guard !taskCompleted else { return }
                task.setTaskCompleted(success: false)
                taskCompleted = true
            }
        }
    }
    
    /**
     * Handle background notification task
     * 
     * Enhanced with:
     * - Recovery logic (verify scheduled notifications)
     * - Next task scheduling
     * - Graceful expiration handling
     * 
     * @param task BGProcessingTask
     */
    private func handleBackgroundNotify(task: BGProcessingTask) {
        print("DNP-NOTIFY: Background notify task started")
        
        // Enhanced expiration handler with graceful cleanup
        var taskCompleted = false
        task.expirationHandler = {
            guard !taskCompleted else { return }
            print("DNP-NOTIFY: Background notify task expired - performing graceful cleanup")
            task.setTaskCompleted(success: false)
            taskCompleted = true
        }
        
        Task {
            do {
                // Phase 3.3: Recovery logic - verify scheduled notifications
                // Check if notifications are still scheduled
                if let reactivationManager = self.reactivationManager {
                    // Perform lightweight verification (non-blocking)
                    let verificationResult = try await reactivationManager.verifyFutureNotifications()
                    if verificationResult.notificationsMissing > 0 {
                        print("DNP-NOTIFY: Recovery - found \(verificationResult.notificationsMissing) missing notifications, will reschedule on next app launch")
                        // Note: Full recovery happens on app launch, not in background task
                    }
                }
                
                // Phase 1: Not used for single daily schedule
                // This will be used in Phase 2+ for rolling window maintenance
                // For now, just verify state
                
                // Phase 3.3: Schedule next background task if needed
                // For notify task, schedule next occurrence if applicable
                if let nextScheduledTime = self.getNextScheduledNotificationTime() {
                    // Calculate next notify task time (if applicable)
                    // Note: Notify tasks are typically scheduled less frequently than fetch tasks
                    print("DNP-NOTIFY: Next notification scheduled at \(nextScheduledTime)")
                }
                
                guard !taskCompleted else { return }
                task.setTaskCompleted(success: true)
                taskCompleted = true
                print("DNP-NOTIFY: Background notify task completed successfully")
                
            } catch {
                print("DNP-NOTIFY: Background notify task failed: \(error.localizedDescription)")
                guard !taskCompleted else { return }
                task.setTaskCompleted(success: false)
                taskCompleted = true
            }
        }
    }
    
    /**
     * Check for missed BGTask and reschedule if needed
     * 
     * Phase 1: BGTask Miss Detection
     * - Checks if BGTask was scheduled but not run within 15 min window
     * - Reschedules if missed
     */
    private func checkForMissedBGTask() {
        Task {
            var earliestBeginTimestamp: Int64?
            var lastSuccessfulRun: Int64?
            
            // Get BGTask tracking info via state actor (thread-safe)
            if #available(iOS 13.0, *) {
                if let stateActor = await self.stateActor {
                    earliestBeginTimestamp = await stateActor.getBGTaskEarliestBegin()
                    lastSuccessfulRun = await stateActor.getLastSuccessfulRun()
                } else {
                    // Fallback to direct storage access
                    earliestBeginTimestamp = self.storage?.getBGTaskEarliestBegin()
                    lastSuccessfulRun = self.storage?.getLastSuccessfulRun()
                }
            } else {
                // Fallback for iOS < 13
                earliestBeginTimestamp = self.storage?.getBGTaskEarliestBegin()
                lastSuccessfulRun = self.storage?.getLastSuccessfulRun()
            }
            
            guard let earliestBeginTime = earliestBeginTimestamp else {
                // No BGTask scheduled
                return
            }
            
            let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
            let missWindow = Int64(15 * 60 * 1000) // 15 minutes in milliseconds
            
            // Check if task was missed (current time > earliestBeginTime + 15 min)
            if currentTime > earliestBeginTime + missWindow {
                // Check if there was a successful run
                if let lastRun = lastSuccessfulRun {
                    // If last successful run was after earliestBeginTime, task was not missed
                    if lastRun >= earliestBeginTime {
                        print("DNP-FETCH: BGTask completed successfully, no reschedule needed")
                        return
                    }
                }
                
                // Task was missed - reschedule
                print("DNP-FETCH: BGTask missed window; rescheduling")
                
                // Reschedule for 1 minute from now
                let rescheduleTime = currentTime + (1 * 60 * 1000) // 1 minute from now
                let rescheduleDate = Date(timeIntervalSince1970: Double(rescheduleTime) / 1000.0)
                
                let request = BGAppRefreshTaskRequest(identifier: fetchTaskIdentifier)
                request.earliestBeginDate = rescheduleDate
                
                do {
                    try backgroundTaskScheduler.submit(request)
                    
                    // Save rescheduled time via state actor (thread-safe)
                    if #available(iOS 13.0, *) {
                        if let stateActor = await self.stateActor {
                            await stateActor.saveBGTaskEarliestBegin(timestamp: rescheduleTime)
                        } else {
                            // Fallback to direct storage access
                            self.storage?.saveBGTaskEarliestBegin(timestamp: rescheduleTime)
                        }
                    } else {
                        // Fallback for iOS < 13
                        self.storage?.saveBGTaskEarliestBegin(timestamp: rescheduleTime)
                    }
                    
                    print("DNP-FETCH: BGTask rescheduled for \(rescheduleDate)")
                } catch {
                    print("DNP-FETCH: Failed to reschedule BGTask: \(error)")
                }
            }
        }
    }
    
    private func scheduleBackgroundFetch(config: [String: Any]) throws {
        let request = BGAppRefreshTaskRequest(identifier: fetchTaskIdentifier)
        
        // Calculate next run time (simplified - would use proper cron parsing in production)
        let nextRunTime = calculateNextRunTime(from: config["schedule"] as? String ?? "0 9 * * *")
        request.earliestBeginDate = Date(timeIntervalSinceNow: nextRunTime)
        
        try backgroundTaskScheduler.submit(request)
        print("DNP-FETCH-SCHEDULE: Background fetch scheduled for \(request.earliestBeginDate!)")
    }
    
    private func scheduleUserNotification(config: [String: Any]) throws {
        let content = UNMutableNotificationContent()
        content.title = config["title"] as? String ?? "Daily Notification"
        content.body = config["body"] as? String ?? "Your daily update is ready"
        content.sound = (config["sound"] as? Bool ?? true) ? .default : nil
        
        // Create trigger (simplified - would use proper cron parsing in production)
        let nextRunTime = calculateNextRunTime(from: config["schedule"] as? String ?? "0 9 * * *")
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: nextRunTime, repeats: false)
        
        let request = UNNotificationRequest(
            identifier: "daily-notification-\(Date().timeIntervalSince1970)",
            content: content,
            trigger: trigger
        )
        
        notificationCenter.add(request) { error in
            if let error = error {
                print("DNP-NOTIFY-SCHEDULE: Failed to schedule notification: \(error)")
            } else {
                print("DNP-NOTIFY-SCHEDULE: Notification scheduled successfully")
            }
        }
    }
    
    private func calculateNextRunTime(from schedule: String) -> TimeInterval {
        // Simplified implementation - would use proper cron parsing in production
        // For now, return next day at 9 AM
        return 86400 // 24 hours
    }

    // MARK: - Static Daily Reminder Methods

    @objc func scheduleDailyReminder(_ call: CAPPluginCall) {
        guard let id = call.getString("id"),
              let title = call.getString("title"),
              let body = call.getString("body"),
              let time = call.getString("time") else {
            call.reject("Missing required parameters: id, title, body, time")
            return
        }

        let sound = call.getBool("sound", true)
        let vibration = call.getBool("vibration", true)
        let priority = call.getString("priority", "normal")
        let repeatDaily = call.getBool("repeatDaily", true)
        let timezone = call.getString("timezone")

        print("DNP-REMINDER: Scheduling daily reminder: \(id)")

        // Parse time (HH:mm format)
        let timeComponents = time.components(separatedBy: ":")
        guard timeComponents.count == 2,
              let hour = Int(timeComponents[0]),
              let minute = Int(timeComponents[1]),
              hour >= 0 && hour <= 23,
              minute >= 0 && minute <= 59 else {
            call.reject("Invalid time format. Use HH:mm (e.g., 09:00)")
            return
        }

        // Create notification content
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = sound ? .default : nil
        content.categoryIdentifier = "DAILY_REMINDER"

        // Set priority
        if #available(iOS 15.0, *) {
            switch priority {
            case "high":
                content.interruptionLevel = .critical
            case "low":
                content.interruptionLevel = .passive
            default:
                content.interruptionLevel = .active
            }
        }

        // Create date components for daily trigger
        var dateComponents = DateComponents()
        dateComponents.hour = hour
        dateComponents.minute = minute

        let trigger = UNCalendarNotificationTrigger(
            dateMatching: dateComponents,
            repeats: repeatDaily
        )

        let request = UNNotificationRequest(
            identifier: "reminder_\(id)",
            content: content,
            trigger: trigger
        )

        // Store reminder in UserDefaults
        storeReminderInUserDefaults(
            id: id,
            title: title,
            body: body,
            time: time,
            sound: sound,
            vibration: vibration,
            priority: priority,
            repeatDaily: repeatDaily,
            timezone: timezone
        )

        // Schedule the notification
        notificationCenter.add(request) { error in
            DispatchQueue.main.async {
                if let error = error {
                    print("DNP-REMINDER: Failed to schedule reminder: \(error)")
                    call.reject("Failed to schedule daily reminder: \(error.localizedDescription)")
                } else {
                    print("DNP-REMINDER: Daily reminder scheduled successfully: \(id)")
                    call.resolve()
                }
            }
        }
    }

    @objc func cancelDailyReminder(_ call: CAPPluginCall) {
        guard let reminderId = call.getString("reminderId") else {
            call.reject("Missing reminderId parameter")
            return
        }

        print("DNP-REMINDER: Cancelling daily reminder: \(reminderId)")

        // Cancel the notification
        notificationCenter.removePendingNotificationRequests(withIdentifiers: ["reminder_\(reminderId)"])

        // Remove from UserDefaults
        removeReminderFromUserDefaults(id: reminderId)

        call.resolve()
    }

    @objc func getScheduledReminders(_ call: CAPPluginCall) {
        print("DNP-REMINDER: Getting scheduled reminders")

        // Get pending notifications
        notificationCenter.getPendingNotificationRequests { requests in
            let reminderRequests = requests.filter { $0.identifier.hasPrefix("reminder_") }

            // Get stored reminder data from UserDefaults
            let reminders = self.getRemindersFromUserDefaults()

            var result: [[String: Any]] = []
            for reminder in reminders {
                let isScheduled = reminderRequests.contains { $0.identifier == "reminder_\(reminder["id"] as! String)" }
                var reminderInfo = reminder
                reminderInfo["isScheduled"] = isScheduled
                result.append(reminderInfo)
            }

            DispatchQueue.main.async {
                call.resolve(["reminders": result])
            }
        }
    }

    @objc func updateDailyReminder(_ call: CAPPluginCall) {
        guard let reminderId = call.getString("reminderId") else {
            call.reject("Missing reminderId parameter")
            return
        }

        print("DNP-REMINDER: Updating daily reminder: \(reminderId)")

        // Cancel existing reminder
        notificationCenter.removePendingNotificationRequests(withIdentifiers: ["reminder_\(reminderId)"])

        // Update in UserDefaults
        let title = call.getString("title")
        let body = call.getString("body")
        let time = call.getString("time")
        let sound = call.getBool("sound")
        let vibration = call.getBool("vibration")
        let priority = call.getString("priority")
        let repeatDaily = call.getBool("repeatDaily")
        let timezone = call.getString("timezone")

        updateReminderInUserDefaults(
            id: reminderId,
            title: title,
            body: body,
            time: time,
            sound: sound,
            vibration: vibration,
            priority: priority,
            repeatDaily: repeatDaily,
            timezone: timezone
        )

        // Reschedule with new settings if all required fields are provided
        if let title = title, let body = body, let time = time {
            // Parse time
            let timeComponents = time.components(separatedBy: ":")
            guard timeComponents.count == 2,
                  let hour = Int(timeComponents[0]),
                  let minute = Int(timeComponents[1]) else {
                call.reject("Invalid time format")
                return
            }

            // Create new notification content
            let content = UNMutableNotificationContent()
            content.title = title
            content.body = body
            content.sound = (sound ?? true) ? .default : nil
            content.categoryIdentifier = "DAILY_REMINDER"

            // Set priority
            let finalPriority = priority ?? "normal"
            if #available(iOS 15.0, *) {
                switch finalPriority {
                case "high":
                    content.interruptionLevel = .critical
                case "low":
                    content.interruptionLevel = .passive
                default:
                    content.interruptionLevel = .active
                }
            }

            // Create date components for daily trigger
            var dateComponents = DateComponents()
            dateComponents.hour = hour
            dateComponents.minute = minute

            let trigger = UNCalendarNotificationTrigger(
                dateMatching: dateComponents,
                repeats: repeatDaily ?? true
            )

            let request = UNNotificationRequest(
                identifier: "reminder_\(reminderId)",
                content: content,
                trigger: trigger
            )

            // Schedule the updated notification
            notificationCenter.add(request) { error in
                DispatchQueue.main.async {
                    if let error = error {
                        call.reject("Failed to reschedule updated reminder: \(error.localizedDescription)")
                    } else {
                        call.resolve()
                    }
                }
            }
        } else {
            call.resolve()
        }
    }

    // MARK: - Helper Methods for Reminder Storage

    private func storeReminderInUserDefaults(
        id: String,
        title: String,
        body: String,
        time: String,
        sound: Bool,
        vibration: Bool,
        priority: String,
        repeatDaily: Bool,
        timezone: String?
    ) {
        let reminderData: [String: Any] = [
            "id": id,
            "title": title,
            "body": body,
            "time": time,
            "sound": sound,
            "vibration": vibration,
            "priority": priority,
            "repeatDaily": repeatDaily,
            "timezone": timezone ?? "",
            "createdAt": Date().timeIntervalSince1970,
            "lastTriggered": 0
        ]

        var reminders = UserDefaults.standard.array(forKey: "daily_reminders") as? [[String: Any]] ?? []
        reminders.append(reminderData)
        UserDefaults.standard.set(reminders, forKey: "daily_reminders")

        print("DNP-REMINDER: Reminder stored: \(id)")
    }

    private func removeReminderFromUserDefaults(id: String) {
        var reminders = UserDefaults.standard.array(forKey: "daily_reminders") as? [[String: Any]] ?? []
        reminders.removeAll { ($0["id"] as? String) == id }
        UserDefaults.standard.set(reminders, forKey: "daily_reminders")

        print("DNP-REMINDER: Reminder removed: \(id)")
    }

    private func getRemindersFromUserDefaults() -> [[String: Any]] {
        return UserDefaults.standard.array(forKey: "daily_reminders") as? [[String: Any]] ?? []
    }

    private func updateReminderInUserDefaults(
        id: String,
        title: String?,
        body: String?,
        time: String?,
        sound: Bool?,
        vibration: Bool?,
        priority: String?,
        repeatDaily: Bool?,
        timezone: String?
    ) {
        var reminders = UserDefaults.standard.array(forKey: "daily_reminders") as? [[String: Any]] ?? []
        
        for i in 0..<reminders.count {
            if reminders[i]["id"] as? String == id {
                if let title = title { reminders[i]["title"] = title }
                if let body = body { reminders[i]["body"] = body }
                if let time = time { reminders[i]["time"] = time }
                if let sound = sound { reminders[i]["sound"] = sound }
                if let vibration = vibration { reminders[i]["vibration"] = vibration }
                if let priority = priority { reminders[i]["priority"] = priority }
                if let repeatDaily = repeatDaily { reminders[i]["repeatDaily"] = repeatDaily }
                if let timezone = timezone { reminders[i]["timezone"] = timezone }
                break
            }
        }
        
        UserDefaults.standard.set(reminders, forKey: "daily_reminders")
        print("DNP-REMINDER: Reminder updated: \(id)")
    }
    
    // MARK: - Phase 1: Core Notification Methods
    
    /**
     * Schedule a daily notification with the specified options
     * 
     * Phase 1: Single daily schedule (one prefetch 5 min before + one notification)
     * 
     * @param call Plugin call containing notification parameters
     */
    @objc func scheduleDailyNotification(_ call: CAPPluginCall) {
        guard let scheduler = scheduler else {
            let error = DailyNotificationErrorCodes.createErrorResponse(
                code: DailyNotificationErrorCodes.PLUGIN_NOT_INITIALIZED,
                message: "Plugin not initialized"
            )
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        // Capacitor passes the options object directly as call data
        // Read parameters directly from call (matching Android implementation)
        guard let time = call.getString("time"), !time.isEmpty else {
            let error = DailyNotificationErrorCodes.missingParameter("time")
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        // Parse time (HH:mm format)
        let timeComponents = time.components(separatedBy: ":")
        guard timeComponents.count == 2,
              let hour = Int(timeComponents[0]),
              let minute = Int(timeComponents[1]),
              hour >= 0 && hour <= 23,
              minute >= 0 && minute <= 59 else {
            let error = DailyNotificationErrorCodes.invalidTimeFormat()
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        // Extract other parameters (with defaults matching Android)
        let title = call.getString("title") ?? "Daily Update"
        let body = call.getString("body") ?? "Your daily notification is ready"
        let sound = call.getBool("sound", true)
        let url = call.getString("url")
        let priority = call.getString("priority") ?? "default"
        
        // Calculate scheduled time (next occurrence at specified hour:minute)
        let scheduledTime = calculateNextScheduledTime(hour: hour, minute: minute)
        let fetchedAt = Int64(Date().timeIntervalSince1970 * 1000) // Current time in milliseconds
        
        // Create notification content
        let content = NotificationContent(
            id: "daily_\(Date().timeIntervalSince1970)",
            title: title,
            body: body,
            scheduledTime: scheduledTime,
            fetchedAt: fetchedAt,
            url: url,
            payload: nil,
            etag: nil
        )
        
        // Store notification content via state actor (thread-safe)
        Task {
            // Reset: Cancel all existing notifications and clear rollover state
            // This ensures clicking "Test Notification" starts fresh
            // Cancel all pending notifications (including rollovers)
            await scheduler.cancelAllNotifications()
            NSLog("DNP-PLUGIN: Cleared all pending notifications for fresh schedule")
            print("DNP-PLUGIN: Cleared all pending notifications for fresh schedule")
            
            // Clear all stored notification content
            if let storage = self.storage {
                storage.clearAllNotifications()
                NSLog("DNP-PLUGIN: Cleared all stored notification content")
                print("DNP-PLUGIN: Cleared all stored notification content")
            }
            
            // Clear rollover state from UserDefaults
            // Clear global rollover time
            if let storage = self.storage {
                storage.saveLastRolloverTime(0)
            }
            
            // Clear per-notification rollover times
            // We need to clear all rollover_* keys from UserDefaults
            let userDefaults = UserDefaults.standard
            let allKeys = userDefaults.dictionaryRepresentation().keys
            for key in allKeys {
                if key.hasPrefix("rollover_") {
                    userDefaults.removeObject(forKey: key)
                }
            }
            userDefaults.synchronize()
            NSLog("DNP-PLUGIN: Cleared all rollover state")
            print("DNP-PLUGIN: Cleared all rollover state")
            if #available(iOS 13.0, *) {
                if let stateActor = await self.stateActor {
                    await stateActor.saveNotificationContent(content)
                } else {
                    // Fallback to direct storage access
                    self.storage?.saveNotificationContent(content)
                }
            } else {
                // Fallback for iOS < 13
                self.storage?.saveNotificationContent(content)
            }
            
            // Schedule notification
            let scheduled = await scheduler.scheduleNotification(content)
            
            if scheduled {
                // Schedule background fetch 5 minutes before notification time
                self.scheduleBackgroundFetch(scheduledTime: scheduledTime)
                
                DispatchQueue.main.async {
                    print("DNP-PLUGIN: Daily notification scheduled successfully")
                    call.resolve()
                }
            } else {
                DispatchQueue.main.async {
                    let error = DailyNotificationErrorCodes.createErrorResponse(
                        code: DailyNotificationErrorCodes.SCHEDULING_FAILED,
                        message: "Failed to schedule notification"
                    )
                    let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
                }
            }
        }
    }
    
    /**
     * Get the last notification that was delivered
     * 
     * @param call Plugin call
     */
    @objc func getLastNotification(_ call: CAPPluginCall) {
        Task {
            var lastNotification: NotificationContent?
            
            if #available(iOS 13.0, *) {
                if let stateActor = await self.stateActor {
                    lastNotification = await stateActor.getLastNotification()
                } else {
                    // Fallback to direct storage access
                    lastNotification = self.storage?.getLastNotification()
                }
            } else {
                // Fallback for iOS < 13
                lastNotification = self.storage?.getLastNotification()
            }
            
            DispatchQueue.main.async {
                if let notification = lastNotification {
                    let result: [String: Any] = [
                        "id": notification.id,
                        "title": notification.title ?? "",
                        "body": notification.body ?? "",
                        "timestamp": notification.scheduledTime,
                        "url": notification.url ?? ""
                    ]
                    call.resolve(result)
                } else {
                    call.resolve([:])
                }
            }
        }
    }
    
    /**
     * Cancel all scheduled notifications
     * 
     * @param call Plugin call
     */
    @objc func cancelAllNotifications(_ call: CAPPluginCall) {
        guard let scheduler = scheduler else {
            let error = DailyNotificationErrorCodes.createErrorResponse(
                code: DailyNotificationErrorCodes.PLUGIN_NOT_INITIALIZED,
                message: "Plugin not initialized"
            )
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        Task {
            await scheduler.cancelAllNotifications()
            
            // Clear notifications via state actor (thread-safe)
            if #available(iOS 13.0, *) {
                if let stateActor = await self.stateActor {
                    await stateActor.clearAllNotifications()
                } else {
                    // Fallback to direct storage access
                    self.storage?.clearAllNotifications()
                }
            } else {
                // Fallback for iOS < 13
                self.storage?.clearAllNotifications()
            }
            
            DispatchQueue.main.async {
                print("DNP-PLUGIN: All notifications cancelled successfully")
                call.resolve()
            }
        }
    }
    
    /**
     * Get the current status of notifications
     * 
     * @param call Plugin call
     */
    @objc func getNotificationStatus(_ call: CAPPluginCall) {
        guard let scheduler = scheduler else {
            let error = DailyNotificationErrorCodes.createErrorResponse(
                code: DailyNotificationErrorCodes.PLUGIN_NOT_INITIALIZED,
                message: "Plugin not initialized"
            )
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        Task {
            let isEnabled = await scheduler.checkPermissionStatus() == .authorized
            let pendingCount = await scheduler.getPendingNotificationCount()
            
            // Get last notification via state actor (thread-safe)
            var lastNotification: NotificationContent?
            var settings: [String: Any] = [:]
            
            if #available(iOS 13.0, *) {
                if let stateActor = await self.stateActor {
                    lastNotification = await stateActor.getLastNotification()
                    settings = await stateActor.getSettings()
                } else {
                    // Fallback to direct storage access
                    lastNotification = self.storage?.getLastNotification()
                    settings = self.storage?.getSettings() ?? [:]
                }
            } else {
                // Fallback for iOS < 13
                lastNotification = self.storage?.getLastNotification()
                settings = self.storage?.getSettings() ?? [:]
            }
            
            // Calculate next notification time
            let nextNotificationTime = await scheduler.getNextNotificationTime() ?? 0
            
            // Get rollover status
            let lastRolloverTime = storage?.getLastRolloverTime() ?? 0
            
            var result: [String: Any] = [
                "isEnabled": isEnabled,
                "isScheduled": pendingCount > 0,
                "lastNotificationTime": lastNotification?.scheduledTime ?? 0,
                "nextNotificationTime": nextNotificationTime,
                "pending": pendingCount,
                "rolloverEnabled": true, // Indicate rollover is active
                "lastRolloverTime": lastRolloverTime, // When last rollover occurred
                "settings": settings
            ]
            
            DispatchQueue.main.async {
                call.resolve(result)
            }
        }
    }
    
    /**
     * Handle notification delivery event (from Notification Center)
     * 
     * This is called when AppDelegate posts notification delivery event
     * Matches Android's scheduleNextNotification() behavior
     * 
     * @param notification NSNotification with userInfo containing notification_id and scheduled_time
     */
    @objc private func handleNotificationDelivery(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
              let notificationId = userInfo["notification_id"] as? String,
              let scheduledTime = userInfo["scheduled_time"] as? Int64 else {
            NSLog("DNP-ROLLOVER: INVALID_DATA userInfo=\(String(describing: notification.userInfo))")
            print("DNP-ROLLOVER: INVALID_DATA userInfo=\(String(describing: notification.userInfo))")
            return
        }
        
        let scheduledTimeStr = formatTime(scheduledTime)
        NSLog("DNP-ROLLOVER: DELIVERY_DETECTED id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
        print("DNP-ROLLOVER: DELIVERY_DETECTED id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
        
        Task {
            await processRollover(notificationId: notificationId, scheduledTime: scheduledTime)
        }
    }
    
    /**
     * Process rollover for delivered notification
     * 
     * @param notificationId ID of notification that was delivered
     * @param scheduledTime Scheduled time of delivered notification
     */
    private func processRollover(notificationId: String, scheduledTime: Int64) async {
        let scheduledTimeStr = formatTime(scheduledTime)
        NSLog("DNP-ROLLOVER: PROCESS_START id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
        print("DNP-ROLLOVER: PROCESS_START id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
        
        guard let scheduler = scheduler, let storage = storage else {
            NSLog("DNP-ROLLOVER: ERROR id=\(notificationId) plugin_not_initialized")
            print("DNP-ROLLOVER: ERROR id=\(notificationId) plugin_not_initialized")
            return
        }
        
        // Get the notification content that was delivered
        guard let content = storage.getNotificationContent(id: notificationId) else {
            NSLog("DNP-ROLLOVER: ERROR id=\(notificationId) content_not_found")
            print("DNP-ROLLOVER: ERROR id=\(notificationId) content_not_found")
            return
        }
        
        let contentTimeStr = formatTime(content.scheduledTime)
        NSLog("DNP-ROLLOVER: CONTENT_FOUND id=\(notificationId) content_scheduled_time=\(contentTimeStr)")
        print("DNP-ROLLOVER: CONTENT_FOUND id=\(notificationId) content_scheduled_time=\(contentTimeStr)")
        
        // Schedule next notification
        // Note: DailyNotificationFetcher integration deferred to Phase 2
        let scheduled = await scheduler.scheduleNextNotification(
            content,
            storage: storage,
            fetcher: nil // TODO: Phase 2 - Add fetcher instance
        )
        
        if scheduled {
            NSLog("DNP-ROLLOVER: PROCESS_SUCCESS id=\(notificationId) next_notification_scheduled")
            print("DNP-ROLLOVER: PROCESS_SUCCESS id=\(notificationId) next_notification_scheduled")
            // Log success (non-fatal, background operation)
        } else {
            NSLog("DNP-ROLLOVER: PROCESS_FAILED id=\(notificationId) next_notification_not_scheduled")
            print("DNP-ROLLOVER: PROCESS_FAILED id=\(notificationId) next_notification_not_scheduled")
            // Log failure but continue (recovery will handle on next launch)
        }
    }
    
    /**
     * Format time for logging
     * 
     * @param timestamp Timestamp in milliseconds
     * @return Formatted time string
     */
    private func formatTime(_ timestamp: Int64) -> String {
        let date = Date(timeIntervalSince1970: Double(timestamp) / 1000.0)
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        return formatter.string(from: date)
    }
    
    /**
     * Check permission status
     * Returns boolean flags for each permission type
     * 
     * @param call Plugin call
     */
    @objc func checkPermissionStatus(_ call: CAPPluginCall) {
        NSLog("DNP-PLUGIN: checkPermissionStatus called - thread: %@", Thread.isMainThread ? "main" : "background")
        
        // Ensure scheduler is initialized (should be initialized in load(), but check anyway)
        if scheduler == nil {
            NSLog("DNP-PLUGIN: Scheduler not initialized, initializing now...")
            scheduler = DailyNotificationScheduler()
        }
        
        guard let scheduler = scheduler else {
            let error = DailyNotificationErrorCodes.createErrorResponse(
                code: DailyNotificationErrorCodes.PLUGIN_NOT_INITIALIZED,
                message: "Plugin not initialized"
            )
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            NSLog("DNP-PLUGIN: checkPermissionStatus failed - plugin not initialized")
            call.reject(errorMessage, errorCode)
            return
        }
        
        // Use Task without @MainActor, then dispatch to main queue for call.resolve
        Task {
            do {
                NSLog("DNP-PLUGIN: Task started - thread: %@", Thread.isMainThread ? "main" : "background")
                NSLog("DNP-PLUGIN: Calling scheduler.checkPermissionStatus()...")
                
                // Check notification permission status
                let notificationStatus = await scheduler.checkPermissionStatus()
                NSLog("DNP-PLUGIN: Got notification status: %d", notificationStatus.rawValue)
                
                let notificationsEnabled = notificationStatus == .authorized
                
                NSLog("DNP-PLUGIN: Notification status: %d, enabled: %@", notificationStatus.rawValue, notificationsEnabled ? "YES" : "NO")
                
                // iOS doesn't have exact alarms like Android, but we can check if notifications are authorized
                // For iOS, "exact alarm" equivalent is having authorized notifications
                let exactAlarmEnabled = notificationsEnabled
                
                // iOS doesn't have wake locks, but we can check Background App Refresh
                // Note: Background App Refresh status requires checking system settings
                // For now, we'll assume it's enabled if notifications are enabled
                // Phase 2: Add proper Background App Refresh status check
                let wakeLockEnabled = notificationsEnabled
                
                // All permissions granted if notifications are authorized
                let allPermissionsGranted = notificationsEnabled
                
                let result: [String: Any] = [
                    "notificationsEnabled": notificationsEnabled,
                    "exactAlarmEnabled": exactAlarmEnabled,
                    "wakeLockEnabled": wakeLockEnabled,
                    "allPermissionsGranted": allPermissionsGranted
                ]
                
                NSLog("DNP-PLUGIN: checkPermissionStatus result: %@", result)
                NSLog("DNP-PLUGIN: About to call resolve - thread: %@", Thread.isMainThread ? "main" : "background")
                
                // Dispatch to main queue for call.resolve (required by Capacitor)
                DispatchQueue.main.async {
                    NSLog("DNP-PLUGIN: On main queue, calling resolve")
                    call.resolve(result)
                    NSLog("DNP-PLUGIN: Call resolved successfully")
                }
            } catch {
                NSLog("DNP-PLUGIN: checkPermissionStatus error: %@", error.localizedDescription)
                let errorMessage = "Failed to check permission status: \(error.localizedDescription)"
                // Dispatch to main queue for call.reject (required by Capacitor)
                DispatchQueue.main.async {
                    call.reject(errorMessage, "permission_check_failed")
                }
            }
        }
        
        NSLog("DNP-PLUGIN: Task created and returned")
    }
    
    /**
     * Request notification permissions
     * Shows system permission dialog if permissions haven't been determined yet
     * 
     * @param call Plugin call
     */
    @objc func requestNotificationPermissions(_ call: CAPPluginCall) {
        NSLog("DNP-PLUGIN: requestNotificationPermissions called - thread: %@", Thread.isMainThread ? "main" : "background")
        
        // Ensure scheduler is initialized
        if scheduler == nil {
            NSLog("DNP-PLUGIN: Scheduler not initialized, initializing now...")
            scheduler = DailyNotificationScheduler()
        }
        
        guard let scheduler = scheduler else {
            let error = DailyNotificationErrorCodes.createErrorResponse(
                code: DailyNotificationErrorCodes.PLUGIN_NOT_INITIALIZED,
                message: "Plugin not initialized"
            )
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            NSLog("DNP-PLUGIN: requestNotificationPermissions failed - plugin not initialized")
            call.reject(errorMessage, errorCode)
            return
        }
        
        // Use Task without @MainActor, then dispatch to main queue for call.resolve
        Task {
            do {
                NSLog("DNP-PLUGIN: Task started for requestNotificationPermissions")
                
                // First check current status
                let currentStatus = await scheduler.checkPermissionStatus()
                NSLog("DNP-PLUGIN: Current permission status: %d", currentStatus.rawValue)
                
                // If already authorized, return success immediately
                if currentStatus == .authorized {
                    NSLog("DNP-PLUGIN: Permissions already granted")
                    let result: [String: Any] = [
                        "granted": true,
                        "status": "authorized"
                    ]
                    DispatchQueue.main.async {
                        call.resolve(result)
                    }
                    return
                }
                
                // If denied, we can't request again (user must go to Settings)
                if currentStatus == .denied {
                    NSLog("DNP-PLUGIN: Permissions denied - user must enable in Settings")
                    let error = DailyNotificationErrorCodes.createErrorResponse(
                        code: DailyNotificationErrorCodes.NOTIFICATIONS_DENIED,
                        message: "Notification permissions denied. Please enable in Settings."
                    )
                    let errorMessage = error["message"] as? String ?? "Permissions denied"
                    let errorCode = error["error"] as? String ?? "notifications_denied"
                    DispatchQueue.main.async {
                        call.reject(errorMessage, errorCode)
                    }
                    return
                }
                
                // Request permissions (will show system dialog if .notDetermined)
                NSLog("DNP-PLUGIN: Requesting permissions...")
                let granted = await scheduler.requestPermissions()
                NSLog("DNP-PLUGIN: Permission request result: %@", granted ? "granted" : "denied")
                
                // Get updated status
                let newStatus = await scheduler.checkPermissionStatus()
                
                let result: [String: Any] = [
                    "granted": granted,
                    "status": granted ? "authorized" : "denied",
                    "previousStatus": currentStatus.rawValue,
                    "newStatus": newStatus.rawValue
                ]
                
                NSLog("DNP-PLUGIN: requestNotificationPermissions result: %@", result)
                
                DispatchQueue.main.async {
                    call.resolve(result)
                }
            } catch {
                NSLog("DNP-PLUGIN: requestNotificationPermissions error: %@", error.localizedDescription)
                let errorMessage = "Failed to request permissions: \(error.localizedDescription)"
                DispatchQueue.main.async {
                    call.reject(errorMessage, "permission_request_failed")
                }
            }
        }
    }
    
    // MARK: - iOS-Specific Methods
    
    /**
     * Get notification permission status (iOS-specific)
     * 
     * Returns detailed permission status matching API.md specification
     * 
     * @param call Plugin call
     */
    @objc func getNotificationPermissionStatus(_ call: CAPPluginCall) {
        Task {
            do {
                guard let scheduler = scheduler else {
                    throw NSError(domain: "DailyNotificationPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Scheduler not initialized"])
                }
                
                let status = await scheduler.checkPermissionStatus()
                
                // Map to iOS-specific error if denied
                if status == .denied {
                    let error = DailyNotificationErrorCodes.notificationPermissionDenied()
                    let errorMessage = error["message"] as? String ?? "Notification permission denied"
                    let errorCode = error["error"] as? String ?? DailyNotificationErrorCodes.NOTIFICATION_PERMISSION_DENIED
                    DispatchQueue.main.async {
                        call.reject(errorMessage, errorCode)
                    }
                    return
                }
                
                let result: [String: Any] = [
                    "authorized": status == .authorized,
                    "denied": status == .denied,
                    "notDetermined": status == .notDetermined,
                    "provisional": status == .provisional
                ]
                
                DispatchQueue.main.async {
                    call.resolve(result)
                }
            } catch {
                DispatchQueue.main.async {
                    call.reject("Failed to get permission status: \(error.localizedDescription)", "permission_status_failed")
                }
            }
        }
    }
    
    /**
     * Request notification permission (iOS-specific)
     * 
     * @param call Plugin call
     */
    @objc func requestNotificationPermission(_ call: CAPPluginCall) {
        Task {
            do {
                guard let scheduler = scheduler else {
                    throw NSError(domain: "DailyNotificationPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Scheduler not initialized"])
                }
                
                let granted = await scheduler.requestPermissions()
                
                let result: [String: Any] = [
                    "granted": granted
                ]
                
                DispatchQueue.main.async {
                    call.resolve(result)
                }
            } catch {
                DispatchQueue.main.async {
                    call.reject("Failed to request permission: \(error.localizedDescription)", "permission_request_failed")
                }
            }
        }
    }
    
    /**
     * Get pending notifications (iOS-specific)
     * 
     * @param call Plugin call
     */
    @objc func getPendingNotifications(_ call: CAPPluginCall) {
        Task {
            do {
                let requests = try await notificationCenter.pendingNotificationRequests()
                
                var notifications: [[String: Any]] = []
                for request in requests {
                    let content = request.content
                    var triggerDate: Int64 = 0
                    
                    if let calendarTrigger = request.trigger as? UNCalendarNotificationTrigger {
                        if let nextDate = calendarTrigger.nextTriggerDate() {
                            triggerDate = Int64(nextDate.timeIntervalSince1970 * 1000)
                        }
                    } else if let timeIntervalTrigger = request.trigger as? UNTimeIntervalNotificationTrigger {
                        if let nextDate = timeIntervalTrigger.nextTriggerDate() {
                            triggerDate = Int64(nextDate.timeIntervalSince1970 * 1000)
                        }
                    }
                    
                    let notification: [String: Any] = [
                        "identifier": request.identifier,
                        "title": content.title,
                        "body": content.body,
                        "triggerDate": triggerDate,
                        "triggerType": request.trigger is UNCalendarNotificationTrigger ? "calendar" : (request.trigger is UNTimeIntervalNotificationTrigger ? "timeInterval" : "location"),
                        "repeats": request.trigger?.repeats ?? false
                    ]
                    notifications.append(notification)
                }
                
                let result: [String: Any] = [
                    "count": notifications.count,
                    "notifications": notifications
                ]
                
                DispatchQueue.main.async {
                    call.resolve(result)
                }
            } catch {
                DispatchQueue.main.async {
                    call.reject("Failed to get pending notifications: \(error.localizedDescription)", "pending_notifications_failed")
                }
            }
        }
    }
    
    /**
     * Get background task status (iOS-specific)
     * 
     * @param call Plugin call
     */
    @objc func getBackgroundTaskStatus(_ call: CAPPluginCall) {
        // Note: BGTaskScheduler doesn't provide a way to query registered task identifiers
        // We assume tasks are registered if setupBackgroundTasks() was called
        // Background App Refresh status cannot be checked programmatically
        // User must check in Settings app
        
        let result: [String: Any] = [
            "fetchTaskRegistered": true, // Assumed registered if setupBackgroundTasks() was called
            "notifyTaskRegistered": true, // Assumed registered if setupBackgroundTasks() was called
            "lastFetchExecution": storage?.getLastSuccessfulRun() ?? NSNull(),
            "lastNotifyExecution": NSNull(), // TODO: Track notify execution
            "backgroundRefreshEnabled": NSNull() // Cannot check programmatically
        ]
        
        call.resolve(result)
    }
    
    /**
     * Open notification settings (iOS-specific)
     * 
     * @param call Plugin call
     */
    @objc func openNotificationSettings(_ call: CAPPluginCall) {
        if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
            if UIApplication.shared.canOpenURL(settingsUrl) {
                UIApplication.shared.open(settingsUrl) { success in
                    DispatchQueue.main.async {
                        if success {
                            call.resolve()
                        } else {
                            call.reject("Failed to open notification settings", "open_settings_failed")
                        }
                    }
                }
            } else {
                call.reject("Cannot open settings URL", "open_settings_failed")
            }
        } else {
            call.reject("Invalid settings URL", "open_settings_failed")
        }
    }
    
    /**
     * Open Background App Refresh settings (iOS-specific)
     * 
     * Note: iOS doesn't provide a direct URL to Background App Refresh settings.
     * This opens the app's settings page where user can find Background App Refresh.
     * 
     * @param call Plugin call
     */
    @objc func openBackgroundAppRefreshSettings(_ call: CAPPluginCall) {
        // iOS doesn't have a direct URL to Background App Refresh settings
        // Open app settings instead, where user can find Background App Refresh
        if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
            if UIApplication.shared.canOpenURL(settingsUrl) {
                UIApplication.shared.open(settingsUrl) { success in
                    DispatchQueue.main.async {
                        if success {
                            call.resolve()
                        } else {
                            call.reject("Failed to open settings", "open_settings_failed")
                        }
                    }
                }
            } else {
                call.reject("Cannot open settings URL", "open_settings_failed")
            }
        } else {
            call.reject("Invalid settings URL", "open_settings_failed")
        }
    }
    
    // MARK: - Channel Methods (iOS Parity with Android)
    
    /**
     * Check if notification channel is enabled
     * 
     * iOS Note: iOS doesn't have per-channel control like Android. This method
     * checks if notifications are authorized app-wide, which is the iOS equivalent.
     * 
     * @param call Plugin call with optional channelId parameter
     */
    @objc func isChannelEnabled(_ call: CAPPluginCall) {
        NSLog("DNP-PLUGIN: isChannelEnabled called")
        
        // Ensure scheduler is initialized
        if scheduler == nil {
            scheduler = DailyNotificationScheduler()
        }
        
        guard let scheduler = scheduler else {
            let error = DailyNotificationErrorCodes.createErrorResponse(
                code: DailyNotificationErrorCodes.PLUGIN_NOT_INITIALIZED,
                message: "Plugin not initialized"
            )
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        // Get channelId from call (optional, for API parity with Android)
        let channelId = call.getString("channelId") ?? "default"
        
        // iOS doesn't have per-channel control, so check app-wide notification authorization
        Task {
            let status = await scheduler.checkPermissionStatus()
            let enabled = (status == .authorized || status == .provisional)
            
            let result: [String: Any] = [
                "enabled": enabled,
                "channelId": channelId
            ]
            
            DispatchQueue.main.async {
                call.resolve(result)
            }
        }
    }
    
    /**
     * Open notification channel settings
     * 
     * iOS Note: iOS doesn't have per-channel settings. This method opens
     * the app's notification settings in iOS Settings, which is the iOS equivalent.
     * 
     * @param call Plugin call with optional channelId parameter
     */
    @objc func openChannelSettings(_ call: CAPPluginCall) {
        NSLog("DNP-PLUGIN: openChannelSettings called")
        
        // Get channelId from call (optional, for API parity with Android)
        let channelId = call.getString("channelId") ?? "default"
        
        // iOS doesn't have per-channel settings, so open app-wide notification settings
        if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
            if UIApplication.shared.canOpenURL(settingsUrl) {
                UIApplication.shared.open(settingsUrl) { success in
                    if success {
                        NSLog("DNP-PLUGIN: Opened iOS Settings for channel: %@", channelId)
                        DispatchQueue.main.async {
                            call.resolve()
                        }
                    } else {
                        NSLog("DNP-PLUGIN: Failed to open iOS Settings")
                        DispatchQueue.main.async {
                            call.reject("Failed to open settings")
                        }
                    }
                }
            } else {
                call.reject("Cannot open settings URL")
            }
        } else {
            call.reject("Invalid settings URL")
        }
    }
    
    /**
     * Update notification settings
     * 
     * @param call Plugin call containing new settings
     */
    @objc func updateSettings(_ call: CAPPluginCall) {
        guard let settings = call.getObject("settings") else {
            let error = DailyNotificationErrorCodes.missingParameter("settings")
            let errorMessage = error["message"] as? String ?? "Unknown error"
            let errorCode = error["error"] as? String ?? "unknown_error"
            call.reject(errorMessage, errorCode)
            return
        }
        
        Task {
            // Save settings via state actor (thread-safe)
            if #available(iOS 13.0, *) {
                if let stateActor = await self.stateActor {
                    await stateActor.saveSettings(settings)
                } else {
                    // Fallback to direct storage access
                    self.storage?.saveSettings(settings)
                }
            } else {
                // Fallback for iOS < 13
                self.storage?.saveSettings(settings)
            }
            
            DispatchQueue.main.async {
                print("DNP-PLUGIN: Settings updated successfully")
                call.resolve()
            }
        }
    }
    
    // MARK: - Phase 1: Helper Methods
    
    /**
     * Get next scheduled notification time
     * 
     * Helper method to get the next scheduled notification time for
     * scheduling background tasks. Uses async/await internally.
     * 
     * @return Next scheduled notification time in milliseconds (Int64), or nil if none
     */
    private func getNextScheduledNotificationTime() -> Int64? {
        guard let scheduler = scheduler else {
            return nil
        }
        
        // Use async helper to get next notification time
        // Note: This is called from background task handlers which are already async
        var nextTime: Int64? = nil
        let semaphore = DispatchSemaphore(value: 0)
        
        Task {
            nextTime = await scheduler.getNextNotificationTime()
            semaphore.signal()
        }
        
        // Wait with timeout (2 seconds - background tasks have limited time)
        _ = semaphore.wait(timeout: .now() + 2.0)
        return nextTime
    }
    
    /**
     * Calculate next scheduled time for given hour and minute
     * 
     * Uses scheduler's calculateNextOccurrence for consistency
     * 
     * @param hour Hour (0-23)
     * @param minute Minute (0-59)
     * @return Timestamp in milliseconds
     */
    private func calculateNextScheduledTime(hour: Int, minute: Int) -> Int64 {
        guard let scheduler = scheduler else {
            // Fallback calculation if scheduler not available
            let calendar = Calendar.current
            let now = Date()
            
            var components = calendar.dateComponents([.year, .month, .day], from: now)
            components.hour = hour
            components.minute = minute
            components.second = 0
            
            var scheduledDate = calendar.date(from: components) ?? now
            
            // If scheduled time has passed today, schedule for tomorrow
            if scheduledDate <= now {
                scheduledDate = calendar.date(byAdding: .day, value: 1, to: scheduledDate) ?? scheduledDate
            }
            
            return Int64(scheduledDate.timeIntervalSince1970 * 1000)
        }
        
        return scheduler.calculateNextOccurrence(hour: hour, minute: minute)
    }
    
    /**
     * Schedule background fetch 5 minutes before notification time
     * 
     * @param scheduledTime Notification scheduled time in milliseconds
     */
    private func scheduleBackgroundFetch(scheduledTime: Int64) {
        // Calculate fetch time (5 minutes before notification)
        let fetchTime = scheduledTime - (5 * 60 * 1000) // 5 minutes in milliseconds
        let fetchDate = Date(timeIntervalSince1970: Double(fetchTime) / 1000.0)
        
        // Schedule BGTaskScheduler task
        let request = BGAppRefreshTaskRequest(identifier: fetchTaskIdentifier)
        request.earliestBeginDate = fetchDate
        
        do {
            try backgroundTaskScheduler.submit(request)
            
            // Store earliest begin date for miss detection via state actor (thread-safe)
            Task {
                if #available(iOS 13.0, *) {
                    if let stateActor = await self.stateActor {
                        await stateActor.saveBGTaskEarliestBegin(timestamp: fetchTime)
                    } else {
                        // Fallback to direct storage access
                        self.storage?.saveBGTaskEarliestBegin(timestamp: fetchTime)
                    }
                } else {
                    // Fallback for iOS < 13
                    self.storage?.saveBGTaskEarliestBegin(timestamp: fetchTime)
                }
            }
            
            print("DNP-FETCH-SCHEDULE: Background fetch scheduled for \(fetchDate)")
        } catch {
            // BGTaskScheduler errors are common on simulator (Code=1: notPermitted)
            // This is expected behavior - simulators don't reliably support background tasks
            // On real devices, this should work if Background App Refresh is enabled
            let errorDescription = error.localizedDescription
            if errorDescription.contains("BGTaskSchedulerErrorDomain") || 
               errorDescription.contains("Code=1") ||
               (error as NSError).domain == "BGTaskSchedulerErrorDomain" {
                print("DNP-FETCH-SCHEDULE: Background fetch scheduling failed (expected on simulator): \(errorDescription)")
                print("DNP-FETCH-SCHEDULE: Note: BGTaskScheduler requires real device or Background App Refresh enabled")
            } else {
                print("DNP-FETCH-SCHEDULE: Failed to schedule background fetch: \(error)")
            }
            // Don't fail notification scheduling if background fetch fails
            // Notification will still be delivered, just without prefetch
        }
    }
}

// MARK: - CAPBridgedPlugin Conformance
// This extension makes the plugin conform to CAPBridgedPlugin protocol
// which is required for Capacitor to discover and register the plugin
@objc extension DailyNotificationPlugin: CAPBridgedPlugin {
    @objc public var identifier: String {
        return "com.timesafari.dailynotification"
    }
    
    @objc public var jsName: String {
        return "DailyNotification"
    }
    
    @objc public var pluginMethods: [CAPPluginMethod] {
        var methods: [CAPPluginMethod] = []
        
        // Core methods
        methods.append(CAPPluginMethod(name: "configure", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "configureNativeFetcher", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "scheduleDailyNotification", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "getLastNotification", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "getNotificationStatus", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "updateSettings", returnType: CAPPluginReturnPromise))
        
        // Permission methods
        methods.append(CAPPluginMethod(name: "checkPermissionStatus", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "requestNotificationPermissions", returnType: CAPPluginReturnPromise))
        
        // iOS-specific methods
        methods.append(CAPPluginMethod(name: "getNotificationPermissionStatus", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "requestNotificationPermission", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "getPendingNotifications", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "getBackgroundTaskStatus", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "openNotificationSettings", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "openBackgroundAppRefreshSettings", returnType: CAPPluginReturnPromise))
        
        // Channel methods (iOS parity with Android)
        methods.append(CAPPluginMethod(name: "isChannelEnabled", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "openChannelSettings", returnType: CAPPluginReturnPromise))
        
        // Reminder methods
        methods.append(CAPPluginMethod(name: "scheduleDailyReminder", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "cancelDailyReminder", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "getScheduledReminders", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "updateDailyReminder", returnType: CAPPluginReturnPromise))
        
        // Dual scheduling methods
        methods.append(CAPPluginMethod(name: "scheduleContentFetch", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "scheduleUserNotification", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "scheduleDualNotification", returnType: CAPPluginReturnPromise))
        methods.append(CAPPluginMethod(name: "getDualScheduleStatus", returnType: CAPPluginReturnPromise))
        
        return methods
    }
}