package org.timesafari.dailynotification

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import java.util.concurrent.TimeUnit

/**
 * Manages recovery of alarms and notifications on app launch
 * Phase 1: Cold start recovery only
 * Phase 2: Force stop detection and recovery
 * 
 * Implements: 
 * - [Plugin Requirements §3.1.2 - App Cold Start](../../../../../../../doc/alarms/03-plugin-requirements.md#312-app-cold-start)
 * - [Plugin Requirements §3.1.4 - Force Stop Recovery](../../../../../../../doc/alarms/03-plugin-requirements.md#314-force-stop-recovery-android-only)
 * Platform Reference: [Android §2.1.4](../../../../../../../doc/alarms/01-platform-capability-reference.md#214-alarms-can-be-restored-after-app-restart)
 * 
 * @author Matthew Raymer
 * @version 2.0.0 - Phase 2: Force stop detection
 */
class ReactivationManager(private val context: Context) {
    
    /**
     * Recovery scenario enum
     * 
     * Defines the different scenarios that trigger recovery
     */
    enum class RecoveryScenario {
        COLD_START,    // Process killed, alarms may or may not exist
        FORCE_STOP,    // Alarms cleared, DB still populated
        BOOT,          // Device reboot
        NONE           // No recovery required (warm resume or first launch)
    }
    
