//
//  DailyNotificationBackgroundTaskTestHarness.swift
//  DailyNotificationPlugin
//
//  Test harness for BGTaskScheduler prefetch functionality
//  Reference implementation demonstrating task registration, scheduling, and handling
//
//  See: doc/test-app-ios/IOS_PREFETCH_TESTING.md for testing procedures
//

import Foundation
import BackgroundTasks
import UIKit
import os.log

/// Minimal BGTaskScheduler test harness for DailyNotificationPlugin prefetch testing
///
/// This is a reference implementation demonstrating:
/// - Task registration
/// - Task scheduling
/// - Task handler implementation
/// - Expiration handling
/// - Completion reporting
///
/// **Usage:**
/// - Reference this when implementing actual prefetch logic in `DailyNotificationBackgroundTaskManager.swift`
/// - Use in test app for debugging BGTaskScheduler behavior
/// - See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for comprehensive testing guide
///
/// **Info.plist Requirements:**
/// ```xml
/// <key>BGTaskSchedulerPermittedIdentifiers</key>
/// <array>
///     <string>org.timesafari.dailynotification.fetch</string>
/// </array>
/// ```
///
/// **Background Modes (Xcode Capabilities):**
/// - Background fetch
/// - Background processing (if using BGProcessingTask)
class DailyNotificationBackgroundTaskTestHarness {
    
    // MARK: - Constants
    
    static let prefetchTaskIdentifier = "org.timesafari.dailynotification.fetch"
    
    // MARK: - Structured Logging
    
    /// OSLog categories for structured logging (iOS 13.0+ compatible)
    private static let pluginLog = OSLog(subsystem: "org.timesafari.dailynotification", category: "plugin")
    private static let fetchLog = OSLog(subsystem: "org.timesafari.dailynotification", category: "fetch")
    private static let schedulerLog = OSLog(subsystem: "org.timesafari.dailynotification", category: "scheduler")
    private static let storageLog = OSLog(subsystem: "org.timesafari.dailynotification", category: "storage")
    
    /// Log telemetry snapshot for validation
    static func logTelemetrySnapshot(prefix: String = "DNP-") {
        // Phase 2: Capture telemetry counters from structured logs
        os_log("[DNP-FETCH] Telemetry snapshot: %{public}@prefetch_scheduled_total, %{public}@prefetch_executed_total, %{public}@prefetch_success_total", log: fetchLog, type: .info, prefix, prefix, prefix)
    }
    
    // MARK: - Registration
    
