/**
 * DailyNotificationRollingWindow.java
 * 
 * Rolling window safety for notification scheduling
 * Ensures today's notifications are always armed and tomorrow's are armed within iOS caps
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

package org.timesafari.dailynotification;

import android.content.Context;
import android.util.Log;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Manages rolling window safety for notification scheduling
 * 
 * This class implements the critical rolling window logic:
 * - Today's remaining notifications are always armed
 * - Tomorrow's notifications are armed only if within iOS capacity limits
 * - Automatic window maintenance as time progresses
 * - Platform-specific capacity management
 */
public class DailyNotificationRollingWindow {
    
    private static final String TAG = "DailyNotificationRollingWindow";
    
    // iOS notification limits
    private static final int IOS_MAX_PENDING_NOTIFICATIONS = 64;
    private static final int IOS_MAX_DAILY_NOTIFICATIONS = 20;
    
    // Android has no hard limits, but we use reasonable defaults
    private static final int ANDROID_MAX_PENDING_NOTIFICATIONS = 100;
    private static final int ANDROID_MAX_DAILY_NOTIFICATIONS = 50;
    
    // Window maintenance intervals
    private static final long WINDOW_MAINTENANCE_INTERVAL_MS = TimeUnit.MINUTES.toMillis(15);
    
    private final Context context;
    private final DailyNotificationScheduler scheduler;
    private final DailyNotificationTTLEnforcer ttlEnforcer;
    private final DailyNotificationStorage storage;
    private final boolean isIOSPlatform;
    
    // Window state
    private long lastMaintenanceTime = 0;
    private int currentPendingCount = 0;
    private int currentDailyCount = 0;
    
    /**
     * Constructor
     * 
     * @param context Application context
     * @param scheduler Notification scheduler
     * @param ttlEnforcer TTL enforcement instance
     * @param storage Storage instance
     * @param isIOSPlatform Whether running on iOS platform
     */
    public DailyNotificationRollingWindow(Context context, 
                                         DailyNotificationScheduler scheduler,
                                         DailyNotificationTTLEnforcer ttlEnforcer,
                                         DailyNotificationStorage storage,
                                         boolean isIOSPlatform) {
        this.context = context;
        this.scheduler = scheduler;
        this.ttlEnforcer = ttlEnforcer;
        this.storage = storage;
        this.isIOSPlatform = isIOSPlatform;
        
        Log.d(TAG, "Rolling window initialized for " + (isIOSPlatform ? "iOS" : "Android"));
    }
    
    /**
     * Maintain the rolling window by ensuring proper notification coverage
     * 
     * This method should be called periodically to maintain the rolling window:
     * - Arms today's remaining notifications
     * - Arms tomorrow's notifications if within capacity limits
     * - Updates window state and statistics
     */
    public void maintainRollingWindow() {
        try {
            long currentTime = System.currentTimeMillis();
            
            // Check if maintenance is needed
            if (currentTime - lastMaintenanceTime < WINDOW_MAINTENANCE_INTERVAL_MS) {
                Log.d(TAG, "Window maintenance not needed yet");
                return;
            }
            
            Log.d(TAG, "Starting rolling window maintenance");
            
            // Update current state
            updateWindowState();
            
            // Arm today's remaining notifications
            armTodaysRemainingNotifications();
            
            // Arm tomorrow's notifications if within capacity
            armTomorrowsNotificationsIfWithinCapacity();
            
            // Update maintenance time
            lastMaintenanceTime = currentTime;
            
            Log.i(TAG, String.format("Rolling window maintenance completed: pending=%d, daily=%d", 
                currentPendingCount, currentDailyCount));
            
        } catch (Exception e) {
            Log.e(TAG, "Error during rolling window maintenance", e);
        }
    }
    
    /**
     * Arm today's remaining notifications
     * 
     * Ensures all notifications for today that haven't fired yet are armed
     */
    private void armTodaysRemainingNotifications() {
        try {
            Log.d(TAG, "Arming today's remaining notifications");
            
            // Get today's date
            Calendar today = Calendar.getInstance();
            String todayDate = formatDate(today);
            
            // Get all notifications for today
            List<NotificationContent> todaysNotifications = getNotificationsForDate(todayDate);
            
            int armedCount = 0;
            int skippedCount = 0;
            
            for (NotificationContent notification : todaysNotifications) {
                // Check if notification is in the future
                if (notification.getScheduledTime() > System.currentTimeMillis()) {
                    
                    // Check TTL before arming
                    if (ttlEnforcer != null && !ttlEnforcer.validateBeforeArming(notification)) {
                        Log.w(TAG, "Skipping today's notification due to TTL: " + notification.getId());
                        skippedCount++;
                        continue;
                    }
                    
                    // Arm the notification
                    boolean armed = scheduler.scheduleNotification(notification);
                    if (armed) {
                        armedCount++;
                        currentPendingCount++;
                    } else {
                        Log.w(TAG, "Failed to arm today's notification: " + notification.getId());
                    }
                }
            }
            
            Log.i(TAG, String.format("Today's notifications: armed=%d, skipped=%d", armedCount, skippedCount));
            
        } catch (Exception e) {
            Log.e(TAG, "Error arming today's remaining notifications", e);
        }
    }
    
