/**
 * DailyNotificationReceiver.java
 * 
 * Broadcast receiver for handling scheduled notification alarms
 * Displays notifications when scheduled time is reached
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

package com.timesafari.dailynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Trace;
import android.util.Log;

import androidx.core.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

/**
 * Broadcast receiver for daily notification alarms
 * 
 * This receiver is triggered by AlarmManager when it's time to display
 * a notification. It retrieves the notification content from storage
 * and displays it to the user.
 */
public class DailyNotificationReceiver extends BroadcastReceiver {
    
    private static final String TAG = "DailyNotificationReceiver";
    private static final String CHANNEL_ID = "timesafari.daily";
    private static final String EXTRA_NOTIFICATION_ID = "notification_id";
    
    /**
     * Handle broadcast intent when alarm triggers
     * 
     * Ultra-lightweight receiver that only parses intent and enqueues work.
     * All heavy operations (storage, JSON, scheduling) are moved to WorkManager.
     * 
     * @param context Application context
     * @param intent Broadcast intent
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        Trace.beginSection("DN:onReceive");
        try {
            Log.d(TAG, "DN|RECEIVE_START action=" + intent.getAction());
            
            String action = intent.getAction();
            if (action == null) {
                Log.w(TAG, "DN|RECEIVE_ERR null_action");
                return;
            }
            
            if ("com.timesafari.daily.NOTIFICATION".equals(action)) {
                // Parse intent and enqueue work - keep receiver ultra-light
                String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
                if (notificationId == null) {
                    Log.w(TAG, "DN|RECEIVE_ERR missing_id");
                    return;
                }
                
                // Enqueue work immediately - don't block receiver
                // Pass the full intent to extract static reminder extras
                enqueueNotificationWork(context, notificationId, intent);
                Log.d(TAG, "DN|RECEIVE_OK enqueued=" + notificationId);
                
            } else if ("com.timesafari.daily.DISMISS".equals(action)) {
                // Handle dismissal - also lightweight
                String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
                if (notificationId != null) {
                    enqueueDismissalWork(context, notificationId);
                    Log.d(TAG, "DN|DISMISS_OK enqueued=" + notificationId);
                }
            } else {
                Log.w(TAG, "DN|RECEIVE_ERR unknown_action=" + action);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "DN|RECEIVE_ERR exception=" + e.getMessage(), e);
        } finally {
            Trace.endSection();
        }
    }
    
    /**
     * Enqueue notification processing work to WorkManager with deduplication
     * 
     * Uses unique work name based on notification ID to prevent duplicate
     * work items from being enqueued for the same notification. WorkManager's
     * enqueueUniqueWork automatically prevents duplicates when using the same
     * work name.
     * 
     * @param context Application context
     * @param notificationId ID of notification to process
     * @param intent Intent containing notification data (may include static reminder extras)
     */
    private void enqueueNotificationWork(Context context, String notificationId, Intent intent) {
        try {
            // Create unique work name based on notification ID to prevent duplicates
            // WorkManager will automatically skip if work with this name already exists
            String workName = "display_" + notificationId;
            
            // Extract static reminder extras from intent if present
            // Static reminders have title/body in Intent extras, not in storage
            boolean isStaticReminder = intent.getBooleanExtra("is_static_reminder", false);
            String title = intent.getStringExtra("title");
            String body = intent.getStringExtra("body");
            boolean sound = intent.getBooleanExtra("sound", true);
            boolean vibration = intent.getBooleanExtra("vibration", true);
            String priority = intent.getStringExtra("priority");
            if (priority == null) {
                priority = "normal";
            }
            
            Data.Builder dataBuilder = new Data.Builder()
                .putString("notification_id", notificationId)
                .putString("action", "display")
                .putBoolean("is_static_reminder", isStaticReminder);
            
            // Add static reminder data if present
            if (isStaticReminder && title != null && body != null) {
                dataBuilder.putString("title", title)
                    .putString("body", body)
                    .putBoolean("sound", sound)
                    .putBoolean("vibration", vibration)
                    .putString("priority", priority);
                Log.d(TAG, "DN|WORK_ENQUEUE static_reminder id=" + notificationId);
            }
            
            Data inputData = dataBuilder.build();
            
            OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(DailyNotificationWorker.class)
                .setInputData(inputData)
                .addTag("daily_notification_display")
                .build();
            
            // Use unique work name with KEEP policy (don't replace if exists)
            // This prevents duplicate work items from being enqueued even if
            // the receiver is triggered multiple times for the same notification
            WorkManager.getInstance(context).enqueueUniqueWork(
                workName,
                ExistingWorkPolicy.KEEP,
                workRequest
            );
            
            Log.d(TAG, "DN|WORK_ENQUEUE display=" + notificationId + " work_name=" + workName);
            
        } catch (Exception e) {
            Log.e(TAG, "DN|WORK_ENQUEUE_ERR display=" + notificationId + " err=" + e.getMessage(), e);
        }
    }
    
