/**
 * DailyNotificationETagManager.swift
 * 
 * iOS ETag Manager for efficient content fetching
 * Implements ETag headers, 304 response handling, and conditional requests
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

import Foundation

/**
 * Manages ETag headers and conditional requests for efficient content fetching
 * 
 * This class implements the critical ETag functionality:
 * - Stores ETag values for each content URL
 * - Sends conditional requests with If-None-Match headers
 * - Handles 304 Not Modified responses
 * - Tracks network efficiency metrics
 * - Provides fallback for ETag failures
 */
class DailyNotificationETagManager {
    
    // MARK: - Constants
    
    private static let TAG = "DailyNotificationETagManager"
    
    // HTTP headers
    private static let HEADER_ETAG = "ETag"
    private static let HEADER_IF_NONE_MATCH = "If-None-Match"
    private static let HEADER_LAST_MODIFIED = "Last-Modified"
    private static let HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"
    
    // HTTP status codes
    private static let HTTP_NOT_MODIFIED = 304
    private static let HTTP_OK = 200
    
    // Request timeout
    private static let REQUEST_TIMEOUT_SECONDS: TimeInterval = 12.0
    
    // ETag cache TTL
    private static let ETAG_CACHE_TTL_SECONDS: TimeInterval = 24 * 60 * 60 // 24 hours
    
    // MARK: - Properties
    
    private let storage: DailyNotificationStorage
    private let logger: DailyNotificationLogger
    
    // ETag cache: URL -> ETagInfo
    private var etagCache: [String: ETagInfo] = [:]
    private let cacheQueue = DispatchQueue(label: "etag.cache", attributes: .concurrent)
    
    // Network metrics
    private let metrics = NetworkMetrics()
    
    // MARK: - Initialization
    
    /**
     * Constructor
     * 
     * @param storage Storage instance for persistence
     * @param logger Logger instance for debugging
     */
    init(storage: DailyNotificationStorage, logger: DailyNotificationLogger) {
        self.storage = storage
        self.logger = logger
        
        // Load ETag cache from storage
        loadETagCache()
        
        logger.log(.debug, "\(Self.TAG): ETagManager initialized with \(etagCache.count) cached ETags")
    }
    
    // MARK: - ETag Cache Management
    
    /**
     * Load ETag cache from storage
     */
    private func loadETagCache() {
        do {
            logger.log(.debug, "(Self.TAG): Loading ETag cache from storage")
            
            // This would typically load from SQLite or UserDefaults
            // For now, we'll start with an empty cache
            logger.log(.debug, "(Self.TAG): ETag cache loaded from storage")
            
        } catch {
            logger.log(.error, "(Self.TAG): Error loading ETag cache: \(error)")
        }
    }
    
    /**
     * Save ETag cache to storage
     */
    private func saveETagCache() {
        do {
            logger.log(.debug, "(Self.TAG): Saving ETag cache to storage")
            
            // This would typically save to SQLite or UserDefaults
            // For now, we'll just log the action
            logger.log(.debug, "(Self.TAG): ETag cache saved to storage")
            
        } catch {
            logger.log(.error, "(Self.TAG): Error saving ETag cache: \(error)")
        }
    }
    
    /**
     * Get ETag for URL
     * 
     * @param url Content URL
     * @return ETag value or nil if not cached
     */
    func getETag(for url: String) -> String? {
        return cacheQueue.sync {
            let info = etagCache[url]
            if let info = info, !info.isExpired() {
                return info.etag
            }
            return nil
        }
    }
    
    /**
     * Set ETag for URL
     * 
     * @param url Content URL
     * @param etag ETag value
     */
    func setETag(for url: String, etag: String) {
        do {
            logger.log(.debug, "(Self.TAG): Setting ETag for \(url): \(etag)")
            
            let info = ETagInfo(etag: etag, timestamp: Date())
            
            cacheQueue.async(flags: .barrier) {
                self.etagCache[url] = info
                self.saveETagCache()
            }
            
            logger.log(.debug, "(Self.TAG): ETag set successfully")
            
        } catch {
            logger.log(.error, "(Self.TAG): Error setting ETag: \(error)")
        }
    }
    