    /// Register BGTaskScheduler task handler
    ///
    /// Call this in AppDelegate.application(_:didFinishLaunchingWithOptions:)
    /// before app finishes launching.
    static func registerBackgroundTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: prefetchTaskIdentifier,
            using: nil
        ) { task in
            // This closure is called when the task is launched by the system
            handlePrefetchTask(task: task as! BGAppRefreshTask)
        }
        
        print("[DNP-FETCH] Registered BGTaskScheduler with id=\(prefetchTaskIdentifier)")
    }
    
    // MARK: - Scheduling
    
    /// Schedule a BGAppRefreshTask for prefetch
    ///
    /// **Validation:**
    /// - Ensures earliestBeginDate is at least 60 seconds in future (iOS requirement)
    /// - Cancels any existing pending task for this notification (one active task rule)
    /// - Handles simulator limitations gracefully (Code=1 error is expected)
    ///
    /// - Parameter earliestOffsetSeconds: Seconds from now when task can begin
    /// - Parameter notificationId: Optional notification ID to cancel existing task
    /// - Returns: true if scheduling succeeded, false otherwise
    @discardableResult
    static func schedulePrefetchTask(earliestOffsetSeconds: TimeInterval, notificationId: String? = nil) -> Bool {
        // Validate minimum lead time (iOS requires at least 60 seconds)
        guard earliestOffsetSeconds >= 60 else {
            print("[DNP-FETCH] ERROR: earliestOffsetSeconds must be >= 60 (iOS requirement)")
            return false
        }
        
        // One Active Task Rule: Cancel any existing pending task for this notification
        if let notificationId = notificationId {
            cancelPendingTask(for: notificationId)
        }
        
        let request = BGAppRefreshTaskRequest(identifier: prefetchTaskIdentifier)
        request.earliestBeginDate = Date(timeIntervalSinceNow: earliestOffsetSeconds)
        
        do {
            try BGTaskScheduler.shared.submit(request)
            os_log("[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=%{public}@)", log: fetchLog, type: .info, String(describing: request.earliestBeginDate))
            return true
        } catch {
            // Handle simulator limitation (Code=1 is expected on simulator)
            if let nsError = error as NSError?, nsError.domain == "BGTaskSchedulerErrorDomain", nsError.code == 1 {
                os_log("[DNP-FETCH] BGTask scheduling failed on simulator (expected): Code=1 notPermitted", log: fetchLog, type: .default)
                print("[DNP-FETCH] NOTE: BGTaskScheduler doesn't work on simulator - this is expected. Use Xcode → Debug → Simulate Background Fetch for testing.")
                return false
            }
            os_log("[DNP-FETCH] Failed to schedule BGAppRefreshTask: %{public}@", log: fetchLog, type: .error, error.localizedDescription)
            print("[DNP-FETCH] Failed to schedule BGAppRefreshTask: \(error)")
            return false
        }
    }
    
    /// Cancel pending task for a specific notification (one active task rule)
    private static func cancelPendingTask(for notificationId: String) {
        // Get all pending tasks
        BGTaskScheduler.shared.getPendingTaskRequests { requests in
            for request in requests {
                if request.identifier == prefetchTaskIdentifier {
                    // In real implementation, check if this request matches the notificationId
                    // For now, cancel all prefetch tasks (Phase 1: single notification)
                    BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: prefetchTaskIdentifier)
                    os_log("[DNP-FETCH] Cancelled existing pending task for notificationId=%{public}@", log: fetchLog, type: .info, notificationId)
                }
            }
        }
    }
    
    /// Verify only one active task is pending (debug helper)
    static func verifyOneActiveTask() -> Bool {
        var pendingCount = 0
        let semaphore = DispatchSemaphore(value: 0)
        
        BGTaskScheduler.shared.getPendingTaskRequests { requests in
            pendingCount = requests.filter { $0.identifier == prefetchTaskIdentifier }.count
            semaphore.signal()
        }
        
        semaphore.wait()
        
        if pendingCount > 1 {
            os_log("[DNP-FETCH] WARNING: Multiple pending tasks detected (%d) - expected 1", log: fetchLog, type: .default, pendingCount)
            return false
        }
        
        return true
    }
    
    // MARK: - Time Warp Simulation (Testing)
    
    /// Simulate time warp for accelerated testing
    ///
    /// Useful to accelerate DST, T-Lead, and cache TTL tests without waiting in real time.
    /// - Parameter minutesForward: Number of minutes to advance simulated time
    static func simulateTimeWarp(minutesForward: Int) {
        let timeWarpOffset = TimeInterval(minutesForward * 60)
        // Store time warp offset in UserDefaults for test harness use
        UserDefaults.standard.set(timeWarpOffset, forKey: "DNP_TimeWarpOffset")
        print("[DNP-FETCH] Time warp simulated: +\(minutesForward) minutes")
    }
    
    /// Get current time with time warp applied (testing only)
    static func getWarpedTime() -> Date {
        let offset = UserDefaults.standard.double(forKey: "DNP_TimeWarpOffset")
        return Date().addingTimeInterval(offset)
    }
    
    // MARK: - Force Reschedule
    
    /// Force reschedule all BGTasks and notifications
    ///
    /// Forces re-registration of BGTasks and notifications.
    /// Useful when testing repeated failures or BGTask recovery behavior.
    static func forceRescheduleAll() {
        print("[DNP-FETCH] Force rescheduling all tasks and notifications")
        // Cancel existing tasks
        BGTaskScheduler.shared.cancelAllTaskRequests()
        // Re-register and reschedule
        registerBackgroundTasks()
        // Trigger reschedule logic (implementation-specific)
    }
    
    // MARK: - Handler
    
    /// Handle BGAppRefreshTask execution
    ///
    /// **Apple Best Practice Pattern:**
    /// 1. Schedule next task immediately (at start of execution)
    /// 2. Initiate async work
    /// 3. Mark task as complete
    /// 4. Use expiration handler to cancel if needed
    ///
    /// This is called by the system when the background task is launched.
    /// Replace PrefetchOperation with your actual prefetch logic.
    private static func handlePrefetchTask(task: BGAppRefreshTask) {
        os_log("[DNP-FETCH] BGTask handler invoked (task.identifier=%{public}@)", log: fetchLog, type: .info, task.identifier)
        
        // STEP 1: Schedule the next task IMMEDIATELY (Apple best practice)
        // This ensures continuity even if app is terminated shortly after
        // In real implementation, calculate next schedule based on notification time
        schedulePrefetchTask(earliestOffsetSeconds: 60 * 30) // 30 minutes later, for example
        
        // Define the work
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        
        let operation = PrefetchOperation()
        var taskCompleted = false
        
        // STEP 4: Set expiration handler (called if iOS terminates task early, ~30 seconds)
        task.expirationHandler = {
            os_log("[DNP-FETCH] Task expired - cancelling operations", log: fetchLog, type: .default)
            operation.cancel()
            
            // Ensure task is marked complete even on expiration
            if !taskCompleted {
                taskCompleted = true
                task.setTaskCompleted(success: false)
                os_log("[DNP-FETCH] Task marked complete (success=false) due to expiration", log: fetchLog, type: .info)
            }
        }
        
        // STEP 2: Initiate async work
        // STEP 3: Mark task as complete when done
        operation.completionBlock = {
            let success = !operation.isCancelled && !operation.isFailed
            
            // Ensure setTaskCompleted is called exactly once
            if !taskCompleted {
                taskCompleted = true
                task.setTaskCompleted(success: success)
                os_log("[DNP-FETCH] Task marked complete (success=%{public}@)", log: fetchLog, type: .info, success ? "true" : "false")
            } else {
                os_log("[DNP-FETCH] WARNING: Attempted to complete task twice", log: fetchLog, type: .default)
            }
        }
        
        queue.addOperation(operation)
    }
}

