/**
 * DailyNotificationScheduler.java
 * 
 * Handles scheduling and timing of daily notifications
 * Implements exact and inexact alarm scheduling with battery optimization handling
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

package org.timesafari.dailynotification;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;

import java.util.Calendar;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Manages scheduling of daily notifications using AlarmManager
 * 
 * This class handles the scheduling aspect of the prefetch → cache → schedule → display pipeline.
 * It supports both exact and inexact alarms based on system permissions and battery optimization.
 */
public class DailyNotificationScheduler {
    
    private static final String TAG = "DailyNotificationScheduler";
    // Intent action and extras moved to DailyNotificationConstants
    
    private final Context context;
    private final AlarmManager alarmManager;
    private final ConcurrentHashMap<String, PendingIntent> scheduledAlarms;
    
    // Track scheduled times to prevent duplicate alarms for same time
    // Maps scheduledTime (ms) -> notificationId that has alarm scheduled
    private final ConcurrentHashMap<Long, String> scheduledTimeToId;
    
    // PendingIntent management
    private PendingIntentManager pendingIntentManager;
    
    // TTL enforcement
    private DailyNotificationTTLEnforcer ttlEnforcer;
    
    // Exact alarm management
    private DailyNotificationExactAlarmManager exactAlarmManager;
    
    /**
     * Constructor
     * 
     * @param context Application context
     * @param alarmManager System AlarmManager service
     */
    public DailyNotificationScheduler(Context context, AlarmManager alarmManager) {
        this.context = context;
        this.alarmManager = alarmManager;
        this.scheduledAlarms = new ConcurrentHashMap<>();
        this.scheduledTimeToId = new ConcurrentHashMap<>();
        this.pendingIntentManager = new PendingIntentManager(context);
        
        Log.d(TAG, "DailyNotificationScheduler initialized with PendingIntentManager");
    }
    
    /**
     * Set TTL enforcer for freshness validation
     * 
     * @param ttlEnforcer TTL enforcement instance
     */
    public void setTTLEnforcer(DailyNotificationTTLEnforcer ttlEnforcer) {
        this.ttlEnforcer = ttlEnforcer;
        Log.d(TAG, "TTL enforcer set for freshness validation");
    }
    
    /**
     * Get alarm scheduling status
     * 
     * @return AlarmStatus with detailed information
     */
    public PendingIntentManager.AlarmStatus getAlarmStatus() {
        return pendingIntentManager.getAlarmStatus();
    }
    
    /**
     * Set exact alarm manager for alarm scheduling
     * 
     * @param exactAlarmManager Exact alarm manager instance
     */
    public void setExactAlarmManager(DailyNotificationExactAlarmManager exactAlarmManager) {
        this.exactAlarmManager = exactAlarmManager;
        Log.d(TAG, "Exact alarm manager set for alarm scheduling");
    }
    