    /**
     * Remove ETag for URL
     * 
     * @param url Content URL
     */
    func removeETag(for url: String) {
        do {
            logger.log(.debug, "(Self.TAG): Removing ETag for \(url)")
            
            cacheQueue.async(flags: .barrier) {
                self.etagCache.removeValue(forKey: url)
                self.saveETagCache()
            }
            
            logger.log(.debug, "(Self.TAG): ETag removed successfully")
            
        } catch {
            logger.log(.error, "(Self.TAG): Error removing ETag: \(error)")
        }
    }
    
    /**
     * Clear all ETags
     */
    func clearETags() {
        do {
            logger.log(.debug, "(Self.TAG): Clearing all ETags")
            
            cacheQueue.async(flags: .barrier) {
                self.etagCache.removeAll()
                self.saveETagCache()
            }
            
            logger.log(.debug, "(Self.TAG): All ETags cleared")
            
        } catch {
            logger.log(.error, "(Self.TAG): Error clearing ETags: \(error)")
        }
    }
    
    // MARK: - Conditional Requests
    
    /**
     * Make conditional request with ETag
     * 
     * @param url Content URL
     * @return ConditionalRequestResult with response data
     */
    func makeConditionalRequest(to url: String) async throws -> ConditionalRequestResult {
        do {
            logger.log(.debug, "(Self.TAG): Making conditional request to \(url)")
            
            // Get cached ETag
            let etag = getETag(for: url)
            
            // Create URL request
            guard let requestURL = URL(string: url) else {
                return ConditionalRequestResult.error("Invalid URL: \(url)")
            }
            
            var request = URLRequest(url: requestURL)
            request.timeoutInterval = DailyNotificationETagManager.REQUEST_TIMEOUT_SECONDS
            
            // Set conditional headers
            if let etag = etag {
                request.setValue(etag, forHTTPHeaderField: DailyNotificationETagManager.HEADER_IF_NONE_MATCH)
                logger.log(.debug, "(Self.TAG): Added If-None-Match header: \(etag)")
            }
            
            // Set user agent
            request.setValue("DailyNotificationPlugin/1.0.0", forHTTPHeaderField: "User-Agent")
            
            // Execute request synchronously (for background tasks)
            let (data, response) = try await URLSession.shared.data(for: request)
            
            guard let httpResponse = response as? HTTPURLResponse else {
                return ConditionalRequestResult.error("Invalid response type")
            }
            
            // Handle response
            let result = handleResponse(httpResponse, data: data, url: url)
            
            // Update metrics
            metrics.recordRequest(url: url, responseCode: httpResponse.statusCode, fromCache: result.isFromCache)
            
            logger.log(.info, "(Self.TAG): Conditional request completed: \(httpResponse.statusCode) (cached: \(result.isFromCache))")
            
            return result
            
        } catch {
            logger.log(.error, "(Self.TAG): Error making conditional request: \(error)")
            metrics.recordError(url: url, error: error.localizedDescription)
            return ConditionalRequestResult.error(error.localizedDescription)
        }
    }
    
    /**
     * Handle HTTP response
     * 
     * @param response HTTP response
     * @param data Response data
     * @param url Request URL
     * @return ConditionalRequestResult
     */
    private func handleResponse(_ response: HTTPURLResponse, data: Data, url: String) -> ConditionalRequestResult {
        do {
            switch response.statusCode {
            case DailyNotificationETagManager.HTTP_NOT_MODIFIED:
                logger.log(.debug, "(Self.TAG): 304 Not Modified - using cached content")
                return ConditionalRequestResult.notModified()
                
            case DailyNotificationETagManager.HTTP_OK:
                logger.log(.debug, "(Self.TAG): 200 OK - new content available")
                return handleOKResponse(response, data: data, url: url)
                
            default:
                logger.log(.warning, "\(Self.TAG): Unexpected response code: \(response.statusCode)")
                return ConditionalRequestResult.error("Unexpected response code: \(response.statusCode)")
            }
            
        } catch {
            logger.log(.error, "(Self.TAG): Error handling response: \(error)")
            return ConditionalRequestResult.error(error.localizedDescription)
        }
    }
    
