# iOS Rollover Implementation — Edge Case Handling Plan

**Status**: Planning Phase  
**Priority**: Reliability-First  
**Author**: AI Assistant  
**Date**: 2025-01-27

## Objective

Implement Android-like automatic rollover for iOS notifications with comprehensive edge case handling to ensure reliability across all scenarios, including time changes, timezone changes, DST transitions, and race conditions.

---

## Edge Case Categories

### 1. **Time Changes**
- Manual clock adjustments (user changes device time)
- System clock corrections (NTP sync)
- Clock drift corrections
- Time jumps (forward/backward)

### 2. **Timezone Changes**
- User changes device timezone
- Automatic timezone detection changes
- Travel across timezones
- Timezone database updates

### 3. **DST Transitions**
- Spring forward (lose 1 hour)
- Fall back (gain 1 hour)
- DST rule changes
- Regions that don't observe DST

### 4. **Race Conditions**
- Multiple rollover attempts for same notification
- Concurrent scheduling operations
- App state transitions during rollover
- Background task conflicts

### 5. **System Events**
- Device reboots
- App termination
- Low memory conditions
- Background execution limits

### 6. **Notification System Edge Cases**
- Notification limit reached (64 pending)
- Notification delivery failures
- System notification queue issues
- Permission changes

---

## Detection Mechanisms

### A. Time Change Detection

**iOS Limitation**: iOS doesn't provide direct time change notifications like Android's `ACTION_TIME_CHANGED` broadcast.

**Solution**: Multi-layered detection:

1. **App Launch Detection**
   - Store last known system time on app exit
   - Compare on app launch
   - Detect significant time jumps (>5 minutes)

2. **Background Task Detection**
   - Store timestamp when scheduling notification
   - Compare with current time when background task runs
   - Detect time discrepancies

3. **Notification Delivery Detection**
   - Compare scheduled time with actual delivery time
   - Flag if delivery time is significantly different

4. **Periodic Validation**
   - Background task validates scheduled notifications
   - Checks if notification times are still valid
   - Adjusts if time change detected

### B. Timezone Change Detection

**iOS Limitation**: No direct timezone change notification.

**Solution**:

1. **Store Timezone on Schedule**
   - Save timezone identifier when scheduling
   - Store as part of notification metadata

2. **Compare on Access**
   - Check current timezone vs stored timezone
   - Detect changes on app launch, background tasks, rollover

3. **Recalculate on Change**
   - If timezone changed, recalculate all scheduled times
   - Maintain same local time (e.g., 9:00 AM stays 9:00 AM)

### C. DST Transition Detection

**Solution**: Use Calendar API for DST-aware calculations:

1. **Calendar-Based Calculation**
   - Use `Calendar.date(byAdding: .hour, value: 24, to:)` 
   - Automatically handles DST transitions
   - No manual DST detection needed

2. **Validation After Calculation**
   - Verify calculated time is exactly 24 hours later in local time
   - Log DST transitions for debugging
   - Handle edge cases (e.g., 2:00 AM → 3:00 AM spring forward)

### D. Duplicate Prevention

**Solution**: Multi-level idempotence checks:

1. **Database-Level Check**
   - Store rollover state per notification ID
   - Track last processed rollover time
   - Prevent duplicate rollover attempts

2. **Storage-Level Check**
   - Check for existing notifications at same scheduled time
   - Use tolerance window (1 minute) for DST shifts
   - Compare notification IDs and scheduled times

3. **System-Level Check**
   - Query `UNUserNotificationCenter` for pending notifications
   - Check if notification already scheduled
   - Cancel and reschedule if needed

4. **Request-Level Check**
   - Use unique notification IDs
   - Include timestamp in ID generation
   - Prevent ID collisions

---

## Handling Strategies

### Strategy 1: Time Change Handling

**When Detected**:
1. **Validate All Scheduled Notifications**
   - Check if scheduled times are still valid
   - Recalculate if time change was significant
   - Cancel invalid notifications

2. **Recalculate Rollover Times**
   - If time changed, recalculate next notification time
   - Use DST-safe calculation
   - Maintain same local time (e.g., 9:00 AM)

3. **Reschedule Affected Notifications**
   - Cancel old notifications
   - Schedule with corrected times
   - Update storage with new times

4. **Log Time Change Event**
   - Record time change in history
   - Log old time, new time, delta
   - Track which notifications were affected

