//
//  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) {
        // Validate and extract configuration parameters
        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 3: Process activeDidIntegration configuration
        if let activeDidConfig = call.getObject("activeDidIntegration") {
            // Extract and store activeDidIntegration configuration
            // This enables TimeSafari-specific DID-based authentication and API integration
            if let platform = activeDidConfig["platform"] as? String {
                UserDefaults.standard.set(platform, forKey: "activeDidIntegration_platform")
            }
            if let storageType = activeDidConfig["storageType"] as? String {
                UserDefaults.standard.set(storageType, forKey: "activeDidIntegration_storageType")
            }
            if let jwtExpirationSeconds = activeDidConfig["jwtExpirationSeconds"] as? Int {
                UserDefaults.standard.set(jwtExpirationSeconds, forKey: "activeDidIntegration_jwtExpirationSeconds")
            }
            if let apiServer = activeDidConfig["apiServer"] as? String {
                UserDefaults.standard.set(apiServer, forKey: "activeDidIntegration_apiServer")
            }
            if let activeDid = activeDidConfig["activeDid"] as? String {
                UserDefaults.standard.set(activeDid, forKey: "activeDidIntegration_activeDid")
            }
            if let autoSync = activeDidConfig["autoSync"] as? Bool {
                UserDefaults.standard.set(autoSync, forKey: "activeDidIntegration_autoSync")
            }
            if let identityChangeGraceSeconds = activeDidConfig["identityChangeGraceSeconds"] as? Int {
                UserDefaults.standard.set(identityChangeGraceSeconds, forKey: "activeDidIntegration_identityChangeGraceSeconds")
            }
            print("DNP-PLUGIN: activeDidIntegration configuration stored")
        }
        
        // Determine database path (use provided or default)
        let finalDbPath: String
        if let dbPath = dbPath, !dbPath.isEmpty {
            finalDbPath = dbPath
        } else {
            let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            finalDbPath = documentsPath.appendingPathComponent("daily_notifications.db").path
        }
        
        // Reinitialize storage with new database path if needed
        if let currentStorage = storage {
            if currentStorage.getDatabasePath() != finalDbPath {
                storage = DailyNotificationStorage(databasePath: finalDbPath)
            }
        } else {
            storage = DailyNotificationStorage(databasePath: finalDbPath)
        }
        
        // Delegate to storage to store configuration
        storeConfiguration(
            ttlSeconds: ttlSeconds,
            prefetchLeadMinutes: prefetchLeadMinutes,
            maxNotificationsPerDay: maxNotificationsPerDay,
            retentionDays: retentionDays,
            storageMode: storageMode,
            dbPath: finalDbPath
        )
        
        call.resolve()
    }
    
    /**
     * 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
        }
        
        do {
            // Delegate to background fetch scheduler
            try scheduleBackgroundFetch(config: config)
            call.resolve()
        } catch {
            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
        }
        
        do {
            // Delegate to user notification scheduler
            try scheduleUserNotification(config: config)
            call.resolve()
        } catch {
            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
        }
        
        do {
            // Delegate to ScheduleHelper for dual scheduling orchestration
            try DailyNotificationScheduleHelper.scheduleDualNotification(
                contentFetchConfig: contentFetchConfig,
                userNotificationConfig: userNotificationConfig,
                scheduleBackgroundFetch: { [weak self] config in
                    guard let strongSelf = self else {
                        throw NSError(domain: "DailyNotificationPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Plugin deallocated"])
                    }
                    try strongSelf.scheduleBackgroundFetch(config: config)
                },
                scheduleUserNotification: { [weak self] config in
                    guard let strongSelf = self else {
                        throw NSError(domain: "DailyNotificationPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Plugin deallocated"])
                    }
                    try strongSelf.scheduleUserNotification(config: config)
                }
            )
            call.resolve()
        } catch {
            call.reject("Dual notification scheduling failed: \(error.localizedDescription)")
        }
    }
    
    @objc func getDualScheduleStatus(_ call: CAPPluginCall) {
        Task {
            do {
                // Delegate to private helper (will be moved to service in future batch)
                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"])
        }
        
        // Delegate to ScheduleHelper for health status (combines multiple sources)
        return try await DailyNotificationScheduleHelper.getHealthStatus(
            scheduler: scheduler,
            storage: self.storage,
            stateActor: await self.stateActor
        )
    }
    
    // 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 3: Check for JWT-signed fetcher configuration
        // If native fetcher is configured, use it; otherwise fall back to dummy content
        let nativeFetcherConfig = UserDefaults.standard.string(forKey: "native_fetcher_config")
        
        // Save content to storage via state actor (thread-safe)
        Task {
            let content: NotificationContent
            
            if let configJson = nativeFetcherConfig,
               let configData = configJson.data(using: .utf8),
               let config = try? JSONSerialization.jsonObject(with: configData) as? [String: Any],
               let apiBaseUrl = config["apiBaseUrl"] as? String,
               let activeDid = config["activeDid"] as? String,
               let jwtToken = config["jwtToken"] as? String {
                // Phase 3: JWT-signed fetcher is configured - attempt HTTP fetch
                print("DNP-FETCH: Using JWT-signed fetcher (apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...)")
                
                // Attempt to fetch content from TimeSafari API
                // Note: This is a minimal implementation - can be extended with full API client
                do {
                    let fetchedContent = try await fetchContentFromAPI(
                        apiBaseUrl: apiBaseUrl,
                        activeDid: activeDid,
                        jwtToken: jwtToken
                    )
                    content = fetchedContent
                    print("DNP-FETCH: Successfully fetched content from API")
                } catch {
                    // Fallback to dummy content on fetch failure
                    print("DNP-FETCH: API fetch failed (\(error.localizedDescription)), using fallback content")
                    content = NotificationContent(
                        id: "fallback_\(Date().timeIntervalSince1970)",
                        title: "Daily Update",
                        body: "Your daily notification is ready",
                        scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000),
                        fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
                        url: nil,
                        payload: ["fetchError": error.localizedDescription],
                        etag: nil
                    )
                }
            } else {
                // Fallback: Dummy content fetch (no network)
                print("DNP-FETCH: Using dummy content (native fetcher not configured)")
                content = NotificationContent(
                    id: "dummy_\(Date().timeIntervalSince1970)",
                    title: "Daily Update",
                    body: "Your daily notification is ready",
                    scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000),
                    fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
                    url: nil,
                    payload: nil,
                    etag: nil
                )
            }
            do {
                // Use the content (either from JWT fetcher or dummy)
                if #available(iOS 13.0, *) {
                    if let stateActor = await self.stateActor {
                        await stateActor.saveNotificationContent(content)
                        
                        // Mark successful run
                        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
                        await stateActor.saveLastSuccessfulRun(timestamp: currentTime)
                } else {
                    // Fallback to direct storage access
                    self.storage?.saveNotificationContent(content)
                        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
                        self.storage?.saveLastSuccessfulRun(timestamp: currentTime)
                    }
                } else {
                    // Fallback for iOS < 13
                    self.storage?.saveNotificationContent(content)
                    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 scheduler = self.scheduler {
                    let nextScheduledTime = await scheduler.getNextNotificationTime()
                    if let nextTime = nextScheduledTime {
                        self.scheduleBackgroundFetch(scheduledTime: nextTime)
                        print("DNP-FETCH: Next background fetch scheduled")
                    } else {
                        print("DNP-FETCH: No future notifications found, skipping next task schedule")
                    }
                } else {
                    print("DNP-FETCH: Scheduler not available, 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 scheduler = self.scheduler {
                    let nextScheduledTime = await scheduler.getNextNotificationTime()
                    if let nextTime = nextScheduledTime {
                        // 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 \(nextTime)")
                    }
                }
                
                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")

        // Validate and 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
        )

        // Delegate to UNUserNotificationCenter to schedule notification
        notificationCenter.add(request) { error in
            DispatchQueue.main.async {
                if let error = error {
                    call.reject("Failed to schedule daily reminder: \(error.localizedDescription)")
                } else {
                    call.resolve()
                }
            }
        }
    }

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

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

        // Delegate to storage for reminder removal
        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
        }

        // 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
        )
        
        // Delegate to ScheduleHelper for orchestration
        Task {
            let scheduled = await DailyNotificationScheduleHelper.scheduleDailyNotification(
                content: content,
                scheduledTime: scheduledTime,
                scheduler: scheduler,
                storage: self.storage,
                stateActor: await self.stateActor,
                scheduleBackgroundFetch: { [weak self] (scheduledTime: Int64) -> Void in
                    self?.scheduleBackgroundFetch(scheduledTime: scheduledTime)
                }
            )
            
            DispatchQueue.main.async {
                if scheduled {
                    call.resolve()
                } else {
                    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 {
            let lastNotification: NotificationContent?
            
            // Delegate to stateActor if available (thread-safe), otherwise use storage directly
            if #available(iOS 13.0, *), let stateActor = await self.stateActor {
                lastNotification = await stateActor.getLastNotification()
            } else {
                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 {
            // Delegate cancellation to scheduler
            await scheduler.cancelAllNotifications()
            
            // Clear storage via stateActor if available (thread-safe), otherwise use storage directly
            if #available(iOS 13.0, *), let stateActor = await self.stateActor {
                await stateActor.clearAllNotifications()
            } else {
                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 {
            // Delegate to scheduler for permission status and pending count
            let isEnabled = await scheduler.checkPermissionStatus() == .authorized
            let pendingCount = await scheduler.getPendingNotificationCount()
            
            // Delegate to stateActor if available (thread-safe), otherwise use storage directly
            let lastNotification: NotificationContent?
            let settings: [String: Any]
            if #available(iOS 13.0, *), let stateActor = await self.stateActor {
                lastNotification = await stateActor.getLastNotification()
                settings = await stateActor.getSettings()
            } else {
                lastNotification = self.storage?.getLastNotification()
                settings = self.storage?.getSettings() ?? [:]
            }
            
            // Delegate to scheduler for next notification time
            let nextNotificationTime = await scheduler.getNextNotificationTime() ?? 0
            
            // Delegate to storage for 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) {
        // Extract notification data from userInfo
        guard let userInfo = notification.userInfo,
              let notificationId = userInfo["notification_id"] as? String,
              let scheduledTime = userInfo["scheduled_time"] as? Int64 else {
            return
        }
        
        // Track notify execution
        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
        storage?.saveLastNotifyExecution(timestamp: currentTime)
        
        // Delegate rollover processing (glue logic - will be moved to service in future)
        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 {
        guard let scheduler = scheduler, let storage = storage else {
            return
        }
        
        // Get the notification content that was delivered
        guard let content = storage.getNotificationContent(id: notificationId) else {
            return
        }
        
        // Delegate to scheduler to schedule next notification (glue logic - will be moved to service)
        // Note: fetcher parameter is unused - scheduler uses fetchScheduler instead (already implemented)
        let scheduled = await scheduler.scheduleNextNotification(
            content,
            storage: storage,
            fetcher: nil // Unused - fetchScheduler handles prefetch scheduling
        )
        
        // Rollover processing is non-fatal - recovery will handle on next launch if needed
        _ = scheduled
    }
    
    /**
     * 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) {
        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 {
            do {
                // Delegate to scheduler for permission status check
                let notificationStatus = await scheduler.checkPermissionStatus()
                let notificationsEnabled = notificationStatus == .authorized
                
                // Format result (iOS-specific: exactAlarm and wakeLock map to notification permission)
                let result: [String: Any] = [
                    "notificationsEnabled": notificationsEnabled,
                    "exactAlarmEnabled": notificationsEnabled, // iOS equivalent
                    "wakeLockEnabled": notificationsEnabled, // iOS equivalent (Background App Refresh)
                    "allPermissionsGranted": notificationsEnabled
                ]
                
                DispatchQueue.main.async {
                    call.resolve(result)
                }
            } catch {
                DispatchQueue.main.async {
                    call.reject("Failed to check permission status: \(error.localizedDescription)", "permission_check_failed")
                }
            }
        }
    }
    
    /**
     * Request notification permissions
     * Shows system permission dialog if permissions haven't been determined yet
     * 
     * @param call Plugin call
     */
    @objc func requestNotificationPermissions(_ 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 {
            do {
                // Delegate to scheduler for permission request
                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 permissions: \(error.localizedDescription)", "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) {
        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 {
            do {
                // Delegate to scheduler for permission status check
                let status = await scheduler.checkPermissionStatus()
                
                // Format result with all status flags
                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) {
        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 {
            do {
                // Delegate to scheduler for permission request
                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 {
                // Delegate to UNUserNotificationCenter for pending requests
                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
        
        // Delegate storage access to storage service
        let lastFetchExecution: Any = storage?.getLastSuccessfulRun() ?? NSNull()
        let lastNotifyExecution: Any = storage?.getLastNotifyExecution() ?? NSNull()
        
        let result: [String: Any] = [
            "fetchTaskRegistered": true, // Assumed registered if setupBackgroundTasks() was called
            "notifyTaskRegistered": true, // Assumed registered if setupBackgroundTasks() was called
            "lastFetchExecution": lastFetchExecution,
            "lastNotifyExecution": lastNotifyExecution,
            "backgroundRefreshEnabled": NSNull() // Cannot check programmatically
        ]
        
        call.resolve(result)
    }
    
    /**
     * Open notification settings (iOS-specific)
     * 
     * @param call Plugin call
     */
    @objc func openNotificationSettings(_ call: CAPPluginCall) {
        // Delegate to UIApplication to open settings
        guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
            call.reject("Invalid settings URL", "open_settings_failed")
            return
        }
        
        guard UIApplication.shared.canOpenURL(settingsUrl) else {
            call.reject("Cannot open settings URL", "open_settings_failed")
            return
        }
        
        UIApplication.shared.open(settingsUrl) { success in
            DispatchQueue.main.async {
                if success {
                    call.resolve()
                } else {
                    call.reject("Failed to open notification settings", "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
        guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
            call.reject("Invalid settings URL", "open_settings_failed")
            return
        }
        
        guard UIApplication.shared.canOpenURL(settingsUrl) else {
            call.reject("Cannot open settings URL", "open_settings_failed")
            return
        }
        
        UIApplication.shared.open(settingsUrl) { success in
            DispatchQueue.main.async {
                if success {
                    call.resolve()
                } else {
                    call.reject("Failed to open settings", "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) {
        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)
        // iOS doesn't have per-channel control, so check app-wide notification authorization
        let channelId = call.getString("channelId") ?? "default"
        Task {
            // Delegate to scheduler for permission status check
            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) {
        // Get channelId from call (optional, for API parity with Android)
        // iOS doesn't have per-channel settings, so open app-wide notification settings
        guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
            call.reject("Invalid settings URL", "open_settings_failed")
            return
        }
        
        guard UIApplication.shared.canOpenURL(settingsUrl) else {
            call.reject("Cannot open settings URL", "open_settings_failed")
            return
        }
        
        UIApplication.shared.open(settingsUrl) { success in
            DispatchQueue.main.async {
                if success {
                    call.resolve()
                } else {
                    call.reject("Failed to open settings", "open_settings_failed")
                }
            }
        }
    }
    
    /**
     * 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 {
            // Delegate to stateActor if available (thread-safe), otherwise use storage directly
            if #available(iOS 13.0, *), let stateActor = await self.stateActor {
                await stateActor.saveSettings(settings)
            } else {
                self.storage?.saveSettings(settings)
            }
            
            DispatchQueue.main.async {
                call.resolve()
            }
        }
    }
    
    // MARK: - Phase 3: JWT Fetcher HTTP Implementation
    
    /**
     * Fetch notification content from TimeSafari API using JWT authentication
     * 
     * Phase 3: Complete HTTP implementation for JWT-signed fetcher
     * 
     * This method:
     * - Makes authenticated HTTP request to TimeSafari API
     * - Uses JWT token in Authorization header
     * - Parses response and converts to NotificationContent
     * - Handles errors gracefully with fallback
     * 
     * @param apiBaseUrl Base URL for TimeSafari API server
     * @param activeDid Active DID for authentication
     * @param jwtToken JWT token for Authorization header
     * @return NotificationContent from API or throws error
     */
    private func fetchContentFromAPI(
        apiBaseUrl: String,
        activeDid: String,
        jwtToken: String
    ) async throws -> NotificationContent {
        // Construct API endpoint URL
        // Note: This is a minimal implementation - can be extended with full endpoint support
        let endpoint = "/api/v2/report/offers"
        guard let baseURL = URL(string: apiBaseUrl),
              let url = URL(string: endpoint, relativeTo: baseURL) else {
            throw NSError(
                domain: "DailyNotification",
                code: -1,
                userInfo: [NSLocalizedDescriptionKey: "Invalid API URL"]
            )
        }
        
        // Create HTTP request with JWT authentication
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.timeoutInterval = 30.0 // 30 second timeout
        
        print("DNP-FETCH-HTTP: Making request to \(url.absoluteString)")
        
        // Execute HTTP request
        let (data, response) = try await URLSession.shared.data(for: request)
        
        // Validate HTTP response
        guard let httpResponse = response as? HTTPURLResponse else {
            throw NSError(
                domain: "DailyNotification",
                code: -2,
                userInfo: [NSLocalizedDescriptionKey: "Invalid HTTP response"]
            )
        }
        
        print("DNP-FETCH-HTTP: Response status code: \(httpResponse.statusCode)")
        
        // Check for successful response
        guard httpResponse.statusCode == 200 else {
            throw NSError(
                domain: "DailyNotification",
                code: httpResponse.statusCode,
                userInfo: [NSLocalizedDescriptionKey: "HTTP error: \(httpResponse.statusCode)"]
            )
        }
        
        // Parse JSON response
        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            throw NSError(
                domain: "DailyNotification",
                code: -3,
                userInfo: [NSLocalizedDescriptionKey: "Failed to parse JSON response"]
            )
        }
        
        // Convert API response to NotificationContent
        // Note: This is a minimal conversion - can be extended to handle full TimeSafari API response structure
        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
        let content = NotificationContent(
            id: "api_\(currentTime)",
            title: json["title"] as? String ?? "Daily Update",
            body: json["body"] as? String ?? "Your daily notification is ready",
            scheduledTime: currentTime + (5 * 60 * 1000), // 5 min from now
            fetchedAt: currentTime,
            url: apiBaseUrl,
            payload: json,
            etag: httpResponse.value(forHTTPHeaderField: "ETag")
        )
        
        print("DNP-FETCH-HTTP: Successfully converted API response to NotificationContent")
        return content
    }
    
    // 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
    }
}