    /**
     * Handle 200 OK response
     * 
     * @param response HTTP response
     * @param data Response data
     * @param url Request URL
     * @return ConditionalRequestResult with new content
     */
    private func handleOKResponse(_ response: HTTPURLResponse, data: Data, url: String) -> ConditionalRequestResult {
        do {
            // Get new ETag
            let newETag = response.allHeaderFields[DailyNotificationETagManager.HEADER_ETAG] as? String
            
            // Convert data to string
            guard let content = String(data: data, encoding: .utf8) else {
                return ConditionalRequestResult.error("Unable to decode response data")
            }
            
            // Update ETag cache
            if let newETag = newETag {
                setETag(for: url, etag: newETag)
            }
            
            return ConditionalRequestResult.success(content: content, etag: newETag)
            
        } catch {
            logger.log(.error, "(Self.TAG): Error handling OK response: \(error)")
            return ConditionalRequestResult.error(error.localizedDescription)
        }
    }
    
    // MARK: - Network Metrics
    
    /**
     * Get network efficiency metrics
     * 
     * @return NetworkMetrics with current statistics
     */
    func getMetrics() -> NetworkMetrics {
        return metrics
    }
    
    /**
     * Reset network metrics
     */
    func resetMetrics() {
        metrics.reset()
        logger.log(.debug, "(Self.TAG): Network metrics reset")
    }
    
    // MARK: - Cache Management
    
    /**
     * Clean expired ETags
     */
    func cleanExpiredETags() {
        do {
            logger.log(.debug, "(Self.TAG): Cleaning expired ETags")
            
            let initialSize = etagCache.count
            
            cacheQueue.async(flags: .barrier) {
                self.etagCache = self.etagCache.filter { !$0.value.isExpired() }
            }
            
            let finalSize = etagCache.count
            
            if initialSize != finalSize {
                saveETagCache()
                logger.log(.info, "(Self.TAG): Cleaned \(initialSize - finalSize) expired ETags")
            }
            
        } catch {
            logger.log(.error, "(Self.TAG): Error cleaning expired ETags: \(error)")
        }
    }
    
    /**
     * Get cache statistics
     * 
     * @return CacheStatistics with cache info
     */
    func getCacheStatistics() -> CacheStatistics {
        let totalETags = etagCache.count
        let expiredETags = etagCache.values.filter { $0.isExpired() }.count
        let validETags = totalETags - expiredETags
        
        return CacheStatistics(totalETags: totalETags, expiredETags: expiredETags, validETags: validETags)
    }
    
    // MARK: - Data Classes
    
    /**
     * ETag information
     */
    private struct ETagInfo {
        let etag: String
        let timestamp: Date
        
        func isExpired() -> Bool {
            return Date().timeIntervalSince(timestamp) > DailyNotificationETagManager.ETAG_CACHE_TTL_SECONDS
        }
    }
    
    /**
     * Conditional request result
     */
    struct ConditionalRequestResult {
        let success: Bool
        let isFromCache: Bool
        let content: String?
        let etag: String?
        let error: String?
        
        static func success(content: String, etag: String?) -> ConditionalRequestResult {
            return ConditionalRequestResult(success: true, isFromCache: false, content: content, etag: etag, error: nil)
        }
        
        static func notModified() -> ConditionalRequestResult {
            return ConditionalRequestResult(success: true, isFromCache: true, content: nil, etag: nil, error: nil)
        }
        
        static func error(_ error: String) -> ConditionalRequestResult {
            return ConditionalRequestResult(success: false, isFromCache: false, content: nil, etag: nil, error: error)
        }
    }
    
    /**
     * Network metrics
     */
    class NetworkMetrics {
        var totalRequests: Int = 0
        var cachedResponses: Int = 0
        var networkResponses: Int = 0
        var errors: Int = 0
        
        func recordRequest(url: String, responseCode: Int, fromCache: Bool) {
            totalRequests += 1
            if fromCache {
                cachedResponses += 1
            } else {
                networkResponses += 1
            }
        }
        
        func recordError(url: String, error: String) {
            errors += 1
        }
        
        func reset() {
            totalRequests = 0
            cachedResponses = 0
            networkResponses = 0
            errors = 0
        }
        
        func getCacheHitRatio() -> Double {
            if totalRequests == 0 { return 0.0 }
            return Double(cachedResponses) / Double(totalRequests)
        }
    }
    
    /**
     * Cache statistics
     */
    struct CacheStatistics {
        let totalETags: Int
        let expiredETags: Int
        let validETags: Int
        
        var description: String {
            return "CacheStatistics{total=\(totalETags), expired=\(expiredETags), valid=\(validETags)}"
        }
    }
}
