/**
 * DailyNotificationFetcher.java
 * 
 * Handles background content fetching for daily notifications
 * Implements the prefetch step of the prefetch → cache → schedule → display pipeline
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

package org.timesafari.dailynotification;

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

import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;

/**
 * Manages background content fetching for daily notifications
 * 
 * This class implements the prefetch step of the offline-first pipeline.
 * It schedules background work to fetch content before it's needed,
 * with proper timeout handling and fallback mechanisms.
 */
public class DailyNotificationFetcher {
    
    private static final String TAG = "DailyNotificationFetcher";
    private static final String WORK_TAG_FETCH = "daily_notification_fetch";
    private static final String WORK_TAG_MAINTENANCE = "daily_notification_maintenance";
    
    private static final int NETWORK_TIMEOUT_MS = 30000; // 30 seconds
    private static final int MAX_RETRY_ATTEMPTS = 3;
    private static final long RETRY_DELAY_MS = 60000; // 1 minute
    
    private final Context context;
    private final DailyNotificationStorage storage; // Deprecated path (kept for transitional read paths)
    private final org.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage; // Preferred path
    private final WorkManager workManager;
    
    // ETag manager for efficient fetching
    private final DailyNotificationETagManager etagManager;
    
    /**
     * Constructor
     * 
     * @param context Application context
     * @param storage Storage instance for saving fetched content
     */
    public DailyNotificationFetcher(Context context, DailyNotificationStorage storage) {
        this(context, storage, null);
    }

    public DailyNotificationFetcher(Context context,
                                    DailyNotificationStorage storage,
                                    org.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage) {
        this.context = context;
        this.storage = storage;
        this.roomStorage = roomStorage;
        this.workManager = WorkManager.getInstance(context);
        this.etagManager = new DailyNotificationETagManager(storage);
        
        Log.d(TAG, "DailyNotificationFetcher initialized with ETag support");
    }
    
    /**
     * Schedule a background fetch for content
     * 
     * @param fetchTime When to fetch the content (already calculated, typically 5 minutes before notification)
     */
    public void scheduleFetch(long fetchTime) {
        try {
            Log.d(TAG, "Scheduling background fetch for time: " + fetchTime);
            
            long currentTime = System.currentTimeMillis();
            long delayMs = fetchTime - currentTime;
            
            Log.d(TAG, "DN|FETCH_SCHEDULING fetch_time=" + fetchTime + 
                      " current=" + currentTime + 
                      " delay_ms=" + delayMs);
            
            if (fetchTime > currentTime) {
                // Create work data - we need to calculate the notification time (fetchTime + 5 minutes)
                long scheduledTime = fetchTime + TimeUnit.MINUTES.toMillis(5);
                
                Data inputData = new Data.Builder()
                    .putLong("scheduled_time", scheduledTime)
                    .putLong("fetch_time", fetchTime)
                    .putInt("retry_count", 0)
                    .build();
                
                // Create unique work name based on scheduled time to prevent duplicate fetches
                // Use scheduled time rounded to nearest minute to handle multiple notifications
                // scheduled close together
                long scheduledTimeMinutes = scheduledTime / (60 * 1000);
                String workName = "fetch_" + scheduledTimeMinutes;
                
                // Create one-time work request
                OneTimeWorkRequest fetchWork = new OneTimeWorkRequest.Builder(
                    DailyNotificationFetchWorker.class)
                    .setInputData(inputData)
                    .addTag(WORK_TAG_FETCH)
                    .setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
                    .build();
                
                // Use unique work name with REPLACE policy (newer fetch replaces older)
                // This prevents duplicate fetch workers for the same scheduled time
                workManager.enqueueUniqueWork(
                    workName,
                    ExistingWorkPolicy.REPLACE,
                    fetchWork
                );
                
                Log.i(TAG, "DN|WORK_ENQUEUED work_id=" + fetchWork.getId().toString() + 
                          " fetch_at=" + fetchTime + 
                          " work_name=" + workName +
                          " delay_ms=" + delayMs +
                          " delay_minutes=" + (delayMs / 60000.0));
                Log.i(TAG, "Background fetch scheduled successfully");
                
            } else {
                Log.w(TAG, "DN|FETCH_PAST_TIME fetch_time=" + fetchTime + 
                          " current=" + currentTime + 
                          " past_by_ms=" + (currentTime - fetchTime));
                scheduleImmediateFetch();
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling background fetch", e);
            // Fallback to immediate fetch
            scheduleImmediateFetch();
        }
    }
    
    /**
     * Schedule an immediate fetch (fallback)
     */
    public void scheduleImmediateFetch() {
        try {
            Log.d(TAG, "Scheduling immediate fetch");
            
            Data inputData = new Data.Builder()
                .putLong("scheduled_time", System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))
                .putLong("fetch_time", System.currentTimeMillis())
                .putInt("retry_count", 0)
                .putBoolean("immediate", true)
                .build();
            
            OneTimeWorkRequest fetchWork = new OneTimeWorkRequest.Builder(
                DailyNotificationFetchWorker.class)
                .setInputData(inputData)
                .addTag(WORK_TAG_FETCH)
                .build();
            
            workManager.enqueue(fetchWork);
            
            Log.i(TAG, "Immediate fetch scheduled successfully");
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling immediate fetch", e);
        }
    }
    