**Implementation**:
```swift
func handleTimeChange(
    lastKnownTime: Int64,
    currentTime: Int64,
    scheduledNotifications: [NotificationContent]
) async {
    let timeDelta = abs(currentTime - lastKnownTime)
    
    // Only handle significant time changes (>5 minutes)
    guard timeDelta > (5 * 60 * 1000) else {
        return // Ignore small clock adjustments
    }
    
    // Recalculate all scheduled notifications
    for notification in scheduledNotifications {
        // Recalculate using original scheduled time
        let originalScheduledTime = notification.scheduledTime
        let newScheduledTime = recalculateScheduledTime(
            originalTime: originalScheduledTime,
            timeDelta: timeDelta
        )
        
        // Cancel old notification
        await scheduler.cancelNotification(id: notification.id)
        
        // Reschedule with corrected time
        let updatedNotification = NotificationContent(
            id: notification.id,
            title: notification.title,
            body: notification.body,
            scheduledTime: newScheduledTime,
            fetchedAt: notification.fetchedAt,
            url: notification.url,
            payload: notification.payload,
            etag: notification.etag
        )
        
        await scheduler.scheduleNotification(updatedNotification)
    }
    
    // Record time change in history
    await recordTimeChangeEvent(
        oldTime: lastKnownTime,
        newTime: currentTime,
        delta: timeDelta
    )
}
```

### Strategy 2: Timezone Change Handling

**When Detected**:
1. **Detect Timezone Change**
   - Compare current timezone with stored timezone
   - Detect on app launch, background tasks, rollover

2. **Recalculate All Scheduled Times**
   - Maintain same local time (e.g., 9:00 AM)
   - Convert to new timezone
   - Update scheduled times

3. **Reschedule All Notifications**
   - Cancel existing notifications
   - Schedule with new times
   - Update storage

**Implementation**:
```swift
func handleTimezoneChange(
    oldTimezone: TimeZone,
    newTimezone: TimeZone,
    scheduledNotifications: [NotificationContent]
) async {
    // Extract local time from each notification
    for notification in scheduledNotifications {
        // Get local time components (hour, minute)
        let scheduledDate = notification.getScheduledTimeAsDate()
        let calendar = Calendar.current
        let hour = calendar.component(.hour, from: scheduledDate)
        let minute = calendar.component(.minute, from: scheduledDate)
        
        // Recalculate in new timezone
        let newScheduledTime = calculateNextOccurrence(
            hour: hour,
            minute: minute,
            timezone: newTimezone
        )
        
        // Cancel old notification
        await scheduler.cancelNotification(id: notification.id)
        
        // Reschedule with new time
        let updatedNotification = NotificationContent(
            id: notification.id,
            title: notification.title,
            body: notification.body,
            scheduledTime: newScheduledTime,
            fetchedAt: notification.fetchedAt,
            url: notification.url,
            payload: notification.payload,
            etag: notification.etag
        )
        
        await scheduler.scheduleNotification(updatedNotification)
    }
    
    // Update stored timezone
    await storage.saveTimezone(newTimezone.identifier)
}
```

### Strategy 3: DST Transition Handling

**When Detected**:
1. **Use Calendar API**
   - `Calendar.date(byAdding: .hour, value: 24, to:)` handles DST automatically
   - No manual DST detection needed

2. **Validate Calculation**
   - Verify 24-hour addition results in correct local time
   - Log DST transitions for debugging
   - Handle edge cases (2:00 AM → 3:00 AM)