    /**
     * Arm tomorrow's notifications if within capacity limits
     * 
     * Only arms tomorrow's notifications if we're within platform-specific limits
     */
    private void armTomorrowsNotificationsIfWithinCapacity() {
        try {
            Log.d(TAG, "Checking capacity for tomorrow's notifications");
            
            // Check if we're within capacity limits
            if (!isWithinCapacityLimits()) {
                Log.w(TAG, "At capacity limit, skipping tomorrow's notifications");
                return;
            }
            
            // Get tomorrow's date
            Calendar tomorrow = Calendar.getInstance();
            tomorrow.add(Calendar.DAY_OF_MONTH, 1);
            String tomorrowDate = formatDate(tomorrow);
            
            // Get all notifications for tomorrow
            List<NotificationContent> tomorrowsNotifications = getNotificationsForDate(tomorrowDate);
            
            int armedCount = 0;
            int skippedCount = 0;
            
            for (NotificationContent notification : tomorrowsNotifications) {
                // Check TTL before arming
                if (ttlEnforcer != null && !ttlEnforcer.validateBeforeArming(notification)) {
                    Log.w(TAG, "Skipping tomorrow's notification due to TTL: " + notification.getId());
                    skippedCount++;
                    continue;
                }
                
                // Arm the notification
                boolean armed = scheduler.scheduleNotification(notification);
                if (armed) {
                    armedCount++;
                    currentPendingCount++;
                    currentDailyCount++;
                } else {
                    Log.w(TAG, "Failed to arm tomorrow's notification: " + notification.getId());
                }
                
                // Check capacity after each arm
                if (!isWithinCapacityLimits()) {
                    Log.w(TAG, "Reached capacity limit while arming tomorrow's notifications");
                    break;
                }
            }
            
            Log.i(TAG, String.format("Tomorrow's notifications: armed=%d, skipped=%d", armedCount, skippedCount));
            
        } catch (Exception e) {
            Log.e(TAG, "Error arming tomorrow's notifications", e);
        }
    }
    
    /**
     * Check if we're within platform-specific capacity limits
     * 
     * @return true if within limits
     */
    private boolean isWithinCapacityLimits() {
        int maxPending = isIOSPlatform ? IOS_MAX_PENDING_NOTIFICATIONS : ANDROID_MAX_PENDING_NOTIFICATIONS;
        int maxDaily = isIOSPlatform ? IOS_MAX_DAILY_NOTIFICATIONS : ANDROID_MAX_DAILY_NOTIFICATIONS;
        
        boolean withinPendingLimit = currentPendingCount < maxPending;
        boolean withinDailyLimit = currentDailyCount < maxDaily;
        
        Log.d(TAG, String.format("Capacity check: pending=%d/%d, daily=%d/%d, within=%s", 
            currentPendingCount, maxPending, currentDailyCount, maxDaily, 
            withinPendingLimit && withinDailyLimit));
        
        return withinPendingLimit && withinDailyLimit;
    }
    
    /**
     * Update window state by counting current notifications
     */
    private void updateWindowState() {
        try {
            Log.d(TAG, "Updating window state");
            
            // Count pending notifications
            currentPendingCount = countPendingNotifications();
            
            // Count today's notifications
            Calendar today = Calendar.getInstance();
            String todayDate = formatDate(today);
            currentDailyCount = countNotificationsForDate(todayDate);
            
            Log.d(TAG, String.format("Window state updated: pending=%d, daily=%d", 
                currentPendingCount, currentDailyCount));
            
        } catch (Exception e) {
            Log.e(TAG, "Error updating window state", e);
        }
    }
    
    /**
     * Count pending notifications
     * 
     * @return Number of pending notifications
     */
    private int countPendingNotifications() {
        try {
            long now = System.currentTimeMillis();
            int count = 0;

            List<NotificationContent> all = storage.getAllNotifications();
            for (NotificationContent n : all) {
                if (n.getScheduledTime() >= now) {
                    count++;
                }
            }
            return count;

        } catch (Exception e) {
            Log.e(TAG, "Error counting pending notifications", e);
            return 0;
        }
    }
    