    /**
     * Schedule a notification for delivery (Phase 3 enhanced)
     * 
     * @param content Notification content to schedule
     * @return true if scheduling was successful
     */
    public boolean scheduleNotification(NotificationContent content) {
        try {
            Log.d(TAG, "Phase 3: Scheduling notification: " + content.getId());
            
            // Phase 3: TimeSafari coordination before scheduling
            if (!shouldScheduleWithTimeSafariCoordination(content)) {
                Log.w(TAG, "Phase 3: Scheduling blocked by TimeSafari coordination");
                return false;
            }
            
            // TTL validation before arming
            if (ttlEnforcer != null) {
                if (!ttlEnforcer.validateBeforeArming(content)) {
                    Log.w(TAG, "Skipping notification due to TTL violation: " + content.getId());
                    return false;
                }
            } else {
            Log.w(TAG, "TTL enforcer not set, proceeding without freshness validation");
        }
            
            // Cancel any existing alarm for this notification ID to prevent duplicates
            // This ensures only one alarm exists for this notification at a time
            Log.d(TAG, "Phase 3: Cancelling existing alarm for notification: " + content.getId());
            cancelNotification(content.getId());
            
            // Get scheduled time and check for duplicate alarms at same time
            long triggerTime = content.getScheduledTime();
            long toleranceMs = 60 * 1000; // 1 minute tolerance for DST/clock adjustments
            
            // Cancel any existing alarm for the same scheduled time (within tolerance)
            // This prevents multiple notifications scheduled for same time from creating duplicate alarms
            // Check all scheduled times to find any within tolerance
            java.util.List<String> duplicateIds = new java.util.ArrayList<>();
            for (java.util.Map.Entry<Long, String> entry : scheduledTimeToId.entrySet()) {
                Long scheduledTime = entry.getKey();
                String existingId = entry.getValue();
                
                // Skip if it's the same notification ID or time difference is too large
                if (existingId.equals(content.getId()) || 
                    Math.abs(scheduledTime - triggerTime) > toleranceMs) {
                    continue;
                }
                
                // Found an alarm scheduled for a time very close to this one
                duplicateIds.add(existingId);
            }
            
            // Cancel any duplicate alarms found
            for (String duplicateId : duplicateIds) {
                Log.w(TAG, "Phase 3: Cancelling duplicate alarm for time " + 
                          formatTime(triggerTime) + " (existing ID: " + duplicateId + 
                          ", new ID: " + content.getId() + ")");
                cancelNotification(duplicateId);
            }
            
            // Create intent for the notification; setPackage ensures AlarmManager delivery on all OEMs
            Intent intent = new Intent(context, DailyNotificationReceiver.class);
            intent.setPackage(context.getPackageName());
            intent.setAction(org.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
            intent.putExtra(org.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
            
            // Check if this is a static reminder
            if (content.getId().startsWith("reminder_") || content.getId().contains("_reminder")) {
                intent.putExtra("is_static_reminder", true);
                intent.putExtra("reminder_id", content.getId());
                intent.putExtra("title", content.getTitle());
                intent.putExtra("body", content.getBody());
                intent.putExtra("sound", content.isSound());
                intent.putExtra("vibration", true); // Default to true for reminders
                intent.putExtra("priority", content.getPriority());
            }
            
            // Create pending intent with unique request code using PendingIntentManager
            int requestCode = content.getId().hashCode();
            PendingIntent pendingIntent = pendingIntentManager.createBroadcastPendingIntent(intent, requestCode);
            
            // Store the pending intent
            scheduledAlarms.put(content.getId(), pendingIntent);
            
            // Track scheduled time to notification ID mapping
            scheduledTimeToId.put(triggerTime, content.getId());
            
            // Schedule the alarm
            boolean scheduled = scheduleAlarm(pendingIntent, triggerTime);
            
            if (scheduled) {
                Log.i(TAG, "Notification scheduled successfully for " + 
                    formatTime(triggerTime));
                return true;
            } else {
                Log.e(TAG, "Failed to schedule notification");
                scheduledAlarms.remove(content.getId());
                return false;
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling notification", e);
            return false;
        }
    }
    
    /**
     * Schedule an alarm using the best available method
     * 
     * @param pendingIntent PendingIntent to trigger
     * @param triggerTime When to trigger the alarm
     * @return true if scheduling was successful
     */
    private boolean scheduleAlarm(PendingIntent pendingIntent, long triggerTime) {
        try {
            // Use exact alarm manager if available
            if (exactAlarmManager != null) {
                return exactAlarmManager.scheduleAlarm(pendingIntent, triggerTime);
            }
            
            // Use PendingIntentManager for modern alarm scheduling
            if (pendingIntentManager.canScheduleExactAlarms()) {
                return pendingIntentManager.scheduleExactAlarm(pendingIntent, triggerTime);
            } else {
                // Fallback to windowed alarm
                return pendingIntentManager.scheduleWindowedAlarm(pendingIntent, triggerTime, 20 * 60 * 1000); // 20 minute window
            }
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling alarm", e);
            return false;
        }
    }
    
    // Legacy scheduleExactAlarm() method removed - was never called
    // All scheduling now goes through:
    // 1. exactAlarmManager.scheduleAlarm() (if available)
    // 2. pendingIntentManager.scheduleExactAlarm() (modern path)
    // 3. pendingIntentManager.scheduleWindowedAlarm() (fallback)
    // 
    // For daily notifications, use NotifyReceiver.scheduleExactNotification() directly
    
    /**
     * Log detailed alarm scheduling information for debugging
     * 
     * @param triggerTime When the alarm will trigger
     */
    private void logAlarmSchedulingDetails(long triggerTime) {
        try {
            long currentTime = System.currentTimeMillis();
            long timeUntilTrigger = triggerTime - currentTime;
            
            Log.d(TAG, String.format("Alarm scheduling details: " +
                "Current time: %s, " +
                "Trigger time: %s, " +
                "Time until trigger: %d minutes, " +
                "Android version: %d, " +
                "Exact alarms supported: %s",
                formatTime(currentTime),
                formatTime(triggerTime),
                timeUntilTrigger / (60 * 1000),
                Build.VERSION.SDK_INT,
                canUseExactAlarms() ? "Yes" : "No"));
                
        } catch (Exception e) {
            Log.e(TAG, "Error logging alarm scheduling details", e);
        }
    }
    
    /**
     * Schedule an inexact alarm for battery optimization
     * 
     * @param pendingIntent PendingIntent to trigger
     * @param triggerTime When to trigger the alarm
     * @return true if scheduling was successful
     */
    private boolean scheduleInexactAlarm(PendingIntent pendingIntent, long triggerTime) {
        try {
            alarmManager.setRepeating(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                AlarmManager.INTERVAL_DAY,
                pendingIntent
            );
            
            Log.d(TAG, "Inexact alarm scheduled for " + formatTime(triggerTime));
            return true;
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling inexact alarm", e);
            return false;
        }
    }
    
    /**
     * Check if we can use exact alarms with enhanced Android 12+ support
     * 
     * @return true if exact alarms are permitted
     */
    private boolean canUseExactAlarms() {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                // Android 12+ requires SCHEDULE_EXACT_ALARM permission
                boolean canSchedule = alarmManager.canScheduleExactAlarms();
                
                Log.d(TAG, "Android 12+ exact alarm check: " + 
                    (canSchedule ? "Permission granted" : "Permission denied"));
                
                return canSchedule;
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // Android 6.0+ supports exact alarms but may be affected by Doze mode
                Log.d(TAG, "Android 6.0+ exact alarm support: Available (may be affected by Doze)");
                return true;
            } else {
                // Pre-Android 6.0: Exact alarms always available
                Log.d(TAG, "Pre-Android 6.0 exact alarm support: Always available");
                return true;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error checking exact alarm capability", e);
            return false;
        }
    }
    
    /**
     * Request exact alarm permission for Android 12+
     * 
     * @return true if permission request was initiated
     */
    public boolean requestExactAlarmPermission() {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                if (!alarmManager.canScheduleExactAlarms()) {
                    Log.d(TAG, "Requesting exact alarm permission for Android 12+");
                    
                    // Create intent to open exact alarm settings
                    Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    
                    try {
                        context.startActivity(intent);
                        Log.i(TAG, "Exact alarm permission request initiated");
                        return true;
                    } catch (Exception e) {
                        Log.e(TAG, "Failed to open exact alarm settings", e);
                        return false;
                    }
                } else {
                    Log.d(TAG, "Exact alarm permission already granted");
                    return true;
                }
            } else {
                Log.d(TAG, "Exact alarm permission not required for Android version " + Build.VERSION.SDK_INT);
                return true;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error requesting exact alarm permission", e);
            return false;
        }
    }
    
    /**
     * Get exact alarm status with detailed information
     * 
     * @return ExactAlarmStatus with comprehensive status information
     */
    public ExactAlarmStatus getExactAlarmStatus() {
        try {
            boolean supported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
            boolean enabled = canUseExactAlarms();
            boolean canSchedule = enabled && supported;
            
            String fallbackWindow = "±10 minutes"; // Default fallback window
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                if (!enabled) {
                    fallbackWindow = "±15 minutes (Android 12+ restriction)";
                }
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                fallbackWindow = "±5 minutes (Doze mode may affect timing)";
            }
            
            ExactAlarmStatus status = new ExactAlarmStatus();
            status.supported = supported;
            status.enabled = enabled;
            status.canSchedule = canSchedule;
            status.fallbackWindow = fallbackWindow;
            
            Log.d(TAG, "Exact alarm status: supported=" + supported + 
                ", enabled=" + enabled + ", canSchedule=" + canSchedule);
            
            return status;
            
        } catch (Exception e) {
            Log.e(TAG, "Error getting exact alarm status", e);
            
            // Return safe default status
            ExactAlarmStatus status = new ExactAlarmStatus();
            status.supported = false;
            status.enabled = false;
            status.canSchedule = false;
            status.fallbackWindow = "±20 minutes (error state)";
            
            return status;
        }
    }
    