3. **Handle Edge Cases**
   - Spring forward: Notification might be scheduled for 2:00 AM (doesn't exist)
   - Fall back: Notification might be scheduled for 2:00 AM (occurs twice)
   - Use system's automatic handling

**Implementation**:
```swift
func calculateNextScheduledTime(_ currentScheduledTime: Int64) -> Int64 {
    let calendar = Calendar.current
    let currentDate = Date(timeIntervalSince1970: Double(currentScheduledTime) / 1000.0)
    
    // Add 24 hours (handles DST automatically)
    guard let nextDate = calendar.date(byAdding: .hour, value: 24, to: currentDate) else {
        // Fallback to simple addition
        return currentScheduledTime + (24 * 60 * 60 * 1000)
    }
    
    // Validate: Ensure it's exactly 24 hours later in local time
    let currentHour = calendar.component(.hour, from: currentDate)
    let currentMinute = calendar.component(.minute, from: currentDate)
    let nextHour = calendar.component(.hour, from: nextDate)
    let nextMinute = calendar.component(.minute, from: nextDate)
    
    // Log DST transitions
    if currentHour != nextHour || currentMinute != nextMinute {
        print("\(Self.TAG): DST transition detected: \(currentHour):\(currentMinute) -> \(nextHour):\(nextMinute)")
    }
    
    return Int64(nextDate.timeIntervalSince1970 * 1000)
}
```

### Strategy 4: Duplicate Prevention

**Multi-Level Checks**:

1. **Rollover State Tracking**
   - Store rollover state in database
   - Track last processed notification ID
   - Prevent duplicate rollover attempts

2. **Time-Based Deduplication**
   - Check for existing notifications at same scheduled time
   - Use tolerance window (1 minute) for DST shifts
   - Compare notification IDs

3. **System-Level Verification**
   - Query `UNUserNotificationCenter` for pending notifications
   - Check if notification already scheduled
   - Cancel and reschedule if needed

**Implementation**:
```swift
func scheduleNextNotification(
    _ content: NotificationContent,
    storage: DailyNotificationStorage?,
    fetcher: DailyNotificationFetcher? = nil
) async -> Bool {
    // Check 1: Rollover state tracking
    if let storage = storage {
        let lastRolloverTime = await storage.getLastRolloverTime(for: content.id)
        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
        
        // If rollover was processed recently (< 1 hour ago), skip
        if let lastTime = lastRolloverTime,
           (currentTime - lastTime) < (60 * 60 * 1000) {
            print("\(Self.TAG): RESCHEDULE_SKIP id=\(content.id) already_processed")
            return false
        }
    }
    
    // Calculate next time
    let nextScheduledTime = calculateNextScheduledTime(content.scheduledTime)
    
    // Check 2: Storage-level duplicate check
    if let storage = storage {
        let existingNotifications = storage.getAllNotifications()
        let toleranceMs: Int64 = 60 * 1000 // 1 minute tolerance
        
        for existing in existingNotifications {
            if abs(existing.scheduledTime - nextScheduledTime) <= toleranceMs {
                print("\(Self.TAG): RESCHEDULE_DUPLICATE id=\(content.id) existing_id=\(existing.id)")
                return false
            }
        }
    }
    
    // Check 3: System-level duplicate check
    let pendingNotifications = await notificationCenter.pendingNotificationRequests()
    for pending in pendingNotifications {
        if let trigger = pending.trigger as? UNCalendarNotificationTrigger,
           let nextDate = trigger.nextTriggerDate() {
            let pendingTime = Int64(nextDate.timeIntervalSince1970 * 1000)
            let toleranceMs: Int64 = 60 * 1000
            
            if abs(pendingTime - nextScheduledTime) <= toleranceMs {
                print("\(Self.TAG): RESCHEDULE_DUPLICATE id=\(content.id) system_pending_id=\(pending.identifier)")
                return false
            }
        }
    }
    
    // All checks passed, proceed with scheduling
    // ... (rest of scheduling logic)
    
    // Mark rollover as processed
    await storage?.saveLastRolloverTime(for: content.id, time: Int64(Date().timeIntervalSince1970 * 1000))
    
    return true
}
```

### Strategy 5: Race Condition Prevention

**Solution**: Use serial queue + state tracking

1. **Serial Queue for Rollover**
   - Use dedicated serial queue for rollover operations
   - Prevent concurrent rollover attempts
   - Ensure atomic operations

2. **State Machine**
   - Track rollover state (pending, processing, completed)
   - Prevent duplicate processing
   - Handle failures gracefully

3. **Locking Mechanism**
   - Use actor or serial queue for thread safety
   - Prevent race conditions
   - Ensure atomic updates

**Implementation**:
```swift
actor RolloverCoordinator {
    private var processingNotifications: Set<String> = []
    private let scheduler: DailyNotificationScheduler
    private let storage: DailyNotificationStorage
    
    func processRollover(for notificationId: String) async -> Bool {
        // Check if already processing
        if processingNotifications.contains(notificationId) {
            print("RolloverCoordinator: Already processing \(notificationId)")
            return false
        }
        
        // Mark as processing
        processingNotifications.insert(notificationId)
        defer {
            processingNotifications.remove(notificationId)
        }
        
        // Perform rollover
        // ... (rollover logic)
        
        return true
    }
}
```

---

## Implementation Architecture

### Component 1: TimeChangeDetector

**Purpose**: Detect time changes and trigger recovery

**Responsibilities**:
- Store last known system time
- Compare on app launch/background tasks
- Detect significant time jumps
- Trigger time change recovery

**Location**: `ios/Plugin/DailyNotificationTimeChangeDetector.swift`

### Component 2: TimezoneChangeDetector

**Purpose**: Detect timezone changes and trigger recalculation

**Responsibilities**:
- Store current timezone
- Compare on access
- Detect timezone changes
- Trigger timezone change recovery

**Location**: `ios/Plugin/DailyNotificationTimezoneChangeDetector.swift`

### Component 3: RolloverCoordinator

**Purpose**: Coordinate rollover operations with duplicate prevention

**Responsibilities**:
- Manage rollover state
- Prevent duplicate rollovers
- Coordinate multiple detection mechanisms
- Handle race conditions

**Location**: `ios/Plugin/DailyNotificationRolloverCoordinator.swift`

### Component 4: Enhanced Recovery Manager

**Purpose**: Extend existing recovery manager with time/timezone change handling

**Responsibilities**:
- Integrate time change detection
- Integrate timezone change detection
- Coordinate with rollover coordinator
- Handle all edge cases

**Location**: `ios/Plugin/DailyNotificationReactivationManager.swift` (enhance existing)

---

## Testing Strategy

### Test Category 1: Time Changes

1. **Manual Clock Adjustment**
   - Set device time forward 1 hour
   - Verify notifications rescheduled correctly
   - Verify rollover still works

2. **Clock Jump Forward**
   - Set device time forward 24 hours
   - Verify all notifications recalculated
   - Verify no duplicates created

3. **Clock Jump Backward**
   - Set device time backward 1 hour
   - Verify notifications still valid
   - Verify rollover works correctly

### Test Category 2: Timezone Changes

1. **Timezone Change**
   - Change device timezone
   - Verify notifications rescheduled to same local time
   - Verify rollover maintains local time

2. **Travel Simulation**
   - Change timezone multiple times
   - Verify notifications always at correct local time
   - Verify no duplicates

### Test Category 3: DST Transitions

1. **Spring Forward**
   - Test on DST spring forward day
   - Verify 24-hour calculation handles correctly
   - Verify notification fires at correct time

2. **Fall Back**
   - Test on DST fall back day
   - Verify 24-hour calculation handles correctly
   - Verify no duplicate notifications

### Test Category 4: Race Conditions

1. **Concurrent Rollover**
   - Trigger multiple rollover attempts simultaneously
   - Verify only one succeeds
   - Verify no duplicates

2. **App State Transitions**
   - Trigger rollover during app state changes
   - Verify rollover completes correctly
   - Verify no data corruption

### Test Category 5: Edge Cases

1. **Notification Limit**
   - Schedule 64 notifications
   - Verify rollover still works
   - Verify proper error handling

2. **Permission Changes**
   - Revoke notification permission
   - Verify graceful failure
   - Verify recovery when permission restored

---

## Implementation Phases

### Phase 1: Core Rollover (Week 1)
- ✅ DST-safe time calculation
- ✅ Basic rollover scheduling
- ✅ Duplicate prevention (storage + system level)
- ✅ AppDelegate integration

### Phase 2: Edge Case Detection (Week 2)
- ✅ Time change detection
- ✅ Timezone change detection
- ✅ Rollover state tracking
- ✅ Race condition prevention

### Phase 3: Recovery Integration (Week 3)
- ✅ Time change recovery
- ✅ Timezone change recovery
- ✅ Enhanced recovery manager
- ✅ Background task integration

### Phase 4: Testing & Validation (Week 4)
- ✅ Comprehensive edge case testing
- ✅ Real device testing
- ✅ DST transition testing
- ✅ Performance optimization

---

## Success Criteria

1. **Reliability**: 99%+ rollover success rate across all edge cases
2. **No Duplicates**: Zero duplicate notifications in any scenario
3. **Time Accuracy**: Notifications fire within 1 minute of scheduled time
4. **Recovery**: All edge cases handled gracefully with recovery
5. **Performance**: Rollover completes in <1 second
6. **Logging**: Comprehensive logging for debugging

---

## Risk Mitigation

### Risk 1: iOS Background Execution Limits
**Mitigation**: Multiple detection mechanisms (delegate + background + recovery)

### Risk 2: Time Change Detection Reliability
**Mitigation**: Store timestamps, compare on every access, validate scheduled times

### Risk 3: Race Conditions
**Mitigation**: Serial queue, state machine, actor-based coordination

### Risk 4: DST Edge Cases
**Mitigation**: Use Calendar API, validate calculations, comprehensive testing

### Risk 5: Notification System Limits
**Mitigation**: Check pending count, handle gracefully, provide user feedback

---

## Next Steps

1. **Review & Approve Plan** (This document)
2. **Create Implementation Tasks** (Break down into specific tasks)
3. **Implement Phase 1** (Core rollover functionality)
4. **Test Phase 1** (Basic functionality)
5. **Implement Phase 2** (Edge case detection)
6. **Test Phase 2** (Edge case scenarios)
7. **Implement Phase 3** (Recovery integration)
8. **Test Phase 3** (Recovery scenarios)
9. **Final Testing** (Comprehensive validation)
10. **Documentation** (Update docs with edge case handling)

---

## References

- Android Implementation: `DailyNotificationWorker.java` (scheduleNextNotification)
- Android Time Change Handling: `DailyNotificationRebootRecoveryManager.java`
- iOS Calendar API: `Calendar.date(byAdding:to:)` documentation
- iOS Background Tasks: `BGTaskScheduler` documentation
- iOS Notifications: `UNUserNotificationCenter` documentation