    /**
     * Enqueue notification dismissal work to WorkManager with deduplication
     * 
     * Uses unique work name based on notification ID to prevent duplicate
     * dismissal work items.
     * 
     * @param context Application context
     * @param notificationId ID of notification to dismiss
     */
    private void enqueueDismissalWork(Context context, String notificationId) {
        try {
            // Create unique work name based on notification ID to prevent duplicates
            String workName = "dismiss_" + notificationId;
            
            Data inputData = new Data.Builder()
                .putString("notification_id", notificationId)
                .putString("action", "dismiss")
                .build();
            
            OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(DailyNotificationWorker.class)
                .setInputData(inputData)
                .addTag("daily_notification_dismiss")
                .build();
            
            // Use unique work name with REPLACE policy (allow new dismissal to replace pending)
            WorkManager.getInstance(context).enqueueUniqueWork(
                workName,
                ExistingWorkPolicy.REPLACE,
                workRequest
            );
            
            Log.d(TAG, "DN|WORK_ENQUEUE dismiss=" + notificationId + " work_name=" + workName);
            
        } catch (Exception e) {
            Log.e(TAG, "DN|WORK_ENQUEUE_ERR dismiss=" + notificationId + " err=" + e.getMessage(), e);
        }
    }
    
    /**
     * Handle notification intent
     * 
     * @param context Application context
     * @param intent Intent containing notification data
     */
    private void handleNotificationIntent(Context context, Intent intent) {
        try {
            String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
            
            if (notificationId == null) {
                Log.w(TAG, "Notification ID not found in intent");
                return;
            }
            
            Log.d(TAG, "Processing notification: " + notificationId);
            
            // Get notification content from storage
            DailyNotificationStorage storage = new DailyNotificationStorage(context);
            NotificationContent content = storage.getNotificationContent(notificationId);
            
            if (content == null) {
                Log.w(TAG, "Notification content not found: " + notificationId);
                return;
            }
            
            // Check if notification is ready to display
            if (!content.isReadyToDisplay()) {
                Log.d(TAG, "Notification not ready to display yet: " + notificationId);
                return;
            }
            
            // JIT Freshness Re-check (Soft TTL)
            content = performJITFreshnessCheck(context, content);
            
            // Display the notification
            displayNotification(context, content);
            
            // Schedule next notification if this is a recurring daily notification
            scheduleNextNotification(context, content);
            
            Log.i(TAG, "Notification processed successfully: " + notificationId);
            
        } catch (Exception e) {
            Log.e(TAG, "Error handling notification intent", e);
        }
    }
    