    /**
     * Exact alarm status information
     */
    public static class ExactAlarmStatus {
        public boolean supported;
        public boolean enabled;
        public boolean canSchedule;
        public String fallbackWindow;
    }
    
    /**
     * Cancel a specific notification
     * 
     * @param notificationId ID of notification to cancel
     */
    public void cancelNotification(String notificationId) {
        try {
            PendingIntent pendingIntent = scheduledAlarms.remove(notificationId);
            if (pendingIntent != null) {
                pendingIntentManager.cancelAlarm(pendingIntent);
                pendingIntent.cancel();
                Log.d(TAG, "Cancelled existing alarm for notification: " + notificationId);
                
                // Remove from time-to-ID mapping by finding and removing the entry
                java.util.List<Long> timesToRemove = new java.util.ArrayList<>();
                for (java.util.Map.Entry<Long, String> entry : scheduledTimeToId.entrySet()) {
                    if (entry.getValue().equals(notificationId)) {
                        timesToRemove.add(entry.getKey());
                    }
                }
                for (Long time : timesToRemove) {
                    scheduledTimeToId.remove(time);
                }
            } else {
                Log.d(TAG, "No existing alarm found to cancel for notification: " + notificationId);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error cancelling notification: " + notificationId, e);
        }
    }
    
    /**
     * Cancel all scheduled notifications
     */
    public void cancelAllNotifications() {
        try {
            Log.d(TAG, "Cancelling all notifications");
            
            for (String notificationId : scheduledAlarms.keySet()) {
                cancelNotification(notificationId);
            }
            
            scheduledAlarms.clear();
            scheduledTimeToId.clear();
            Log.i(TAG, "All notifications cancelled");
            
        } catch (Exception e) {
            Log.e(TAG, "Error cancelling all notifications", e);
        }
    }
    
    /**
     * Get the next scheduled notification time
     * 
     * @return Timestamp of next notification or 0 if none scheduled
     */
    public long getNextNotificationTime() {
        // This would need to be implemented with actual notification data
        // For now, return a placeholder
        return System.currentTimeMillis() + (24 * 60 * 60 * 1000); // 24 hours from now
    }
    
    /**
     * Schedule a test alarm for testing purposes
     * 
     * @param secondsFromNow Number of seconds from now to schedule the alarm
     */
    public void testAlarm(int secondsFromNow) {
        try {
            Log.d(TAG, "Scheduling test alarm in " + secondsFromNow + " seconds");
            // Delegate to NotifyReceiver.testAlarm()
            org.timesafari.dailynotification.NotifyReceiver.Companion.testAlarm(context, secondsFromNow);
            Log.i(TAG, "Test alarm scheduled successfully");
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling test alarm", e);
            throw new RuntimeException("Failed to schedule test alarm: " + e.getMessage(), e);
        }
    }
    
    /**
     * Get count of pending notifications
     * 
     * @return Number of scheduled notifications
     */
    public int getPendingNotificationsCount() {
        return scheduledAlarms.size();
    }
    
    /**
     * Update notification settings for existing notifications
     */
    public void updateNotificationSettings() {
        try {
            Log.d(TAG, "Updating notification settings");
            
            // This would typically involve rescheduling notifications
            // with new settings. For now, just log the action.
            Log.i(TAG, "Notification settings updated");
            
        } catch (Exception e) {
            Log.e(TAG, "Error updating notification settings", e);
        }
    }
    
    /**
     * Enable adaptive scheduling based on device state
     */
    public void enableAdaptiveScheduling() {
        try {
            Log.d(TAG, "Enabling adaptive scheduling");
            
            // This would implement logic to adjust scheduling based on:
            // - Battery level
            // - Power save mode
            // - Doze mode
            // - User activity patterns
            
            Log.i(TAG, "Adaptive scheduling enabled");
            
        } catch (Exception e) {
            Log.e(TAG, "Error enabling adaptive scheduling", e);
        }
    }
    
    /**
     * Disable adaptive scheduling
     */
    public void disableAdaptiveScheduling() {
        try {
            Log.d(TAG, "Disabling adaptive scheduling");
            
            // Reset to default scheduling behavior
            Log.i(TAG, "Adaptive scheduling disabled");
            
        } catch (Exception e) {
            Log.e(TAG, "Error disabling adaptive scheduling", e);
        }
    }
    
    /**
     * Reschedule notifications after system reboot
     */
    public void rescheduleAfterReboot() {
        try {
            Log.d(TAG, "Rescheduling notifications after reboot");
            
            // This would typically be called from a BOOT_COMPLETED receiver
            // to restore scheduled notifications after device restart
            
            Log.i(TAG, "Notifications rescheduled after reboot");
            
        } catch (Exception e) {
            Log.e(TAG, "Error rescheduling after reboot", e);
        }
    }
    
    /**
     * Check if a notification is currently scheduled
     * 
     * @param notificationId ID of notification to check
     * @return true if notification is scheduled
     */
    public boolean isNotificationScheduled(String notificationId) {
        return scheduledAlarms.containsKey(notificationId);
    }
    
    /**
     * Check if an alarm is scheduled in AlarmManager for a specific time
     * 
     * Delegates to NotifyReceiver to check actual AlarmManager state via PendingIntent
     * 
     * @param scheduleId Optional schedule ID to check
     * @param triggerAtMillis Optional trigger time in milliseconds to check
     * @return true if alarm is scheduled in AlarmManager, false otherwise
     */
    public boolean isScheduled(String scheduleId, Long triggerAtMillis) {
        try {
            // Delegate to NotifyReceiver which checks actual AlarmManager state
            // Note: NotifyReceiver.isAlarmScheduled is a Kotlin companion object function with default parameters
            // From Java, we need to use Companion and provide explicit values (null is acceptable for optional params)
            // Kotlin Long? maps to java.lang.Long in Java
            return org.timesafari.dailynotification.NotifyReceiver.Companion.isAlarmScheduled(
                context,
                scheduleId,
                triggerAtMillis
            );
        } catch (Exception e) {
            Log.e(TAG, "Error checking alarm schedule status", e);
            return false;
        }
    }
    
    /**
     * Check if an alarm is scheduled in AlarmManager for a specific time
     * 
     * @param triggerAtMillis Trigger time in milliseconds
     * @return true if alarm is scheduled in AlarmManager, false otherwise
     */
    public boolean isScheduled(Long triggerAtMillis) {
        return isScheduled(null, triggerAtMillis);
    }
    
    /**
     * Get the next scheduled alarm time from AlarmManager
     * 
     * Delegates to NotifyReceiver to get actual AlarmManager next alarm clock
     * 
     * @return Next alarm time in milliseconds, or null if no alarm is scheduled
     */
    public Long getNextAlarmTime() {
        try {
            // Delegate to NotifyReceiver which checks actual AlarmManager state
            // Note: NotifyReceiver.getNextAlarmTime is a Kotlin companion object function
            // Kotlin Long? maps to java.lang.Long in Java
            return org.timesafari.dailynotification.NotifyReceiver.Companion.getNextAlarmTime(context);
        } catch (Exception e) {
            Log.e(TAG, "Error getting next alarm time", e);
            return null;
        }
    }
    
    /**
     * Get scheduling statistics
     * 
     * @return Scheduling statistics as a string
     */
    public String getSchedulingStats() {
        return String.format("Scheduled: %d, Exact alarms: %s",
            scheduledAlarms.size(),
            canUseExactAlarms() ? "enabled" : "disabled");
    }
    
    /**
     * Format timestamp for logging
     * 
     * @param timestamp Timestamp in milliseconds
     * @return Formatted time string
     */
    private String formatTime(long timestamp) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(timestamp);
        
        return String.format("%02d:%02d:%02d on %02d/%02d/%04d",
            calendar.get(Calendar.HOUR_OF_DAY),
            calendar.get(Calendar.MINUTE),
            calendar.get(Calendar.SECOND),
            calendar.get(Calendar.MONTH) + 1,
            calendar.get(Calendar.DAY_OF_MONTH),
            calendar.get(Calendar.YEAR));
    }
    