// MARK: - Prefetch Operation

/// Simple Operation example for testing
///
/// Replace this with your actual prefetch logic:
/// - HTTP fetch from TimeSafari API
/// - JWT signing
/// - ETag validation
/// - Content caching
/// - Error handling
class PrefetchOperation: Operation, @unchecked Sendable {
    
    var isFailed = false
    private static let fetchLog = OSLog(subsystem: "org.timesafari.dailynotification", category: "fetch")
    
    override func main() {
        if isCancelled { return }
        
        os_log("[DNP-FETCH] PrefetchOperation: starting fetch...", log: Self.fetchLog, type: .info)
        
        // Simulate some work
        // In real implementation, this would be:
        // - Make HTTP request
        // - Parse response
        // - Validate TTL and scheduled_for match notificationTime
        // - Cache content (ensure persisted before completion)
        // - Update database
        // - Handle errors (network, auth, etc.)
        
        // Simulate network delay
        Thread.sleep(forTimeInterval: 2)
        
        if isCancelled { return }
        
        // Simulate success/failure (in real code, check HTTP response)
        let success = true // Replace with actual fetch result
        
        if success {
            os_log("[DNP-FETCH] PrefetchOperation: fetch success", log: Self.fetchLog, type: .info)
            // In real implementation: persist cache here before completion
        } else {
            isFailed = true
            os_log("[DNP-FETCH] PrefetchOperation: fetch failed", log: Self.fetchLog, type: .error)
        }
    }
}

// MARK: - AppDelegate Integration Example

/*
 Example integration in AppDelegate.swift:
 
 import UIKit
 import BackgroundTasks
 
 @main
 class AppDelegate: UIResponder, UIApplicationDelegate {
     
     func application(
         _ application: UIApplication,
         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
     ) -> Bool {
         
         // Register background tasks BEFORE app finishes launching
         DailyNotificationBackgroundTaskTestHarness.registerBackgroundTasks()
         
         // Schedule initial task (for testing)
         DailyNotificationBackgroundTaskTestHarness.schedulePrefetchTask(earliestOffsetSeconds: 5 * 60) // 5 minutes
         
         return true
     }
 }
 */

// MARK: - Testing in Simulator

/*
 To test in simulator:
 
 1. Run app in Xcode
 2. Background the app (Home button / Cmd+Shift+H)
 3. In Xcode menu:
    - Debug → Simulate Background Fetch, or
    - Debug → Simulate Background Refresh
 4. Check console logs for [DNP-FETCH] messages
 
 Expected logs:
 - [DNP-FETCH] Registered BGTaskScheduler with id=...
 - [DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=...)
 - [DNP-FETCH] BGTask handler invoked (task.identifier=...)
 - [DNP-FETCH] PrefetchOperation: starting fake fetch...
 - [DNP-FETCH] PrefetchOperation: finished fake fetch.
 - [DNP-FETCH] Task completionBlock (success=true)
 */

// MARK: - Testing on Real Device

/*
 To test on real device:
 
 1. Install app on iPhone
 2. Enable Background App Refresh in Settings → [Your App]
 3. Schedule a notification with prefetch
 4. Lock device and leave idle (plugged in for best results)
 5. Monitor logs via:
    - Xcode → Devices & Simulators → device → open console
    - Or os_log aggregator / remote logging
 
 Note: Real device timing is heuristic, not deterministic.
 iOS will run the task when it determines it's appropriate,
 not necessarily at the exact earliestBeginDate.
 */