    /**
     * Fetch content immediately (synchronous)
     * 
     * @return Fetched notification content or null if failed
     */
    public NotificationContent fetchContentImmediately() {
        try {
            Log.d(TAG, "Fetching content immediately");
            
            // Check if we should fetch new content
            if (!storage.shouldFetchNewContent()) {
                Log.d(TAG, "Content fetch not needed yet");
                return storage.getLastNotification();
            }
            
            // Attempt to fetch from network
            NotificationContent content = fetchFromNetwork();
            
            if (content != null) {
                // Save to Room storage (authoritative)
                saveToRoomIfAvailable(content);
                // Save to legacy storage for transitional compatibility
                try {
                    storage.saveNotificationContent(content);
                    storage.setLastFetchTime(System.currentTimeMillis());
                } catch (Exception legacyErr) {
                    Log.w(TAG, "Legacy storage save failed (continuing): " + legacyErr.getMessage());
                }
                
                Log.i(TAG, "Content fetched and saved successfully");
                return content;
                
            } else {
                // Fallback to cached content
                Log.w(TAG, "Network fetch failed, using cached content");
                return getFallbackContent();
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error during immediate content fetch", e);
            return getFallbackContent();
        }
    }

    /**
     * Persist fetched content to Room storage when available
     */
    private void saveToRoomIfAvailable(NotificationContent content) {
        if (roomStorage == null) {
            return;
        }
        try {
            org.timesafari.dailynotification.entities.NotificationContentEntity entity =
                new org.timesafari.dailynotification.entities.NotificationContentEntity(
                    content.getId() != null ? content.getId() : java.util.UUID.randomUUID().toString(),
                    "1.0.0",
                    null,
                    "daily",
                    content.getTitle(),
                    content.getBody(),
                    content.getScheduledTime(),
                    java.time.ZoneId.systemDefault().getId()
                );
            entity.priority = mapPriority(content.getPriority());
            try {
                java.lang.reflect.Method isVibration = NotificationContent.class.getDeclaredMethod("isVibration");
                Object vib = isVibration.invoke(content);
                if (vib instanceof Boolean) {
                    entity.vibrationEnabled = (Boolean) vib;
                }
            } catch (Exception ignored) { }
            entity.soundEnabled = content.isSound();
            entity.mediaUrl = content.getMediaUrl();
            entity.deliveryStatus = "pending";
            roomStorage.saveNotificationContent(entity);
        } catch (Throwable t) {
            Log.w(TAG, "Room storage save failed: " + t.getMessage(), t);
        }
    }

    private int mapPriority(String priority) {
        if (priority == null) return 0;
        switch (priority) {
            case "max":
            case "high":
                return 2;
            case "low":
            case "min":
                return -1;
            default:
                return 0;
        }
    }
    
    /**
     * Fetch content from network with ETag support
     * 
     * @return Fetched content or null if failed
     */
    private NotificationContent fetchFromNetwork() {
        try {
            Log.d(TAG, "Fetching content from network with ETag support");
            
            // Get content endpoint URL
            String contentUrl = getContentEndpoint();
            
            // Make conditional request with ETag
            DailyNotificationETagManager.ConditionalRequestResult result = 
                etagManager.makeConditionalRequest(contentUrl);
            
            if (result.success) {
                if (result.isFromCache) {
                    Log.d(TAG, "Content not modified (304) - using cached content");
                    return storage.getLastNotification();
                } else {
                    Log.d(TAG, "New content available (200) - parsing response");
                    return parseNetworkResponse(result.content);
                }
            } else {
                Log.w(TAG, "Conditional request failed: " + result.error);
                return null;
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error during network fetch with ETag", e);
            return null;
        }
    }
    
    /**
     * Parse network response into notification content
     * 
     * @param connection HTTP connection with response
     * @return Parsed notification content or null if parsing failed
     */
    private NotificationContent parseNetworkResponse(HttpURLConnection connection) {
        try {
            // This is a simplified parser - in production you'd use a proper JSON parser
            // For now, we'll create a placeholder content
            
            NotificationContent content = new NotificationContent();
            content.setTitle("Daily Update");
            content.setBody("Your daily notification is ready");
            content.setScheduledTime(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
            // fetchedAt is set in constructor, no need to set it again
            
            return content;
            
        } catch (Exception e) {
            Log.e(TAG, "Error parsing network response", e);
            return null;
        }
    }
    
    /**
     * Parse network response string into notification content
     * 
     * @param responseString Response content as string
     * @return Parsed notification content or null if parsing failed
     */
    private NotificationContent parseNetworkResponse(String responseString) {
        try {
            Log.d(TAG, "Parsing network response string");
            
            // This is a simplified parser - in production you'd use a proper JSON parser
            // For now, we'll create a placeholder content
            
            NotificationContent content = new NotificationContent();
            content.setTitle("Daily Update");
            content.setBody("Your daily notification is ready");
            content.setScheduledTime(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
            // fetchedAt is set in constructor, no need to set it again
            
            Log.d(TAG, "Network response parsed successfully");
            return content;
            
        } catch (Exception e) {
            Log.e(TAG, "Error parsing network response string", e);
            return null;
        }
    }
    
    /**
     * Get fallback content when network fetch fails
     * 
     * @return Fallback notification content
     */
    private NotificationContent getFallbackContent() {
        try {
            // Try to get last known good content
            NotificationContent lastContent = storage.getLastNotification();
            
            if (lastContent != null && !lastContent.isStale()) {
                Log.d(TAG, "Using last known good content as fallback");
                return lastContent;
            }
            
            // Create emergency fallback content
            Log.w(TAG, "Creating emergency fallback content");
            return createEmergencyFallbackContent();
            
        } catch (Exception e) {
            Log.e(TAG, "Error getting fallback content", e);
            return createEmergencyFallbackContent();
        }
    }
    
    /**
     * Create emergency fallback content
     * 
     * @return Emergency notification content
     */
    private NotificationContent createEmergencyFallbackContent() {
        NotificationContent content = new NotificationContent();
        content.setTitle("Daily Update");
        content.setBody("🌅 Good morning! Ready to make today amazing?");
        content.setScheduledTime(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
        // fetchedAt is set in constructor, no need to set it again
        content.setPriority("default");
        content.setSound(true);
        
        return content;
    }
    
    /**
     * Get the content endpoint URL
     * 
     * @return Content endpoint URL
     */
    private String getContentEndpoint() {
        // This would typically come from configuration
        // For now, return a placeholder
        return "https://api.timesafari.com/daily-content";
    }
    
    /**
     * Schedule maintenance work
     */
    public void scheduleMaintenance() {
        try {
            Log.d(TAG, "Scheduling maintenance work");
            
            Data inputData = new Data.Builder()
                .putLong("maintenance_time", System.currentTimeMillis())
                .build();
            
            OneTimeWorkRequest maintenanceWork = new OneTimeWorkRequest.Builder(
                DailyNotificationMaintenanceWorker.class)
                .setInputData(inputData)
                .addTag(WORK_TAG_MAINTENANCE)
                .setInitialDelay(TimeUnit.HOURS.toMillis(2), TimeUnit.MILLISECONDS)
                .build();
            
            workManager.enqueue(maintenanceWork);
            
            Log.i(TAG, "Maintenance work scheduled successfully");
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling maintenance work", e);
        }
    }
    
    /**
     * Cancel all scheduled fetch work
     */
    public void cancelAllFetchWork() {
        try {
            Log.d(TAG, "Cancelling all fetch work");
            
            workManager.cancelAllWorkByTag(WORK_TAG_FETCH);
            workManager.cancelAllWorkByTag(WORK_TAG_MAINTENANCE);
            
            Log.i(TAG, "All fetch work cancelled");
            
        } catch (Exception e) {
            Log.e(TAG, "Error cancelling fetch work", e);
        }
    }
    
    /**
     * Check if fetch work is scheduled
     * 
     * @return true if fetch work is scheduled
     */
    public boolean isFetchWorkScheduled() {
        // This would check WorkManager for pending work
        // For now, return a placeholder
        return false;
    }
    
    /**
     * Get fetch statistics
     * 
     * @return Fetch statistics as a string
     */
    public String getFetchStats() {
        return String.format("Last fetch: %d, Fetch work scheduled: %s",
            storage.getLastFetchTime(),
            isFetchWorkScheduled() ? "yes" : "no");
    }
    
    /**
     * Get ETag manager for external access
     * 
     * @return ETag manager instance
     */
    public DailyNotificationETagManager getETagManager() {
        return etagManager;
    }
    
    /**
     * Get network efficiency metrics
     * 
     * @return Network metrics
     */
    public DailyNotificationETagManager.NetworkMetrics getNetworkMetrics() {
        return etagManager.getMetrics();
    }
    
    /**
     * Get ETag cache statistics
     * 
     * @return Cache statistics
     */
    public DailyNotificationETagManager.CacheStatistics getCacheStatistics() {
        return etagManager.getCacheStatistics();
    }
    
    /**
     * Clean expired ETags
     */
    public void cleanExpiredETags() {
        etagManager.cleanExpiredETags();
    }
    
    /**
     * Reset network metrics
     */
    public void resetNetworkMetrics() {
        etagManager.resetMetrics();
    }
}