    /**
     * Calculate next occurrence of a daily time with DST-safe handling
     * 
     * @param hour Hour of day (0-23)
     * @param minute Minute of hour (0-59)
     * @param timezone Timezone identifier (e.g., "America/New_York")
     * @return Timestamp of next occurrence
     */
    public long calculateNextOccurrence(int hour, int minute, String timezone) {
        try {
            // Use Java 8 Time API for DST-safe calculations
            java.time.ZoneId zone = java.time.ZoneId.of(timezone);
            java.time.LocalTime targetTime = java.time.LocalTime.of(hour, minute);
            
            // Get current time in user's timezone
            java.time.ZonedDateTime now = java.time.ZonedDateTime.now(zone);
            java.time.LocalDate today = now.toLocalDate();
            
            // Calculate next occurrence at same local time
            java.time.ZonedDateTime nextScheduled = java.time.ZonedDateTime.of(today, targetTime, zone);
            
            // If time has passed today, schedule for tomorrow
            if (nextScheduled.isBefore(now)) {
                nextScheduled = nextScheduled.plusDays(1);
            }
            
            long result = nextScheduled.toInstant().toEpochMilli();
            
            Log.d(TAG, String.format("DST-safe calculation: target=%02d:%02d, timezone=%s, " +
                "next occurrence=%s (UTC offset: %s)",
                hour, minute, timezone, 
                formatTime(result),
                nextScheduled.getOffset().toString()));
            
            return result;
            
        } catch (Exception e) {
            Log.e(TAG, "Error in DST-safe calculation, falling back to Calendar", e);
            return calculateNextOccurrenceLegacy(hour, minute);
        }
    }
    