    /**
     * Perform JIT (Just-In-Time) freshness re-check for notification content
     * 
     * This implements a soft TTL mechanism that attempts to refresh stale content
     * just before displaying the notification. If the refresh fails or content
     * is not stale, the original content is returned.
     * 
     * @param context Application context
     * @param content Original notification content
     * @return Updated content if refresh succeeded, original content otherwise
     */
    private NotificationContent performJITFreshnessCheck(Context context, NotificationContent content) {
        try {
            // Check if content is stale (older than 6 hours for JIT check)
            long currentTime = System.currentTimeMillis();
            long age = currentTime - content.getFetchedAt();
            long staleThreshold = 6 * 60 * 60 * 1000; // 6 hours in milliseconds
            
            if (age < staleThreshold) {
                Log.d(TAG, "Content is fresh (age: " + (age / 1000 / 60) + " minutes), skipping JIT refresh");
                return content;
            }
            
            Log.i(TAG, "Content is stale (age: " + (age / 1000 / 60) + " minutes), attempting JIT refresh");
            
            // Attempt to fetch fresh content
            DailyNotificationFetcher fetcher = new DailyNotificationFetcher(context, new DailyNotificationStorage(context));
            
            // Attempt immediate fetch for fresh content
            NotificationContent freshContent = fetcher.fetchContentImmediately();
            
            if (freshContent != null && freshContent.getTitle() != null && !freshContent.getTitle().isEmpty()) {
                Log.i(TAG, "JIT refresh succeeded, using fresh content");
                
                // Update the original content with fresh data while preserving the original ID and scheduled time
                String originalId = content.getId();
                long originalScheduledTime = content.getScheduledTime();
                
                content.setTitle(freshContent.getTitle());
                content.setBody(freshContent.getBody());
                content.setSound(freshContent.isSound());
                content.setPriority(freshContent.getPriority());
                content.setUrl(freshContent.getUrl());
                content.setMediaUrl(freshContent.getMediaUrl());
                content.setScheduledTime(originalScheduledTime); // Preserve original scheduled time
                // Note: fetchedAt remains unchanged to preserve original fetch time
                
                // Save updated content to storage
                DailyNotificationStorage storage = new DailyNotificationStorage(context);
                storage.saveNotificationContent(content);
                
                return content;
            } else {
                Log.w(TAG, "JIT refresh failed or returned empty content, using original content");
                return content;
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error during JIT freshness check", e);
            return content; // Return original content on error
        }
    }
    
    /**
     * Display the notification to the user
     * 
     * @param context Application context
     * @param content Notification content to display
     */
    private void displayNotification(Context context, NotificationContent content) {
        try {
            Log.d(TAG, "Displaying notification: " + content.getId());
            
            NotificationManager notificationManager = 
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            
            if (notificationManager == null) {
                Log.e(TAG, "NotificationManager not available");
                return;
            }
            
            // Create notification builder
            NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(android.R.drawable.ic_dialog_info)
                .setContentTitle(content.getTitle())
                .setContentText(content.getBody())
                .setPriority(getNotificationPriority(content.getPriority()))
                .setAutoCancel(true)
                .setCategory(NotificationCompat.CATEGORY_REMINDER);
            
            // Add sound if enabled
            if (content.isSound()) {
                builder.setDefaults(NotificationCompat.DEFAULT_SOUND);
            }
            
            // Add click action if URL is available
            if (content.getUrl() != null && !content.getUrl().isEmpty()) {
                Intent clickIntent = new Intent(Intent.ACTION_VIEW);
                clickIntent.setData(android.net.Uri.parse(content.getUrl()));
                
                PendingIntent clickPendingIntent = PendingIntent.getActivity(
                    context,
                    content.getId().hashCode(),
                    clickIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
                );
                
                builder.setContentIntent(clickPendingIntent);
            }
            
            // Add dismiss action
            Intent dismissIntent = new Intent(context, DailyNotificationReceiver.class);
            dismissIntent.setAction("com.timesafari.daily.DISMISS");
            dismissIntent.putExtra(EXTRA_NOTIFICATION_ID, content.getId());
            
            PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(
                context,
                content.getId().hashCode() + 1000, // Different request code
                dismissIntent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
            );
            
            builder.addAction(
                android.R.drawable.ic_menu_close_clear_cancel,
                "Dismiss",
                dismissPendingIntent
            );
            
            // Build and display notification
            int notificationId = content.getId().hashCode();
            notificationManager.notify(notificationId, builder.build());
            
            Log.i(TAG, "Notification displayed successfully: " + content.getId());
            
        } catch (Exception e) {
            Log.e(TAG, "Error displaying notification", e);
        }
    }
    
    /**
     * Schedule the next occurrence of this daily notification
     * 
     * Uses centralized NotifyReceiver.scheduleExactNotification() with ROLLOVER_ON_FIRE source
     * to ensure idempotence and proper logging
     * 
     * @param context Application context
     * @param content Current notification content
     */
    private void scheduleNextNotification(Context context, NotificationContent content) {
        try {
            Log.d(TAG, "Scheduling next notification for: " + content.getId());
            
            // Extract scheduleId from notificationId pattern or use fallback
            // Notification IDs are often "daily_${scheduleId}" 
            String scheduleId = null;
            String cronExpression = null;
            long nextScheduledTime = content.getScheduledTime() + (24 * 60 * 60 * 1000);
            
            // Try to extract scheduleId from notificationId (e.g., "daily_1764578136269")
            String notificationId = content.getId();
            if (notificationId != null && notificationId.startsWith("daily_")) {
                scheduleId = notificationId; // Use notificationId as scheduleId
            } else {
                scheduleId = "daily_rollover_" + System.currentTimeMillis();
            }
            
            // Calculate cron from current scheduled time (extract hour:minute)
            try {
                java.util.Calendar cal = java.util.Calendar.getInstance();
                cal.setTimeInMillis(content.getScheduledTime());
                int hour = cal.get(java.util.Calendar.HOUR_OF_DAY);
                int minute = cal.get(java.util.Calendar.MINUTE);
                cronExpression = String.format("%d %d * * *", minute, hour);
                
                // Recalculate next run time from cron (tomorrow at same time)
                nextScheduledTime = calculateNextRunTimeFromCron(cronExpression);
            } catch (Exception e) {
                Log.w(TAG, "Failed to calculate cron from scheduled time, using default", e);
                cronExpression = "0 9 * * *"; // Default to 9 AM
            }
            
            // Create config for next notification
            com.timesafari.dailynotification.UserNotificationConfig config = 
                new com.timesafari.dailynotification.UserNotificationConfig(
                    true, // enabled
                    cronExpression,
                    content.getTitle() != null ? content.getTitle() : "Daily Notification",
                    content.getBody(),
                    content.isSound(),
                    true, // vibration
                    content.getPriority() != null ? content.getPriority() : "normal"
                );
            
            // Use centralized scheduling function with ROLLOVER_ON_FIRE source
            com.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
                context,
                nextScheduledTime,
                config,
                false, // isStaticReminder
                null, // reminderId
                scheduleId,
                com.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE
            );
            
            Log.i(TAG, "Next notification scheduled via centralized function: scheduleId=" + scheduleId);
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling next notification", e);
        }
    }
    
