/**
 * DailyNotificationExactAlarmManager.java
 * 
 * Android Exact Alarm Manager with fallback to windowed alarms
 * Implements SCHEDULE_EXACT_ALARM permission handling and fallback logic
 * 
 * @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.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;

import java.util.concurrent.TimeUnit;

/**
 * Manages Android exact alarms with fallback to windowed alarms
 * 
 * This class implements the critical Android alarm management:
 * - Requests SCHEDULE_EXACT_ALARM permission
 * - Falls back to windowed alarms (±10m) if exact permission denied
 * - Provides deep-link to enable exact alarms in settings
 * - Handles reboot and time-change recovery
 */
public class DailyNotificationExactAlarmManager {
    
    // MARK: - Constants
    
    private static final String TAG = "DailyNotificationExactAlarmManager";
    
    // Permission constants
    private static final String PERMISSION_SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
    
    // Fallback window settings
    private static final long FALLBACK_WINDOW_START_MS = TimeUnit.MINUTES.toMillis(-10); // 10 minutes before
    private static final long FALLBACK_WINDOW_LENGTH_MS = TimeUnit.MINUTES.toMillis(20); // 20 minutes total
    
    // Deep-link constants
    private static final String EXACT_ALARM_SETTINGS_ACTION = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
    private static final String EXACT_ALARM_SETTINGS_PACKAGE = "com.android.settings";
    
    // MARK: - Properties
    
    private final Context context;
    private final AlarmManager alarmManager;
    private final DailyNotificationScheduler scheduler;
    
    // Alarm state
    private boolean exactAlarmsEnabled = false;
    private boolean exactAlarmsSupported = false;
    
    // MARK: - Initialization
    
    /**
     * Constructor
     * 
     * @param context Application context
     * @param alarmManager System AlarmManager service
     * @param scheduler Notification scheduler
     */
    public DailyNotificationExactAlarmManager(Context context, AlarmManager alarmManager, DailyNotificationScheduler scheduler) {
        this.context = context;
        this.alarmManager = alarmManager;
        this.scheduler = scheduler;
        
        // Check exact alarm support and status
        checkExactAlarmSupport();
        checkExactAlarmStatus();
        
        Log.d(TAG, "ExactAlarmManager initialized: supported=" + exactAlarmsSupported + ", enabled=" + exactAlarmsEnabled);
    }
    
    // MARK: - Exact Alarm Support
    