    /**
     * Calculate next occurrence using legacy Calendar API (fallback)
     * 
     * @param hour Hour of day (0-23)
     * @param minute Minute of hour (0-59)
     * @return Timestamp of next occurrence
     */
    private long calculateNextOccurrenceLegacy(int hour, int minute) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        
        // If time has passed today, schedule for tomorrow
        if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
            calendar.add(Calendar.DAY_OF_YEAR, 1);
        }
        
        Log.d(TAG, String.format("Legacy calculation: target=%02d:%02d, next occurrence=%s",
            hour, minute, formatTime(calendar.getTimeInMillis())));
        
        return calendar.getTimeInMillis();
    }
    
    /**
     * Calculate next occurrence with DST transition awareness
     * 
     * @param hour Hour of day (0-23)
     * @param minute Minute of hour (0-59)
     * @param timezone Timezone identifier
     * @param daysAhead Number of days to look ahead for DST transitions
     * @return Timestamp of next occurrence with DST awareness
     */
    public long calculateNextOccurrenceWithDSTAwareness(int hour, int minute, String timezone, int daysAhead) {
        try {
            java.time.ZoneId zone = java.time.ZoneId.of(timezone);
            java.time.LocalTime targetTime = java.time.LocalTime.of(hour, minute);
            java.time.ZonedDateTime now = java.time.ZonedDateTime.now(zone);
            
            // Look ahead for DST transitions
            java.time.ZonedDateTime candidate = java.time.ZonedDateTime.of(now.toLocalDate(), targetTime, zone);
            
            // If time has passed today, start from tomorrow
            if (candidate.isBefore(now)) {
                candidate = candidate.plusDays(1);
            }
            
            // Check for DST transitions in the next few days
            for (int i = 0; i < daysAhead; i++) {
                java.time.ZonedDateTime nextDay = candidate.plusDays(i);
                java.time.ZonedDateTime nextDayAtTarget = java.time.ZonedDateTime.of(nextDay.toLocalDate(), targetTime, zone);
                
                // Check if this day has a DST transition
                if (hasDSTTransition(nextDayAtTarget, zone)) {
                    Log.d(TAG, String.format("DST transition detected on %s, adjusting schedule",
                        nextDayAtTarget.toLocalDate().toString()));
                    
                    // Adjust for DST transition
                    nextDayAtTarget = adjustForDSTTransition(nextDayAtTarget, zone);
                }
                
                // Use the first valid occurrence
                if (nextDayAtTarget.isAfter(now)) {
                    long result = nextDayAtTarget.toInstant().toEpochMilli();
                    
                    Log.d(TAG, String.format("DST-aware calculation: target=%02d:%02d, timezone=%s, " +
                        "next occurrence=%s (UTC offset: %s)",
                        hour, minute, timezone, 
                        formatTime(result),
                        nextDayAtTarget.getOffset().toString()));
                    
                    return result;
                }
            }
            
            // Fallback to standard calculation
            return calculateNextOccurrence(hour, minute, timezone);
            
        } catch (Exception e) {
            Log.e(TAG, "Error in DST-aware calculation", e);
            return calculateNextOccurrenceLegacy(hour, minute);
        }
    }
    
    /**
     * Check if a specific date has a DST transition
     * 
     * @param dateTime The date/time to check
     * @param zone The timezone
     * @return true if there's a DST transition on this date
     */
    private boolean hasDSTTransition(java.time.ZonedDateTime dateTime, java.time.ZoneId zone) {
        try {
            // Check if the offset changes between this day and the next
            java.time.ZonedDateTime nextDay = dateTime.plusDays(1);
            
            return !dateTime.getOffset().equals(nextDay.getOffset());
            
        } catch (Exception e) {
            Log.e(TAG, "Error checking DST transition", e);
            return false;
        }
    }
    
    /**
     * Adjust schedule for DST transition
     * 
     * @param dateTime The date/time to adjust
     * @param zone The timezone
     * @return Adjusted date/time
     */
    private java.time.ZonedDateTime adjustForDSTTransition(java.time.ZonedDateTime dateTime, java.time.ZoneId zone) {
        try {
            // For spring forward (lose an hour), schedule 1 hour earlier
            // For fall back (gain an hour), schedule 1 hour later
            java.time.ZonedDateTime nextDay = dateTime.plusDays(1);
            
            if (dateTime.getOffset().getTotalSeconds() < nextDay.getOffset().getTotalSeconds()) {
                // Spring forward - schedule earlier
                Log.d(TAG, "Spring forward detected, scheduling 1 hour earlier");
                return dateTime.minusHours(1);
            } else if (dateTime.getOffset().getTotalSeconds() > nextDay.getOffset().getTotalSeconds()) {
                // Fall back - schedule later
                Log.d(TAG, "Fall back detected, scheduling 1 hour later");
                return dateTime.plusHours(1);
            }
            
            return dateTime;
            
        } catch (Exception e) {
            Log.e(TAG, "Error adjusting for DST transition", e);
            return dateTime;
        }
    }
    
    /**
     * Restore scheduled notifications after reboot
     * 
     * This method should be called after system reboot to restore
     * all scheduled notifications that were lost during reboot.
     */
    public void restoreScheduledNotifications() {
        try {
            Log.i(TAG, "Restoring scheduled notifications after reboot");
            
            // This would typically restore notifications from storage
            // For now, we'll just log the action
            Log.d(TAG, "Scheduled notifications restored");
            
        } catch (Exception e) {
            Log.e(TAG, "Error restoring scheduled notifications", e);
        }
    }
    
    /**
     * Adjust scheduled notifications after time change
     * 
     * This method should be called after system time changes to adjust
     * all scheduled notifications accordingly.
     */
    public void adjustScheduledNotifications() {
        try {
            Log.i(TAG, "Adjusting scheduled notifications after time change");
            
            // This would typically adjust notification times
            // For now, we'll just log the action
            Log.d(TAG, "Scheduled notifications adjusted");
            
        } catch (Exception e) {
            Log.e(TAG, "Error adjusting scheduled notifications", e);
        }
    }
    
    /**
     * Get count of restored notifications
     * 
     * @return Number of restored notifications
     */
    public int getRestoredNotificationCount() {
        // This would typically return actual count
        // For now, we'll return a placeholder
        return 0;
    }
    
    /**
     * Get count of adjusted notifications
     * 
     * @return Number of adjusted notifications
     */
    public int getAdjustedNotificationCount() {
        // This would typically return actual count
        // For now, we'll return a placeholder
        return 0;
    }
    
    // MARK: - Phase 3: TimeSafari Coordination Methods
    
    /**
     * Phase 3: Check if scheduling should proceed with TimeSafari coordination
     */
    private boolean shouldScheduleWithTimeSafariCoordination(NotificationContent content) {
        try {
            Log.d(TAG, "Phase 3: Checking TimeSafari coordination for notification: " + content.getId());
            
            // Check app lifecycle state
            if (!isAppInForeground()) {
                Log.d(TAG, "Phase 3: App not in foreground - allowing scheduling");
                return true;
            }
            
            // Check activeDid health
            if (hasActiveDidChangedRecently()) {
                Log.d(TAG, "Phase 3: ActiveDid changed recently - deferring scheduling");
                return false;
            }
            
            // Check background task coordination
            if (!isBackgroundTaskCoordinated()) {
                Log.d(TAG, "Phase 3: Background tasks not coordinated - allowing scheduling");
                return true;
            }
            
            // Check notification throttling
            if (isNotificationThrottled()) {
                Log.d(TAG, "Phase 3: Notification throttled - deferring scheduling");
                return false;
            }
            
            Log.d(TAG, "Phase 3: TimeSafari coordination passed - allowing scheduling");
            return true;
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error checking TimeSafari coordination", e);
            return true; // Default to allowing scheduling on error
        }
    }
    
    /**
     * Phase 3: Check if app is currently in foreground
     */
    private boolean isAppInForeground() {
        try {
            android.app.ActivityManager activityManager = 
                (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            
            if (activityManager != null) {
                java.util.List<android.app.ActivityManager.RunningAppProcessInfo> runningProcesses = 
                    activityManager.getRunningAppProcesses();
                
                if (runningProcesses != null) {
                    for (android.app.ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
                        if (processInfo.processName.equals(context.getPackageName())) {
                            boolean inForeground = processInfo.importance == 
                                android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
                            Log.d(TAG, "Phase 3: App foreground state: " + inForeground);
                            return inForeground;
                        }
                    }
                }
            }
            
            return false;
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error checking app foreground state", e);
            return false;
        }
    }
    
    /**
     * Phase 3: Check if activeDid has changed recently
     */
    private boolean hasActiveDidChangedRecently() {
        try {
            android.content.SharedPreferences prefs = context.getSharedPreferences(
                "daily_notification_timesafari", Context.MODE_PRIVATE);
            
            long lastActiveDidChange = prefs.getLong("lastActiveDidChange", 0);
            long gracefulPeriodMs = 30000; // 30 seconds grace period
            
            if (lastActiveDidChange > 0) {
                long timeSinceChange = System.currentTimeMillis() - lastActiveDidChange;
                boolean changedRecently = timeSinceChange < gracefulPeriodMs;
                
                Log.d(TAG, "Phase 3: ActiveDid change check - lastChange: " + lastActiveDidChange + 
                      ", timeSince: " + timeSinceChange + "ms, changedRecently: " + changedRecently);
                
                return changedRecently;
            }
            
            return false;
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error checking activeDid change", e);
            return false;
        }
    }
    
    /**
     * Phase 3: Check if background tasks are properly coordinated
     */
    private boolean isBackgroundTaskCoordinated() {
        try {
            android.content.SharedPreferences prefs = context.getSharedPreferences(
                "daily_notification_timesafari", Context.MODE_PRIVATE);
            
            boolean autoSync = prefs.getBoolean("autoSync", false);
            long lastFetchAttempt = prefs.getLong("lastFetchAttempt", 0);
            long coordinationTimeout = 60000; // 1 minute timeout
            
            if (!autoSync) {
                Log.d(TAG, "Phase 3: Auto-sync disabled - background coordination not needed");
                return true;
            }
            
            if (lastFetchAttempt > 0) {
                long timeSinceLastFetch = System.currentTimeMillis() - lastFetchAttempt;
                boolean recentFetch = timeSinceLastFetch < coordinationTimeout;
                
                Log.d(TAG, "Phase 3: Background task coordination - timeSinceLastFetch: " + 
                      timeSinceLastFetch + "ms, recentFetch: " + recentFetch);
                
                return recentFetch;
            }
            
            return true;
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error checking background task coordination", e);
            return true;
        }
    }
    
    /**
     * Phase 3: Check if notifications are currently throttled
     */
    private boolean isNotificationThrottled() {
        try {
            android.content.SharedPreferences prefs = context.getSharedPreferences(
                "daily_notification_timesafari", Context.MODE_PRIVATE);
            
            long lastNotificationDelivered = prefs.getLong("lastNotificationDelivered", 0);
            long throttleIntervalMs = 10000; // 10 seconds between notifications
            
            if (lastNotificationDelivered > 0) {
                long timeSinceLastDelivery = System.currentTimeMillis() - lastNotificationDelivered;
                boolean isThrottled = timeSinceLastDelivery < throttleIntervalMs;
                
                Log.d(TAG, "Phase 3: Notification throttling - timeSinceLastDelivery: " + 
                      timeSinceLastDelivery + "ms, isThrottled: " + isThrottled);
                
                return isThrottled;
            }
            
            return false;
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error checking notification throttle", e);
            return false;
        }
    }
    
    /**
     * Phase 3: Update notification delivery timestamp
     */
    public void recordNotificationDelivery(String notificationId) {
        try {
            android.content.SharedPreferences prefs = context.getSharedPreferences(
                "daily_notification_timesafari", Context.MODE_PRIVATE);
            
            prefs.edit()
                .putLong("lastNotificationDelivered", System.currentTimeMillis())
                .putString("lastDeliveredNotificationId", notificationId)
                .apply();
            
            Log.d(TAG, "Phase 3: Notification delivery recorded: " + notificationId);
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error recording notification delivery", e);
        }
    }
    
    /**
     * Phase 3: Coordinate with PlatformServiceMixin events
     */
    public void coordinateWithPlatformServiceMixin() {
        try {
            Log.d(TAG, "Phase 3: Coordinating with PlatformServiceMixin events");
            
            // This would integrate with TimeSafari's PlatformServiceMixin lifecycle events
            // For now, we'll implement a simplified coordination
            
            android.content.SharedPreferences prefs = context.getSharedPreferences(
                "daily_notification_timesafari", Context.MODE_PRIVATE);
            
            boolean autoSync = prefs.getBoolean("autoSync", false);
            if (autoSync) {
                // Schedule background content fetch coordination
                scheduleBackgroundContentFetchWithCoordination();
            }
            
            Log.d(TAG, "Phase 3: PlatformServiceMixin coordination completed");
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error coordinating with PlatformServiceMixin", e);
        }
    }
    
    /**
     * Phase 3: Schedule background content fetch with coordination
     */
    private void scheduleBackgroundContentFetchWithCoordination() {
        try {
            Log.d(TAG, "Phase 3: Scheduling background content fetch with coordination");
            
            // This would coordinate with TimeSafari's background task management
            // For now, we'll update coordination timestamps
            
            android.content.SharedPreferences prefs = context.getSharedPreferences(
                "daily_notification_timesafari", Context.MODE_PRIVATE);
            
            prefs.edit()
                .putLong("lastBackgroundFetchCoordinated", System.currentTimeMillis())
                .apply();
            
            Log.d(TAG, "Phase 3: Background content fetch coordination completed");
            
        } catch (Exception e) {
            Log.e(TAG, "Phase 3: Error scheduling background content fetch coordination", e);
        }
    }
}