    /**
     * Helper to convert HH:mm time to cron expression
     */
    private String convertTimeToCron(String clockTime) {
        try {
            String[] parts = clockTime.split(":");
            if (parts.length == 2) {
                int hour = Integer.parseInt(parts[0]);
                int minute = Integer.parseInt(parts[1]);
                return String.format("%d %d * * *", minute, hour);
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to parse clockTime: " + clockTime, e);
        }
        return "0 9 * * *"; // Default to 9 AM
    }
    
    /**
     * Helper to calculate next run time from cron expression
     */
    private long calculateNextRunTimeFromCron(String cron) {
        try {
            String[] parts = cron.trim().split("\\s+");
            if (parts.length >= 2) {
                int minute = Integer.parseInt(parts[0]);
                int hour = Integer.parseInt(parts[1]);
                
                java.util.Calendar calendar = java.util.Calendar.getInstance();
                long now = calendar.getTimeInMillis();
                
                calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
                calendar.set(java.util.Calendar.MINUTE, minute);
                calendar.set(java.util.Calendar.SECOND, 0);
                calendar.set(java.util.Calendar.MILLISECOND, 0);
                
                long nextRun = calendar.getTimeInMillis();
                if (nextRun <= now) {
                    calendar.add(java.util.Calendar.DAY_OF_YEAR, 1);
                    nextRun = calendar.getTimeInMillis();
                }
                return nextRun;
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to calculate next run time from cron: " + cron, e);
        }
        // Fallback: 24 hours from now
        return System.currentTimeMillis() + (24 * 60 * 60 * 1000L);
    }
    
    /**
     * Get notification priority constant
     * 
     * @param priority Priority string from content
     * @return NotificationCompat priority constant
     */
    private int getNotificationPriority(String priority) {
        if (priority == null) {
            return NotificationCompat.PRIORITY_DEFAULT;
        }
        
        switch (priority.toLowerCase()) {
            case "high":
                return NotificationCompat.PRIORITY_HIGH;
            case "low":
                return NotificationCompat.PRIORITY_LOW;
            case "min":
                return NotificationCompat.PRIORITY_MIN;
            case "max":
                return NotificationCompat.PRIORITY_MAX;
            default:
                return NotificationCompat.PRIORITY_DEFAULT;
        }
    }
    
    /**
     * Handle notification dismissal
     * 
     * @param context Application context
     * @param notificationId ID of dismissed notification
     */
    private void handleNotificationDismissal(Context context, String notificationId) {
        try {
            Log.d(TAG, "Handling notification dismissal: " + notificationId);
            
            // Remove from storage
            DailyNotificationStorage storage = new DailyNotificationStorage(context);
            storage.removeNotification(notificationId);
            
            // Cancel any pending alarms
            DailyNotificationScheduler scheduler = new DailyNotificationScheduler(
                context,
                (android.app.AlarmManager) context.getSystemService(Context.ALARM_SERVICE)
            );
            scheduler.cancelNotification(notificationId);
            
            Log.i(TAG, "Notification dismissed successfully: " + notificationId);
            
        } catch (Exception e) {
            Log.e(TAG, "Error handling notification dismissal", e);
        }
    }
}