    /**
     * Check if exact alarms are supported on this device
     */
    private void checkExactAlarmSupport() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            exactAlarmsSupported = true;
            Log.d(TAG, "Exact alarms supported on Android S+");
        } else {
            exactAlarmsSupported = false;
            Log.d(TAG, "Exact alarms not supported on Android " + Build.VERSION.SDK_INT);
        }
    }
    
    /**
     * Check current exact alarm status
     */
    private void checkExactAlarmStatus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            exactAlarmsEnabled = alarmManager.canScheduleExactAlarms();
            Log.d(TAG, "Exact alarm status: " + (exactAlarmsEnabled ? "enabled" : "disabled"));
        } else {
            exactAlarmsEnabled = true; // Always available on older Android versions
            Log.d(TAG, "Exact alarms always available on Android " + Build.VERSION.SDK_INT);
        }
    }
    
    /**
     * Get exact alarm status
     * 
     * @return Status information
     */
    public ExactAlarmStatus getExactAlarmStatus() {
        return new ExactAlarmStatus(
            exactAlarmsSupported,
            exactAlarmsEnabled,
            canScheduleExactAlarms(),
            getFallbackWindowInfo()
        );
    }
    
    /**
     * Check if exact alarms can be scheduled
     * 
     * @return true if exact alarms can be scheduled
     */
    public boolean canScheduleExactAlarms() {
        if (!exactAlarmsSupported) {
            return false;
        }
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            return alarmManager.canScheduleExactAlarms();
        }
        
        return true;
    }
    
    /**
     * Get fallback window information
     * 
     * @return Fallback window info
     */
    public FallbackWindowInfo getFallbackWindowInfo() {
        return new FallbackWindowInfo(
            FALLBACK_WINDOW_START_MS,
            FALLBACK_WINDOW_LENGTH_MS,
            "±10 minutes"
        );
    }
    
    // MARK: - Alarm Scheduling
    
    /**
     * Schedule alarm with exact or fallback logic
     * 
     * @param pendingIntent PendingIntent to trigger
     * @param triggerTime Exact trigger time
     * @return true if scheduling was successful
     */
    public boolean scheduleAlarm(PendingIntent pendingIntent, long triggerTime) {
        try {
            Log.d(TAG, "Scheduling alarm for " + triggerTime);
            
            if (canScheduleExactAlarms()) {
                return scheduleExactAlarm(pendingIntent, triggerTime);
            } else {
                return scheduleWindowedAlarm(pendingIntent, triggerTime);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling alarm", e);
            return false;
        }
    }
    
    /**
     * Schedule exact alarm
     * 
     * @param pendingIntent PendingIntent to trigger
     * @param triggerTime Exact trigger time
     * @return true if scheduling was successful
     */
    private boolean scheduleExactAlarm(PendingIntent pendingIntent, long triggerTime) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
                Log.i(TAG, "Exact alarm scheduled for " + triggerTime);
                return true;
            } else {
                alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
                Log.i(TAG, "Exact alarm scheduled for " + triggerTime + " (pre-M)");
                return true;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling exact alarm", e);
            return false;
        }
    }
    
    /**
     * Schedule windowed alarm as fallback
     * 
     * @param pendingIntent PendingIntent to trigger
     * @param triggerTime Target trigger time
     * @return true if scheduling was successful
     */
    private boolean scheduleWindowedAlarm(PendingIntent pendingIntent, long triggerTime) {
        try {
            // Calculate window start time (10 minutes before target)
            long windowStartTime = triggerTime + FALLBACK_WINDOW_START_MS;
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                alarmManager.setWindow(AlarmManager.RTC_WAKEUP, windowStartTime, FALLBACK_WINDOW_LENGTH_MS, pendingIntent);
                Log.i(TAG, "Windowed alarm scheduled: target=" + triggerTime + ", window=" + windowStartTime + " to " + (windowStartTime + FALLBACK_WINDOW_LENGTH_MS));
                return true;
            } else {
                // Fallback to inexact alarm on older versions
                alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
                Log.i(TAG, "Inexact alarm scheduled for " + triggerTime + " (pre-KitKat)");
                return true;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling windowed alarm", e);
            return false;
        }
    }
    
    // MARK: - Permission Management
    
    /**
     * Request exact alarm permission
     * 
     * @return true if permission request was initiated
     */
    public boolean requestExactAlarmPermission() {
        if (!exactAlarmsSupported) {
            Log.w(TAG, "Exact alarms not supported on this device");
            return false;
        }
        
        if (exactAlarmsEnabled) {
            Log.d(TAG, "Exact alarms already enabled");
            return true;
        }
        
        try {
            // Open exact alarm settings
            Intent intent = new Intent(EXACT_ALARM_SETTINGS_ACTION);
            intent.setPackage(EXACT_ALARM_SETTINGS_PACKAGE);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            
            context.startActivity(intent);
            Log.i(TAG, "Exact alarm permission request initiated");
            return true;
            
        } catch (Exception e) {
            Log.e(TAG, "Error requesting exact alarm permission", e);
            return false;
        }
    }
    
    /**
     * Open exact alarm settings
     * 
     * @return true if settings were opened
     */
    public boolean openExactAlarmSettings() {
        try {
            Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            
            context.startActivity(intent);
            Log.i(TAG, "Exact alarm settings opened");
            return true;
            
        } catch (Exception e) {
            Log.e(TAG, "Error opening exact alarm settings", e);
            return false;
        }
    }
    
    /**
     * Check if exact alarm permission is granted
     * 
     * @return true if permission is granted
     */
    public boolean hasExactAlarmPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            return context.checkSelfPermission(PERMISSION_SCHEDULE_EXACT_ALARM) == PackageManager.PERMISSION_GRANTED;
        }
        return true; // Always available on older versions
    }
    
    // MARK: - Reboot and Time Change Recovery
    
    /**
     * Handle system reboot
     * 
     * This method should be called when the system boots to restore
     * scheduled alarms that were lost during reboot.
     */
    public void handleSystemReboot() {
        try {
            Log.i(TAG, "Handling system reboot - restoring scheduled alarms");
            
            // Re-schedule all pending notifications
            scheduler.restoreScheduledNotifications();
            
            Log.i(TAG, "System reboot handling completed");
            
        } catch (Exception e) {
            Log.e(TAG, "Error handling system reboot", e);
        }
    }
    
    /**
     * Handle time change
     * 
     * This method should be called when the system time changes
     * to adjust scheduled alarms accordingly.
     */
    public void handleTimeChange() {
        try {
            Log.i(TAG, "Handling time change - adjusting scheduled alarms");
            
            // Re-schedule all pending notifications with adjusted times
            scheduler.adjustScheduledNotifications();
            
            Log.i(TAG, "Time change handling completed");
            
        } catch (Exception e) {
            Log.e(TAG, "Error handling time change", e);
        }
    }
    
    // MARK: - Status Classes
    
    /**
     * Exact alarm status information
     */
    public static class ExactAlarmStatus {
        public final boolean supported;
        public final boolean enabled;
        public final boolean canSchedule;
        public final FallbackWindowInfo fallbackWindow;
        
        public ExactAlarmStatus(boolean supported, boolean enabled, boolean canSchedule, FallbackWindowInfo fallbackWindow) {
            this.supported = supported;
            this.enabled = enabled;
            this.canSchedule = canSchedule;
            this.fallbackWindow = fallbackWindow;
        }
        
        @Override
        public String toString() {
            return String.format("ExactAlarmStatus{supported=%s, enabled=%s, canSchedule=%s, fallbackWindow=%s}", 
                supported, enabled, canSchedule, fallbackWindow);
        }
    }
    
    /**
     * Fallback window information
     */
    public static class FallbackWindowInfo {
        public final long startMs;
        public final long lengthMs;
        public final String description;
        
        public FallbackWindowInfo(long startMs, long lengthMs, String description) {
            this.startMs = startMs;
            this.lengthMs = lengthMs;
            this.description = description;
        }
        
        @Override
        public String toString() {
            return String.format("FallbackWindowInfo{start=%dms, length=%dms, description='%s'}", 
                startMs, lengthMs, description);
        }
    }
}