    /**
     * Count notifications for a specific date
     * 
     * @param date Date in YYYY-MM-DD format
     * @return Number of notifications for the date
     */
    private int countNotificationsForDate(String date) {
        try {
            long[] bounds = dateBoundsMillis(date);
            long start = bounds[0];
            long end = bounds[1];

            int count = 0;
            List<NotificationContent> all = storage.getAllNotifications();
            for (NotificationContent n : all) {
                long t = n.getScheduledTime();
                if (t >= start && t < end) {
                    count++;
                }
            }
            return count;

        } catch (Exception e) {
            Log.e(TAG, "Error counting notifications for date: " + date, e);
            return 0;
        }
    }
    
    /**
     * Get notifications for a specific date
     * 
     * @param date Date in YYYY-MM-DD format
     * @return List of notifications for the date
     */
    private List<NotificationContent> getNotificationsForDate(String date) {
        try {
            long[] bounds = dateBoundsMillis(date);
            long start = bounds[0];
            long end = bounds[1];

            List<NotificationContent> results = new ArrayList<>();
            List<NotificationContent> all = storage.getAllNotifications();
            for (NotificationContent n : all) {
                long t = n.getScheduledTime();
                if (t >= start && t < end) {
                    results.add(n);
                }
            }
            return results;

        } catch (Exception e) {
            Log.e(TAG, "Error getting notifications for date: " + date, e);
            return new ArrayList<>();
        }
    }
    
    /**
     * Format date as YYYY-MM-DD
     * 
     * @param calendar Calendar instance
     * @return Formatted date string
     */
    private String formatDate(Calendar calendar) {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1; // Calendar months are 0-based
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        
        return String.format("%04d-%02d-%02d", year, month, day);
    }
    
    /**
     * Get date bounds in milliseconds for a given date string
     * 
     * @param yyyyMmDd Date in YYYY-MM-DD format
     * @return Array with [startMillis, endMillis]
     */
    private long[] dateBoundsMillis(String yyyyMmDd) {
        // yyyyMmDd: "YYYY-MM-DD"
        String[] parts = yyyyMmDd.split("-");
        int year = Integer.parseInt(parts[0]);
        int month = Integer.parseInt(parts[1]); // 1-12
        int day = Integer.parseInt(parts[2]);

        Calendar start = Calendar.getInstance();
        start.set(Calendar.YEAR, year);
        start.set(Calendar.MONTH, month - 1); // Calendar months are 0-based
        start.set(Calendar.DAY_OF_MONTH, day);
        start.set(Calendar.HOUR_OF_DAY, 0);
        start.set(Calendar.MINUTE, 0);
        start.set(Calendar.SECOND, 0);
        start.set(Calendar.MILLISECOND, 0);

        Calendar end = (Calendar) start.clone();
        end.add(Calendar.DAY_OF_MONTH, 1);

        return new long[] { start.getTimeInMillis(), end.getTimeInMillis() };
    }
    
    /**
     * Get rolling window statistics
     * 
     * @return Statistics string
     */
    public String getRollingWindowStats() {
        try {
            int maxPending = isIOSPlatform ? IOS_MAX_PENDING_NOTIFICATIONS : ANDROID_MAX_PENDING_NOTIFICATIONS;
            int maxDaily = isIOSPlatform ? IOS_MAX_DAILY_NOTIFICATIONS : ANDROID_MAX_DAILY_NOTIFICATIONS;
            
            return String.format("Rolling window stats: pending=%d/%d, daily=%d/%d, platform=%s", 
                currentPendingCount, maxPending, currentDailyCount, maxDaily, 
                isIOSPlatform ? "iOS" : "Android");
            
        } catch (Exception e) {
            Log.e(TAG, "Error getting rolling window stats", e);
            return "Error retrieving rolling window statistics";
        }
    }
    
    /**
     * Force window maintenance (for testing or manual triggers)
     */
    public void forceMaintenance() {
        Log.i(TAG, "Forcing rolling window maintenance");
        lastMaintenanceTime = 0; // Reset maintenance time
        maintainRollingWindow();
    }
    
    /**
     * Check if window maintenance is needed
     * 
     * @return true if maintenance is needed
     */
    public boolean isMaintenanceNeeded() {
        long currentTime = System.currentTimeMillis();
        return currentTime - lastMaintenanceTime >= WINDOW_MAINTENANCE_INTERVAL_MS;
    }
    
    /**
     * Get time until next maintenance
     * 
     * @return Milliseconds until next maintenance
     */
    public long getTimeUntilNextMaintenance() {
        long currentTime = System.currentTimeMillis();
        long nextMaintenanceTime = lastMaintenanceTime + WINDOW_MAINTENANCE_INTERVAL_MS;
        return Math.max(0, nextMaintenanceTime - currentTime);
    }
}
