//
//  DailyNotificationReactivationManagerTests.swift
//  DailyNotificationPluginTests
//
//  Created by Matthew Raymer on 2025-12-08
//  Copyright © 2025 TimeSafari. All rights reserved.
//

import XCTest
import UserNotifications
@testable import DailyNotificationPlugin

/**
 * Unit tests for DailyNotificationReactivationManager
 * 
 * Tests all recovery scenarios: cold start, termination, boot, warm start
 */
class DailyNotificationReactivationManagerTests: XCTestCase {
    
    var reactivationManager: DailyNotificationReactivationManager!
    var database: DailyNotificationDatabase!
    var storage: DailyNotificationStorage!
    var scheduler: DailyNotificationScheduler!
    var notificationCenter: UNUserNotificationCenter!
    
    override func setUp() {
        super.setUp()
        
        // Use real notification center for testing
        notificationCenter = UNUserNotificationCenter.current()
        
        // Create real instances with test database paths
        let testDbPath = NSTemporaryDirectory().appending("test_reactivation_db_\(UUID().uuidString).sqlite")
        database = DailyNotificationDatabase(path: testDbPath)
        storage = DailyNotificationStorage(databasePath: testDbPath)
        scheduler = DailyNotificationScheduler()
        
        // Create reactivation manager
        reactivationManager = DailyNotificationReactivationManager(
            database: database,
            storage: storage,
            scheduler: scheduler
        )
        
        // Clear UserDefaults for clean test state
        UserDefaults.standard.removeObject(forKey: "DNP_LAST_LAUNCH_TIME")
    }
    
    override func tearDown() {
        reactivationManager = nil
        database = nil
        storage = nil
        scheduler = nil
        notificationCenter = nil
        
        // Clean up UserDefaults
        UserDefaults.standard.removeObject(forKey: "DNP_LAST_LAUNCH_TIME")
        
        // Clean up test database files
        let fileManager = FileManager.default
        let tempDir = NSTemporaryDirectory()
        if let files = try? fileManager.contentsOfDirectory(atPath: tempDir) {
            for file in files where file.hasPrefix("test_reactivation_db") {
                try? fileManager.removeItem(atPath: tempDir.appending(file))
            }
        }
        
        super.tearDown()
    }
    
    // MARK: - Scenario Detection Tests
    
    func testDetectScenario_None_EmptyStorage() async throws {
        // Given: Empty storage (no notifications added)
        // Storage is already empty from setUp
        
        // When: Detect scenario
        let scenario = try await reactivationManager.detectScenario()
        
        // Then: Should return .none
        XCTAssertEqual(scenario, .none, "Empty storage should return .none scenario")
    }
    
    func testDetectScenario_ColdStart_Mismatch() async throws {
        // Given: Storage has notifications but notification center doesn't
        let notification1 = createTestNotification(id: "test-1", scheduledTime: futureTime())
        storage.saveNotificationContent(notification1)
        
        // Clear notification center
        notificationCenter.removeAllPendingNotificationRequests()
        
        // When: Detect scenario
        let scenario = try await reactivationManager.detectScenario()
        
        // Then: Should return .coldStart (or .termination if no pending)
        XCTAssertTrue(scenario == .coldStart || scenario == .termination, 
                     "Mismatch should return .coldStart or .termination")
    }
    
    func testDetectScenario_WarmStart_Match() async throws {
        // Given: Storage and notification center have matching notifications
        let notification1 = createTestNotification(id: "test-1", scheduledTime: futureTime())
        storage.saveNotificationContent(notification1)
        
        // Schedule notification in notification center
        let content = UNMutableNotificationContent()
        content.title = notification1.title ?? "Test"
        content.body = notification1.body ?? "Test"
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
        let request = UNNotificationRequest(identifier: notification1.id, content: content, trigger: trigger)
        
        try await notificationCenter.add(request)
        
        // When: Detect scenario
        let scenario = try await reactivationManager.detectScenario()
        
        // Then: Should return .warmStart
        XCTAssertEqual(scenario, .warmStart, "Matching notifications should return .warmStart")
        
        // Cleanup
        notificationCenter.removeAllPendingNotificationRequests()
    }
    