    companion object {
        private const val TAG = "DNP-REACTIVATION"
        private const val RECOVERY_TIMEOUT_SECONDS = 2L

        /**
         * Load persisted title/body for a schedule from NotificationContentEntity (post-reboot recovery).
         * Tries schedule.id then "daily_${schedule.id}" to match NotifyReceiver/ScheduleHelper id convention.
         * Internal so BootReceiver can use when rescheduling after boot.
         */
        internal fun getTitleBodyForSchedule(db: DailyNotificationDatabase, schedule: Schedule): Pair<String, String>? {
            val entity = try {
                db.notificationContentDao().getNotificationById(schedule.id)
            } catch (_: Exception) {
                null
            } ?: try {
                db.notificationContentDao().getNotificationById("daily_${schedule.id}")
            } catch (_: Exception) {
                null
            } ?: return null
            val t = entity.title?.takeIf { it.isNotBlank() } ?: return null
            val b = entity.body?.takeIf { it.isNotBlank() } ?: return null
            return Pair(t, b)
        }
        
        /**
         * Run boot-time recovery
         * 
         * Phase 3: Boot recovery that restores alarms after device reboot
         * 
         * Implements: [Plugin Requirements §3.1.1 - Boot Event](../../../../../../../doc/alarms/03-plugin-requirements.md#311-boot-event-android-only)
         * 
         * This method is called from BootReceiver when BOOT_COMPLETED is received.
         * It runs asynchronously with timeout protection to avoid blocking boot.
         * 
         * Recovery steps:
         * 1. Load all schedules from database
         * 2. For each schedule:
         *    - If next run time is in future: reschedule alarm
         *    - If next run time is in past: mark as missed, schedule next occurrence
         * 3. Log summary with counts
         * 
         * @param context Application context
         */
        fun runBootRecovery(context: Context) {
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    withTimeout(TimeUnit.SECONDS.toMillis(RECOVERY_TIMEOUT_SECONDS)) {
                        Log.i(TAG, "Starting boot recovery")
                        
                        val db = DailyNotificationDatabase.getDatabase(context)
                        val dbStartTime = System.currentTimeMillis()
                        val enabledSchedules = try {
                            db.scheduleDao().getEnabled()
                        } catch (e: Exception) {
                            Log.e(TAG, "Failed to load schedules from DB", e)
                            emptyList()
                        }
                        val dbDuration = System.currentTimeMillis() - dbStartTime
                        if (dbDuration > 100) {
                            Log.w(TAG, "Database query slow: ${dbDuration}ms for getEnabled()")
                        } else {
                            Log.d(TAG, "Database query: ${dbDuration}ms, schedules=${enabledSchedules.size}")
                        }
                        
                        if (enabledSchedules.isEmpty()) {
                            Log.i(TAG, "BOOT: No schedules found")
                            return@withTimeout
                        }
                        
                        Log.i(TAG, "Loaded ${enabledSchedules.size} schedules from DB")
                        
                        val currentTime = System.currentTimeMillis()
                        var missedCount = 0
                        var rescheduledCount = 0
                        var errors = 0
                        
                        enabledSchedules.forEach { schedule ->
                            try {
                                when (schedule.kind) {
                                    "notify", "static_reminder" -> {
                                        val nextRunTime = calculateNextRunTimeForSchedule(schedule, currentTime)

                                        if (nextRunTime < currentTime) {
                                            // Past alarm - mark as missed and schedule next occurrence
                                            markMissedNotificationForSchedule(schedule, nextRunTime, db)
                                            missedCount++

                                            // Schedule next occurrence (use rollover interval if set, else 24h)
                                            val nextOccurrence = calculateNextOccurrenceForSchedule(schedule, nextRunTime, currentTime)
                                            rescheduleAlarmForBoot(context, schedule, nextOccurrence, db)
                                            rescheduledCount++

                                            Log.i(TAG, "Marked missed notification: ${schedule.id}")
                                            Log.i(TAG, "Rescheduled alarm: ${schedule.id} for $nextOccurrence")
                                        } else {
                                            // Future alarm - reschedule immediately
                                            rescheduleAlarmForBoot(context, schedule, nextRunTime, db)
                                            rescheduledCount++

                                            Log.i(TAG, "Rescheduled alarm: ${schedule.id} for $nextRunTime")
                                        }
                                    }
                                    "fetch" -> {
                                        // Reschedule fetch work (deferred to Phase 2/3)
                                        // For now, just log
                                        Log.d(TAG, "Fetch schedule ${schedule.id} will be rescheduled by WorkManager")
                                    }
                                    else -> {
                                        Log.w(TAG, "Unknown schedule kind: ${schedule.kind}")
                                    }
                                }
                            } catch (e: Exception) {
                                errors++
                                Log.e(TAG, "Failed to recover schedule ${schedule.id}", e)
                            }
                        }

                        if (enabledSchedules.any { it.id.startsWith("dual_fetch_") }) {
                            if (DualScheduleFetchRecovery.enqueueFromPersistedConfig(context)) {
                                Log.i(TAG, "Dual prefetch WorkManager re-enqueued from persisted config")
                            }
                        }
                        
                        // Record recovery in history
                        val result = RecoveryResult(
                            missedCount = missedCount,
                            rescheduledCount = rescheduledCount,
                            verifiedCount = 0, // Boot recovery doesn't verify
                            errors = errors
                        )
                        
                        recordRecoveryHistoryForBoot(db, result)
                        
                        Log.i(TAG, "Boot recovery complete: $result")
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "Boot recovery failed (non-fatal): ${e.message}", e)
                }
            }
        }
        
        private fun calculateNextRunTimeForSchedule(schedule: Schedule, currentTime: Long): Long {
            return when {
                schedule.nextRunAt != null -> schedule.nextRunAt
                schedule.cron != null -> {
                    // Parse cron expression: "minute hour * * *" (daily schedule)
                    try {
                        val parts = schedule.cron.trim().split("\\s+".toRegex())
                        if (parts.size >= 2) {
                            val minute = parts[0].toIntOrNull() ?: 0
                            val hour = parts[1].toIntOrNull() ?: 9
                            
                            val calendar = java.util.Calendar.getInstance()
                            calendar.set(java.util.Calendar.HOUR_OF_DAY, hour)
                            calendar.set(java.util.Calendar.MINUTE, minute)
                            calendar.set(java.util.Calendar.SECOND, 0)
                            calendar.set(java.util.Calendar.MILLISECOND, 0)
                            
                            var nextRun = calendar.timeInMillis
                            if (nextRun <= currentTime) {
                                calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
                                nextRun = calendar.timeInMillis
                            }
                            return nextRun
                        }
                    } catch (e: Exception) {
                        Log.w(TAG, "Failed to parse cron: ${schedule.cron}", e)
                    }
                    // Fallback: next day at 9 AM
                    currentTime + (24 * 60 * 60 * 1000L)
                }
                schedule.clockTime != null -> {
                    // Parse HH:mm time string
                    try {
                        val parts = schedule.clockTime.split(":")
                        if (parts.size == 2) {
                            val hour = parts[0].toIntOrNull() ?: 9
                            val minute = parts[1].toIntOrNull() ?: 0
                            
                            val calendar = java.util.Calendar.getInstance()
                            calendar.set(java.util.Calendar.HOUR_OF_DAY, hour)
                            calendar.set(java.util.Calendar.MINUTE, minute)
                            calendar.set(java.util.Calendar.SECOND, 0)
                            calendar.set(java.util.Calendar.MILLISECOND, 0)
                            
                            var nextRun = calendar.timeInMillis
                            if (nextRun <= currentTime) {
                                calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
                                nextRun = calendar.timeInMillis
                            }
                            return nextRun
                        }
                    } catch (e: Exception) {
                        Log.w(TAG, "Failed to parse clockTime: ${schedule.clockTime}", e)
                    }
                    // Fallback: next day at 9 AM
                    currentTime + (24 * 60 * 60 * 1000L)
                }
                else -> {
                    // Default: next day at 9 AM
                    currentTime + (24 * 60 * 60 * 1000L)
                }
            }
        }
        
        private fun calculateNextOccurrence(fromTime: Long): Long {
            return fromTime + (24 * 60 * 60 * 1000L)
        }

        /**
         * Next occurrence from a given trigger time. Uses schedule.rolloverIntervalMinutes when set and > 0 (dev/testing), else 24h.
         * Advances until result > currentTime so we don't reschedule in the past.
         */
        private fun calculateNextOccurrenceForSchedule(schedule: Schedule, fromTime: Long, currentTime: Long): Long {
            val intervalMs = when {
                schedule.rolloverIntervalMinutes != null && schedule.rolloverIntervalMinutes!! > 0 ->
                    schedule.rolloverIntervalMinutes!! * 60 * 1000L
                else -> 24 * 60 * 60 * 1000L
            }
            var next = fromTime + intervalMs
            while (next < currentTime) {
                next += intervalMs
            }
            return next
        }
        
        private suspend fun markMissedNotificationForSchedule(
            schedule: Schedule,
            scheduledTime: Long,
            db: DailyNotificationDatabase
        ) {
            try {
                // Find or create NotificationContentEntity for this schedule
                val notificationId = "daily_${schedule.id}"
                val existing = try {
                    db.notificationContentDao().getNotificationById(notificationId)
                } catch (e: Exception) {
                    null
                }
                
                if (existing != null) {
                    existing.deliveryStatus = "missed"
                    existing.lastDeliveryAttempt = System.currentTimeMillis()
                    existing.deliveryAttempts = existing.deliveryAttempts + 1
                    existing.updatedAt = System.currentTimeMillis()
                    db.notificationContentDao().updateNotification(existing)
                } else {
                    // Create new notification content entry for missed alarm
                    val notification = org.timesafari.dailynotification.entities.NotificationContentEntity(
                        notificationId,
                        "2.1.0", // Plugin version
                        null, // timesafariDid
                        "daily", // notificationType
                        "Daily Notification",
                        "Your daily update is ready",
                        scheduledTime,
                        java.time.ZoneId.systemDefault().id
                    )
                    notification.deliveryStatus = "missed"
                    notification.lastDeliveryAttempt = System.currentTimeMillis()
                    notification.deliveryAttempts = 1
                    notification.createdAt = System.currentTimeMillis()
                    notification.updatedAt = System.currentTimeMillis()
                    db.notificationContentDao().insertNotification(notification)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Failed to mark missed notification for schedule ${schedule.id}", e)
                throw e
            }
        }
        
        private suspend fun rescheduleAlarmForBoot(
            context: Context,
            schedule: Schedule,
            nextRunTime: Long,
            db: DailyNotificationDatabase
        ) {
            try {
                val (title, body) = getTitleBodyForSchedule(db, schedule)
                    ?: Pair("Daily Notification", "Your daily update is ready")
                val config = UserNotificationConfig(
                    enabled = schedule.enabled,
                    schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *",
                    title = title,
                    body = body,
                    sound = true,
                    vibration = true,
                    priority = "normal"
                )
                
                NotifyReceiver.scheduleExactNotification(
                    context,
                    nextRunTime,
                    config,
                    scheduleId = schedule.id,
                    source = ScheduleSource.BOOT_RECOVERY,
                    skipPendingIntentIdempotence = true
                )
                
                // Update schedule in database (best effort)
                try {
                    db.scheduleDao().updateRunTimes(schedule.id, schedule.lastRunAt, nextRunTime)
                } catch (e: Exception) {
                    Log.w(TAG, "Failed to update schedule in database: ${schedule.id}", e)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Failed to reschedule alarm for schedule ${schedule.id}", e)
                throw e
            }
        }
        
        private suspend fun recordRecoveryHistoryForBoot(
            db: DailyNotificationDatabase,
            result: RecoveryResult
        ) {
            try {
                db.historyDao().insert(
                    History(
                        refId = "boot_recovery_${System.currentTimeMillis()}",
                        kind = "boot_recovery",
                        occurredAt = System.currentTimeMillis(),
                        outcome = if (result.errors == 0) "success" else "partial",
                        diagJson = """
                            {
                                "scenario": "BOOT",
                                "missed_count": ${result.missedCount},
                                "rescheduled_count": ${result.rescheduledCount},
                                "verified_count": ${result.verifiedCount},
                                "errors": ${result.errors}
                            }
                        """.trimIndent()
                    )
                )
            } catch (e: Exception) {
                Log.w(TAG, "Failed to record boot recovery history (non-fatal)", e)
            }
        }
        
        private data class RecoveryResult(
            val missedCount: Int,
            val rescheduledCount: Int,
            val verifiedCount: Int,
            val errors: Int
        ) {
            override fun toString(): String {
                return "missed=$missedCount, rescheduled=$rescheduledCount, verified=$verifiedCount, errors=$errors"
            }
        }
    }
    
    /**
     * Detect recovery scenario
     * 
     * Phase 2: Determines which recovery scenario applies based on:
     * - Database state (empty vs populated)
     * - AlarmManager state (alarms exist vs cleared)
     * - Boot recovery flag (set by BootReceiver)
     * 
     * @return RecoveryScenario enum value
     */
    private suspend fun detectScenario(): RecoveryScenario {
        val db = DailyNotificationDatabase.getDatabase(context)
        val dbSchedules = db.scheduleDao().getEnabled()
        
        // Check for first launch (empty DB)
        if (dbSchedules.isEmpty()) {
            Log.d(TAG, "No schedules in database - first launch (NONE)")
            return RecoveryScenario.NONE
        }
        
        // Check alarm state first (needed for both force stop and boot detection)
        val alarmsExist = alarmsExist()
        
        // When alarms don't exist, distinguish between boot and force stop
        if (!alarmsExist) {
            // If boot flag is set AND recent (within 60s), it's a boot scenario
            // Otherwise, it's force stop (boot flag might be stale or from package replacement)
            if (isBootRecovery()) {
                Log.i(TAG, "Boot recovery detected: DB has ${dbSchedules.size} schedules, no alarms exist, boot flag set")
                return RecoveryScenario.BOOT
            } else {
                Log.i(TAG, "Force stop detected: DB has ${dbSchedules.size} schedules, but no alarms exist")
                return RecoveryScenario.FORCE_STOP
            }
        }
        
        // Alarms exist - check for boot recovery (unlikely but possible if boot recovery already ran)
        if (isBootRecovery()) {
            Log.i(TAG, "Boot recovery detected (alarms exist - boot recovery may have already run)")
            return RecoveryScenario.BOOT
        }
        
        // Normal cold start: DB has schedules and alarms exist
        // (Alarms may have fired or may be future alarms - need to verify/resync)
        Log.i(TAG, "Cold start detected: DB has ${dbSchedules.size} schedules, alarms exist")
        return RecoveryScenario.COLD_START
    }
    
    /**
     * Check if this is a boot recovery scenario
     * 
     * BootReceiver sets a flag in SharedPreferences when boot completes.
     * This allows ReactivationManager to detect boot scenario.
     * 
     * @return true if boot recovery, false otherwise
     */
    private fun isBootRecovery(): Boolean {
        val prefs = context.getSharedPreferences("dailynotification_recovery", Context.MODE_PRIVATE)
        val lastBootAt = prefs.getLong("last_boot_at", 0)
        val currentTime = System.currentTimeMillis()
        
        if (lastBootAt <= 0) {
            return false
        }
        
        val ageMs = currentTime - lastBootAt
        
        // Boot flag must be at least 1 second old to avoid emulator quirks
        // where ACTION_BOOT_COMPLETED fires during app install/launch
        // Boot flag is valid for 60 seconds after boot
        // This prevents false positives from stale flags
        if (ageMs >= 1000 && ageMs < 60000) {
            // Clear the flag after reading
            prefs.edit().remove("last_boot_at").apply()
            Log.d(TAG, "Boot flag valid: age=${ageMs}ms")
            return true
        }
        
        if (ageMs > 0 && ageMs < 1000) {
            Log.d(TAG, "Boot flag too recent (${ageMs}ms) - likely emulator quirk, ignoring")
            // Don't clear the flag - let it age naturally
        }
        
        return false
    }
    
    /**
     * Check if alarms exist in AlarmManager
     * 
     * Uses PendingIntent check to determine if any alarms are scheduled.
     * This is more reliable than checking nextAlarmClock.
     * 
     * @return true if at least one alarm exists, false otherwise
     */
    private fun alarmsExist(): Boolean {
        return try {
            // Check if any PendingIntent for our receiver exists (must match NotifyReceiver schedule path)
            val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
                setPackage(context.packageName)
                action = "org.timesafari.daily.NOTIFICATION"
            }
            val pendingIntent = PendingIntent.getBroadcast(
                context,
                0, // Use 0 to check for any alarm
                intent,
                PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
            )
            val exists = pendingIntent != null
            Log.d(TAG, "Alarm check: alarms exist = $exists")
            exists
        } catch (e: Exception) {
            Log.e(TAG, "Error checking if alarms exist", e)
            // On error, assume no alarms (conservative for force stop detection)
            false
        }
    }
    
    /**
     * Perform recovery on app launch
     * Phase 2: Adds scenario detection and force stop handling
     * 
     * Detects recovery scenario and routes to appropriate recovery method:
     * - FORCE_STOP: All alarms cleared, reschedule all
     * - COLD_START: Alarms may exist, verify and reschedule missing
     * - BOOT: Device reboot, same as force stop
     * - NONE: No recovery needed (first launch or warm resume)
     * 
     * Runs asynchronously with timeout to avoid blocking app startup
     * 
     * Rollback Safety: If recovery fails, app continues normally
     */
    fun performRecovery() {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                withTimeout(TimeUnit.SECONDS.toMillis(RECOVERY_TIMEOUT_SECONDS)) {
                    Log.i(TAG, "Starting app launch recovery (Phase 2)")
                    
                    // Step 1: Detect scenario
                    val scenario = detectScenario()
                    Log.i(TAG, "Detected scenario: $scenario")
                    
                    // Step 2: Handle based on scenario
                    when (scenario) {
                        RecoveryScenario.FORCE_STOP -> {
                            // Phase 2: Force stop recovery (new in this phase)
                            val result = performForceStopRecovery()
                            Log.i(TAG, "Force stop recovery completed: $result")
                        }
                        RecoveryScenario.COLD_START -> {
                            // Phase 1: Cold start recovery (reuse existing implementation)
                            val result = performColdStartRecovery()
                            Log.i(TAG, "Cold start recovery completed: $result")
                        }
                        RecoveryScenario.BOOT -> {
                            // Phase 2: Boot recovery (uses same logic as force stop)
                            // Boot recovery uses same logic as force stop (all alarms wiped)
                            val result = performForceStopRecovery()
                            Log.i(TAG, "Boot recovery completed: $result")
                        }
                        RecoveryScenario.NONE -> {
                            // No recovery needed (warm resume or first launch)
                            Log.d(TAG, "No recovery needed (NONE scenario)")
                        }
                    }
                    
                    Log.i(TAG, "App launch recovery completed")
                }
            } catch (e: Exception) {
                // Rollback: Log error but don't crash
                Log.e(TAG, "Recovery failed (non-fatal): ${e.message}", e)
                // Record failure in history (best effort, don't fail if this fails)
                try {
                    recordRecoveryFailure(e)
                } catch (historyError: Exception) {
                    Log.w(TAG, "Failed to record recovery failure in history", historyError)
                }
            }
        }
    }
    
    /**
     * Perform cold start recovery
     * 
     * Steps:
     * 1. Detect missed notifications (scheduled_time < now, not delivered)
     * 2. Mark missed notifications in database
     * 3. Verify future alarms are scheduled
     * 4. Reschedule missing future alarms
     * 
     * @return RecoveryResult with counts
     */
    private suspend fun performColdStartRecovery(): RecoveryResult {
        val startTime = System.currentTimeMillis()
        val db = DailyNotificationDatabase.getDatabase(context)
        val currentTime = System.currentTimeMillis()
        
        Log.i(TAG, "Cold start recovery: checking for missed notifications")
        
        // Step 1: Detect missed notifications
        val missedNotifications = try {
            db.notificationContentDao().getNotificationsReadyForDelivery(currentTime)
                .filter { it.deliveryStatus != "delivered" }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to query missed notifications", e)
            emptyList()
        }
        
        var missedCount = 0
        var missedErrors = 0
        
        // Step 2: Mark missed notifications
        missedNotifications.forEach { notification ->
            try {
                // Data integrity check: verify notification is valid
                if (notification.id.isBlank()) {
                    Log.w(TAG, "Skipping invalid notification: empty ID")
                    return@forEach
                }
                
                // Update delivery status
                notification.deliveryStatus = "missed"
                notification.lastDeliveryAttempt = currentTime
                notification.deliveryAttempts = notification.deliveryAttempts + 1
                notification.updatedAt = currentTime
                
                db.notificationContentDao().updateNotification(notification)
                missedCount++
                
                Log.d(TAG, "Marked missed notification: ${notification.id}")
            } catch (e: Exception) {
                missedErrors++
                Log.e(TAG, "Failed to mark missed notification: ${notification.id}", e)
                // Continue processing other notifications
            }
        }
        
        // Step 3: Verify and reschedule future alarms
        val schedules = try {
            db.scheduleDao().getEnabled()
                .filter { it.kind == "notify" || it.kind == "static_reminder" }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to query schedules", e)
            emptyList()
        }
        
        var rescheduledCount = 0
        var verifiedCount = 0
        var rescheduleErrors = 0
        
        schedules.forEach { schedule ->
            try {
                // Data integrity check: verify schedule is valid
                if (schedule.id.isBlank() || schedule.nextRunAt == null) {
                    Log.w(TAG, "Skipping invalid schedule: ${schedule.id}")
                    return@forEach
                }
                
                val nextRunTime = schedule.nextRunAt
                
                // Only check future alarms
                if (nextRunTime >= currentTime) {
                    // Verify alarm is scheduled
                    val isScheduled = NotifyReceiver.isAlarmScheduled(context, scheduleId = schedule.id, triggerAtMillis = nextRunTime)
                    
                    if (isScheduled) {
                        verifiedCount++
                        Log.d(TAG, "Verified scheduled alarm: ${schedule.id} at $nextRunTime")
                    } else {
                        // Reschedule missing alarm
                        rescheduleAlarm(schedule, nextRunTime, db)
                        rescheduledCount++
                        Log.i(TAG, "Rescheduled missing alarm: ${schedule.id} at $nextRunTime")
                    }
                }
            } catch (e: Exception) {
                rescheduleErrors++
                Log.e(TAG, "Failed to verify/reschedule: ${schedule.id}", e)
                // Continue processing other schedules
            }
        }
        
        // Step 4: Record recovery in history
        val result = RecoveryResult(
            missedCount = missedCount,
            rescheduledCount = rescheduledCount,
            verifiedCount = verifiedCount,
            errors = missedErrors + rescheduleErrors
        )
        
        recordRecoveryHistory(db, "cold_start", result)
        
        val duration = System.currentTimeMillis() - startTime
        Log.i(TAG, "Cold start recovery completed: duration=${duration}ms, missed=$missedCount, rescheduled=$rescheduledCount, verified=$verifiedCount, errors=${missedErrors + rescheduleErrors}")
        return result
    }
    
    /**
     * Data class for schedule recovery results
     */
    private data class ScheduleRecoveryResult(
        val missedCount: Int = 0,
        val rescheduledCount: Int = 0,
        val errors: Int = 0
    )
    
    /**
     * Perform force stop recovery
     * 
     * Force stop scenario: ALL alarms were cancelled by OS
     * Need to:
     * 1. Mark all past alarms as missed
     * 2. Reschedule all future alarms
     * 3. Handle both notify and fetch schedules
     * 
     * @return RecoveryResult with counts
     */
    private suspend fun performForceStopRecovery(): RecoveryResult {
        val startTime = System.currentTimeMillis()
        val db = DailyNotificationDatabase.getDatabase(context)
        val currentTime = System.currentTimeMillis()
        
        Log.i(TAG, "Force stop recovery: recovering all schedules")
        
        val dbSchedules = try {
            db.scheduleDao().getEnabled()
        } catch (e: Exception) {
            Log.e(TAG, "Failed to query schedules", e)
            return RecoveryResult(0, 0, 0, 1)
        }
        
        var missedCount = 0
        var rescheduledCount = 0
        var errors = 0
        
        dbSchedules.forEach { schedule ->
            try {
                // Data integrity check: verify schedule is valid
                if (schedule.id.isBlank() || schedule.nextRunAt == null) {
                    Log.w(TAG, "Skipping invalid schedule: ${schedule.id}")
                    return@forEach
                }
                
                when (schedule.kind) {
                    "notify", "static_reminder" -> {
                        val result = recoverNotifySchedule(schedule, currentTime, db)
                        missedCount += result.missedCount
                        rescheduledCount += result.rescheduledCount
                        errors += result.errors
                    }
                    "fetch" -> {
                        val result = recoverFetchSchedule(schedule, currentTime, db)
                        rescheduledCount += result.rescheduledCount
                        errors += result.errors
                    }
                    else -> {
                        Log.w(TAG, "Unknown schedule kind: ${schedule.kind}")
                    }
                }
            } catch (e: Exception) {
                errors++
                Log.e(TAG, "Failed to recover schedule: ${schedule.id}", e)
            }
        }
        
        val result = RecoveryResult(
            missedCount = missedCount,
            rescheduledCount = rescheduledCount,
            verifiedCount = 0, // Not applicable for force stop
            errors = errors
        )
        
        recordRecoveryHistory(db, "force_stop", result)
        
        val duration = System.currentTimeMillis() - startTime
        Log.i(TAG, "Force stop recovery completed: duration=${duration}ms, missed=$missedCount, rescheduled=$rescheduledCount, errors=$errors")
        return result
    }
    
    /**
     * Recover a notify schedule after force stop
     * 
     * Handles notify schedules (kind == "notify")
     * 
     * @param schedule Schedule to recover
     * @param currentTime Current time in milliseconds
     * @param db Database instance
     * @return ScheduleRecoveryResult
     */
    private suspend fun recoverNotifySchedule(
        schedule: Schedule,
        currentTime: Long,
        db: DailyNotificationDatabase
    ): ScheduleRecoveryResult {
        var missedCount = 0
        var rescheduledCount = 0
        var errors = 0
        
        // Calculate next run time
        val nextRunTime = calculateNextRunTimeForSchedule(schedule, currentTime)
        
        if (nextRunTime < currentTime) {
            // Past alarm - was missed during force stop
            Log.i(TAG, "Past alarm detected: ${schedule.id} scheduled for $nextRunTime")
            
            try {
                // Mark as missed
                markMissedNotificationForSchedule(schedule, nextRunTime, db)
                missedCount++
            } catch (e: Exception) {
                errors++
                Log.e(TAG, "Failed to mark missed notification: ${schedule.id}", e)
            }
            
            // Reschedule next occurrence (always reschedule for daily notifications)
            try {
                val nextOccurrence = calculateNextOccurrence(currentTime)
                rescheduleAlarmForBoot(context, schedule, nextOccurrence, db)
                rescheduledCount++
                Log.i(TAG, "Rescheduled next occurrence: ${schedule.id} for $nextOccurrence")
            } catch (e: Exception) {
                errors++
                Log.e(TAG, "Failed to reschedule next occurrence: ${schedule.id}", e)
            }
        } else {
            // Future alarm - reschedule immediately
            Log.i(TAG, "Future alarm detected: ${schedule.id} scheduled for $nextRunTime")
            
            try {
                rescheduleAlarmForBoot(context, schedule, nextRunTime, db)
                rescheduledCount++
            } catch (e: Exception) {
                errors++
                Log.e(TAG, "Failed to reschedule future alarm: ${schedule.id}", e)
            }
        }
        
        return ScheduleRecoveryResult(missedCount, rescheduledCount, errors)
    }
    
    /**
     * Recover a fetch schedule after force stop
     * 
     * Handles fetch schedules (kind == "fetch")
     * For Phase 2, fetch recovery is deferred - just log
     * 
     * @param schedule Schedule to recover
     * @param currentTime Current time in milliseconds
     * @param db Database instance
     * @return ScheduleRecoveryResult
     */
    private suspend fun recoverFetchSchedule(
        schedule: Schedule,
        @Suppress("UNUSED_PARAMETER") currentTime: Long,
        @Suppress("UNUSED_PARAMETER") db: DailyNotificationDatabase
    ): ScheduleRecoveryResult {
        // Phase 2: Fetch recovery deferred to Phase 3
        // For now, just log that fetch schedule was found
        Log.d(TAG, "Fetch schedule ${schedule.id} will be rescheduled by WorkManager (deferred to Phase 3)")
        return ScheduleRecoveryResult(rescheduledCount = 0, errors = 0)
    }
    
    /**
     * Reschedule an alarm
     * 
     * Data integrity: Validates schedule before rescheduling
     */
    private suspend fun rescheduleAlarm(
        schedule: Schedule,
        nextRunTime: Long,
        db: DailyNotificationDatabase
    ) {
        try {
            val (title, body) = ReactivationManager.getTitleBodyForSchedule(db, schedule)
                ?: Pair("Daily Notification", "Your daily update is ready")
            val config = UserNotificationConfig(
                enabled = schedule.enabled,
                schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *",
                title = title,
                body = body,
                sound = true,
                vibration = true,
                priority = "normal"
            )
            
            NotifyReceiver.scheduleExactNotification(
                context, 
                nextRunTime, 
                config,
                scheduleId = schedule.id,
                source = ScheduleSource.APP_LAUNCH_RECOVERY
            )
            
            // Update schedule in database (best effort)
            try {
                db.scheduleDao().updateRunTimes(schedule.id, schedule.lastRunAt, nextRunTime)
            } catch (e: Exception) {
                Log.w(TAG, "Failed to update schedule in database: ${schedule.id}", e)
                // Don't fail rescheduling if DB update fails
            }
            
            Log.i(TAG, "Rescheduled alarm: ${schedule.id} for $nextRunTime")
        } catch (e: Exception) {
            Log.e(TAG, "Failed to reschedule alarm: ${schedule.id}", e)
            throw e // Re-throw to be caught by caller
        }
    }
    
    /**
     * Record recovery in history
     * 
     * Rollback safety: If history recording fails, log warning but don't fail recovery
     */
    private suspend fun recordRecoveryHistory(
        db: DailyNotificationDatabase,
        scenario: String,
        result: RecoveryResult
    ) {
        try {
            db.historyDao().insert(
                History(
                    refId = "recovery_${System.currentTimeMillis()}",
                    kind = "recovery",
                    occurredAt = System.currentTimeMillis(),
                    outcome = if (result.errors == 0) "success" else "partial",
                    diagJson = """
                        {
                            "scenario": "$scenario",
                            "missed_count": ${result.missedCount},
                            "rescheduled_count": ${result.rescheduledCount},
                            "verified_count": ${result.verifiedCount},
                            "errors": ${result.errors}
                        }
                    """.trimIndent()
                )
            )
        } catch (e: Exception) {
            Log.w(TAG, "Failed to record recovery history (non-fatal)", e)
            // Don't throw - history recording failure shouldn't fail recovery
        }
    }
    
    /**
     * Record recovery failure in history
     */
    private suspend fun recordRecoveryFailure(e: Exception) {
        try {
            val db = DailyNotificationDatabase.getDatabase(context)
            db.historyDao().insert(
                History(
                    refId = "recovery_failure_${System.currentTimeMillis()}",
                    kind = "recovery",
                    occurredAt = System.currentTimeMillis(),
                    outcome = "failure",
                    diagJson = """
                        {
                            "error": "${e.message}",
                            "error_type": "${e.javaClass.simpleName}"
                        }
                    """.trimIndent()
                )
            )
        } catch (historyError: Exception) {
            // Silently fail - we're already in error handling
            Log.w(TAG, "Failed to record recovery failure", historyError)
        }
    }
    
    /**
     * Calculate next run time for a schedule
     */
    private fun calculateNextRunTimeForSchedule(schedule: Schedule, currentTime: Long): Long {
        return when {
            schedule.nextRunAt != null -> schedule.nextRunAt
            schedule.cron != null -> {
                // Parse cron expression: "minute hour * * *" (daily schedule)
                try {
                    val parts = schedule.cron.trim().split("\\s+".toRegex())
                    if (parts.size >= 2) {
                        val minute = parts[0].toIntOrNull() ?: 0
                        val hour = parts[1].toIntOrNull() ?: 9
                        
                        val calendar = java.util.Calendar.getInstance()
                        calendar.set(java.util.Calendar.HOUR_OF_DAY, hour)
                        calendar.set(java.util.Calendar.MINUTE, minute)
                        calendar.set(java.util.Calendar.SECOND, 0)
                        calendar.set(java.util.Calendar.MILLISECOND, 0)
                        
                        var nextRun = calendar.timeInMillis
                        if (nextRun <= currentTime) {
                            calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
                            nextRun = calendar.timeInMillis
                        }
                        return nextRun
                    }
                } catch (e: Exception) {
                    Log.w(TAG, "Failed to parse cron: ${schedule.cron}", e)
                }
                // Fallback: next day at 9 AM
                currentTime + (24 * 60 * 60 * 1000L)
            }
            schedule.clockTime != null -> {
                // Parse HH:mm time string
                try {
                    val parts = schedule.clockTime.split(":")
                    if (parts.size == 2) {
                        val hour = parts[0].toIntOrNull() ?: 9
                        val minute = parts[1].toIntOrNull() ?: 0
                        
                        val calendar = java.util.Calendar.getInstance()
                        calendar.set(java.util.Calendar.HOUR_OF_DAY, hour)
                        calendar.set(java.util.Calendar.MINUTE, minute)
                        calendar.set(java.util.Calendar.SECOND, 0)
                        calendar.set(java.util.Calendar.MILLISECOND, 0)
                        
                        var nextRun = calendar.timeInMillis
                        if (nextRun <= currentTime) {
                            calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
                            nextRun = calendar.timeInMillis
                        }
                        return nextRun
                    }
                } catch (e: Exception) {
                    Log.w(TAG, "Failed to parse clockTime: ${schedule.clockTime}", e)
                }
                // Fallback: next day at 9 AM
                currentTime + (24 * 60 * 60 * 1000L)
            }
            else -> {
                // Default: next day at 9 AM
                currentTime + (24 * 60 * 60 * 1000L)
            }
        }
    }
    
    /**
     * Calculate next occurrence for a repeating schedule
     */
    private fun calculateNextOccurrence(fromTime: Long): Long {
        // For daily schedules, add 24 hours
        // This is simplified - production should handle weekly/monthly patterns
        return fromTime + (24 * 60 * 60 * 1000L)
    }
    
    /**
     * Mark missed notification for a schedule
     */
    private suspend fun markMissedNotificationForSchedule(
        schedule: Schedule,
        scheduledTime: Long,
        db: DailyNotificationDatabase
    ) {
        try {
            // Find or create NotificationContentEntity for this schedule
            val notificationId = "daily_${schedule.id}"
            val existing = try {
                db.notificationContentDao().getNotificationById(notificationId)
            } catch (e: Exception) {
                null
            }
            
            if (existing != null) {
                existing.deliveryStatus = "missed"
                existing.lastDeliveryAttempt = System.currentTimeMillis()
                existing.deliveryAttempts = existing.deliveryAttempts + 1
                existing.updatedAt = System.currentTimeMillis()
                db.notificationContentDao().updateNotification(existing)
            } else {
                // Create new notification content entry for missed alarm
                val notification = org.timesafari.dailynotification.entities.NotificationContentEntity(
                    notificationId,
                    "2.1.0", // Plugin version
                    null, // timesafariDid
                    "daily", // notificationType
                    "Daily Notification",
                    "Your daily update is ready",
                    scheduledTime,
                    java.time.ZoneId.systemDefault().id
                )
                notification.deliveryStatus = "missed"
                notification.lastDeliveryAttempt = System.currentTimeMillis()
                notification.deliveryAttempts = 1
                notification.createdAt = System.currentTimeMillis()
                notification.updatedAt = System.currentTimeMillis()
                db.notificationContentDao().insertNotification(notification)
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to mark missed notification for schedule ${schedule.id}", e)
            throw e
        }
    }
    
    /**
     * Reschedule alarm for boot recovery
     */
    private suspend fun rescheduleAlarmForBoot(
        context: Context,
        schedule: Schedule,
        nextRunTime: Long,
        db: DailyNotificationDatabase
    ) {
        try {
            val (title, body) = ReactivationManager.getTitleBodyForSchedule(db, schedule)
                ?: Pair("Daily Notification", "Your daily update is ready")
            val config = UserNotificationConfig(
                enabled = schedule.enabled,
                schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *",
                title = title,
                body = body,
                sound = true,
                vibration = true,
                priority = "normal"
            )
            
            NotifyReceiver.scheduleExactNotification(
                context,
                nextRunTime,
                config,
                scheduleId = schedule.id,
                source = ScheduleSource.BOOT_RECOVERY,
                skipPendingIntentIdempotence = true
            )
            
            // Update schedule in database (best effort)
            try {
                db.scheduleDao().updateRunTimes(schedule.id, schedule.lastRunAt, nextRunTime)
            } catch (e: Exception) {
                Log.w(TAG, "Failed to update schedule in database: ${schedule.id}", e)
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to reschedule alarm for schedule ${schedule.id}", e)
            throw e
        }
    }
    
    /**
     * Record boot recovery in history
     */
    private suspend fun recordRecoveryHistoryForBoot(
        db: DailyNotificationDatabase,
        result: RecoveryResult
    ) {
        try {
            db.historyDao().insert(
                History(
                    refId = "boot_recovery_${System.currentTimeMillis()}",
                    kind = "boot_recovery",
                    occurredAt = System.currentTimeMillis(),
                    outcome = if (result.errors == 0) "success" else "partial",
                    diagJson = """
                        {
                            "scenario": "BOOT",
                            "missed_count": ${result.missedCount},
                            "rescheduled_count": ${result.rescheduledCount},
                            "verified_count": ${result.verifiedCount},
                            "errors": ${result.errors}
                        }
                    """.trimIndent()
                )
            )
        } catch (e: Exception) {
            Log.w(TAG, "Failed to record boot recovery history (non-fatal)", e)
        }
    }
    
    /**
     * Data class for recovery results
     */
    private data class RecoveryResult(
        val missedCount: Int,
        val rescheduledCount: Int,
        val verifiedCount: Int,
        val errors: Int
    ) {
        override fun toString(): String {
            return "missed=$missedCount, rescheduled=$rescheduledCount, verified=$verifiedCount, errors=$errors"
        }
    }
}