    func testDetectScenario_Termination_NoPending() async throws {
        // Given: Storage has notifications but notification center is empty
        let notification1 = createTestNotification(id: "test-1", scheduledTime: futureTime())
        storage.saveNotificationContent(notification1)
        
        // Clear notification center
        notificationCenter.removeAllPendingNotificationRequests()
        
        // When: Detect scenario
        let scenario = try await reactivationManager.detectScenario()
        
        // Then: Should return .termination
        XCTAssertEqual(scenario, .termination, "No pending notifications with storage should return .termination")
    }
    
    // MARK: - Boot Detection Tests
    
    func testDetectBootScenario_FirstLaunch_ReturnsFalse() {
        // Given: No last launch time (first launch)
        UserDefaults.standard.removeObject(forKey: "DNP_LAST_LAUNCH_TIME")
        
        // When: Detect boot scenario
        let isBoot = reactivationManager.detectBootScenario()
        
        // Then: Should return false
        XCTAssertFalse(isBoot, "First launch should not be detected as boot")
    }
    
    func testDetectBootScenario_RecentLaunch_ReturnsFalse() {
        // Given: Recent launch time (not a boot)
        let recentTime = Date().timeIntervalSince1970 - 300 // 5 minutes ago
        UserDefaults.standard.set(recentTime, forKey: "DNP_LAST_LAUNCH_TIME")
        
        // When: Detect boot scenario
        let isBoot = reactivationManager.detectBootScenario()
        
        // Then: Should return false
        XCTAssertFalse(isBoot, "Recent launch should not be detected as boot")
    }
    
    func testDetectBootScenario_BootDetected_ReturnsTrue() {
        // Given: Last launch time is far in past (simulating boot)
        let oldTime = Date().timeIntervalSince1970 - 3600 // 1 hour ago
        UserDefaults.standard.set(oldTime, forKey: "DNP_LAST_LAUNCH_TIME")
        
        // Mock system uptime to be less than time since last launch
        // Note: This is a simplified test - in real scenario, ProcessInfo.systemUptime would be small after boot
        
        // When: Detect boot scenario
        // Since we can't easily mock ProcessInfo.systemUptime, we'll test the logic
        // by checking if the method handles the case correctly
        let isBoot = reactivationManager.detectBootScenario()
        
        // Then: May return true if system uptime is actually small (real device/simulator state)
        // This test verifies the method doesn't crash
        XCTAssertNotNil(isBoot, "Boot detection should not crash")
    }
    
    // MARK: - Missed Notification Detection Tests
    
    func testDetectMissedNotifications_PastScheduledTime() async throws {
        // Given: Notification with past scheduled time
        let pastTime = Int64(Date().timeIntervalSince1970 * 1000) - 3600000 // 1 hour ago
        let notification = createTestNotification(id: "missed-1", scheduledTime: pastTime)
        storage.saveNotificationContent(notification)
        
        // When: Detect missed notifications
        let missed = try await reactivationManager.detectMissedNotifications(currentTime: Date())
        
        // Then: Should detect the missed notification
        XCTAssertEqual(missed.count, 1, "Should detect 1 missed notification")
        XCTAssertEqual(missed.first?.id, "missed-1", "Should detect correct notification")
    }
    
    func testDetectMissedNotifications_FutureScheduledTime() async throws {
        // Given: Notification with future scheduled time
        let futureTime = Int64(Date().timeIntervalSince1970 * 1000) + 3600000 // 1 hour from now
        let notification = createTestNotification(id: "future-1", scheduledTime: futureTime)
        storage.saveNotificationContent(notification)
        
        // When: Detect missed notifications
        let missed = try await reactivationManager.detectMissedNotifications(currentTime: Date())
        
        // Then: Should not detect as missed
        XCTAssertEqual(missed.count, 0, "Should not detect future notifications as missed")
    }
    
    func testDetectMissedNotifications_MixedTimes() async throws {
        // Given: Mix of past and future notifications
        let pastTime = Int64(Date().timeIntervalSince1970 * 1000) - 3600000
        let futureTime = Int64(Date().timeIntervalSince1970 * 1000) + 3600000
        
        let pastNotification = createTestNotification(id: "past-1", scheduledTime: pastTime)
        let futureNotification = createTestNotification(id: "future-1", scheduledTime: futureTime)
        
        storage.saveNotificationContent(pastNotification)
        storage.saveNotificationContent(futureNotification)
        
        // When: Detect missed notifications
        let missed = try await reactivationManager.detectMissedNotifications(currentTime: Date())
        
        // Then: Should only detect past notification
        XCTAssertEqual(missed.count, 1, "Should detect only past notification")
        XCTAssertEqual(missed.first?.id, "past-1", "Should detect correct notification")
    }
    
    // MARK: - Future Notification Verification Tests
    
    func testVerifyFutureNotifications_AllScheduled() async throws {
        // Given: Future notifications in storage and notification center
        let futureTime = Int64(Date().timeIntervalSince1970 * 1000) + 3600000
        let notification = createTestNotification(id: "future-1", scheduledTime: futureTime)
        storage.saveNotificationContent(notification)
        
        // Schedule in notification center
        let content = UNMutableNotificationContent()
        content.title = notification.title ?? "Test"
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3600, repeats: false)
        let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
        try await notificationCenter.add(request)
        
        // When: Verify future notifications
        let result = try await reactivationManager.verifyFutureNotifications()
        
        // Then: Should verify all are scheduled
        XCTAssertEqual(result.totalSchedules, 1, "Should have 1 future schedule")
        XCTAssertEqual(result.notificationsFound, 1, "Should find 1 scheduled notification")
        XCTAssertEqual(result.notificationsMissing, 0, "Should have 0 missing notifications")
        
        // Cleanup
        notificationCenter.removeAllPendingNotificationRequests()
    }
    
    func testVerifyFutureNotifications_SomeMissing() async throws {
        // Given: Future notifications in storage but not all in notification center
        let futureTime = Int64(Date().timeIntervalSince1970 * 1000) + 3600000
        let notification1 = createTestNotification(id: "future-1", scheduledTime: futureTime)
        let notification2 = createTestNotification(id: "future-2", scheduledTime: futureTime + 3600000)
        storage.saveNotificationContent(notification1)
        storage.saveNotificationContent(notification2)
        
        // Only schedule one in notification center
        let content = UNMutableNotificationContent()
        content.title = notification1.title ?? "Test"
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3600, repeats: false)
        let request = UNNotificationRequest(identifier: notification1.id, content: content, trigger: trigger)
        try await notificationCenter.add(request)
        
        // When: Verify future notifications
        let result = try await reactivationManager.verifyFutureNotifications()
        
        // Then: Should detect missing notification
        XCTAssertEqual(result.totalSchedules, 2, "Should have 2 future schedules")
        XCTAssertEqual(result.notificationsFound, 1, "Should find 1 scheduled notification")
        XCTAssertEqual(result.notificationsMissing, 1, "Should have 1 missing notification")
        XCTAssertTrue(result.missingIds.contains("future-2"), "Should identify missing notification")
        
        // Cleanup
        notificationCenter.removeAllPendingNotificationRequests()
    }
    
    // MARK: - Recovery Result Tests
    
    func testRecoveryResult_Initialization() {
        // Given: Recovery result data
        let result = RecoveryResult(
            missedCount: 2,
            rescheduledCount: 3,
            verifiedCount: 5,
            errors: 1
        )
        
        // Then: Should have correct values
        XCTAssertEqual(result.missedCount, 2)
        XCTAssertEqual(result.rescheduledCount, 3)
        XCTAssertEqual(result.verifiedCount, 5)
        XCTAssertEqual(result.errors, 1)
    }
    
    func testVerificationResult_Initialization() {
        // Given: Verification result data
        let result = VerificationResult(
            totalSchedules: 10,
            notificationsFound: 8,
            notificationsMissing: 2,
            missingIds: ["id-1", "id-2"]
        )
        
        // Then: Should have correct values
        XCTAssertEqual(result.totalSchedules, 10)
        XCTAssertEqual(result.notificationsFound, 8)
        XCTAssertEqual(result.notificationsMissing, 2)
        XCTAssertEqual(result.missingIds.count, 2)
    }
    
    // MARK: - Helper Methods
    
    private func createTestNotification(id: String, scheduledTime: Int64) -> NotificationContent {
        return NotificationContent(
            id: id,
            title: "Test Notification",
            body: "Test body",
            scheduledTime: scheduledTime,
            fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
            url: nil,
            payload: nil,
            etag: nil
        )
    }
    
    private func futureTime() -> Int64 {
        return Int64(Date().timeIntervalSince1970 * 1000) + 3600000 // 1 hour from now
    }
}

// MARK: - Mock Classes

// Note: We use real instances of DailyNotificationDatabase, DailyNotificationStorage, and DailyNotificationScheduler
// with test database paths for testing. This provides more realistic testing while still being isolated.

// Note: Methods are now internal in ReactivationManager, so they can be tested directly

