/**
 * DailyNotificationErrorHandler.swift
 * 
 * iOS Error Handler for comprehensive error management
 * Implements error categorization, retry logic, and telemetry
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

import Foundation

/**
 * Manages comprehensive error handling with categorization, retry logic, and telemetry
 * 
 * This class implements the critical error handling functionality:
 * - Categorizes errors by type, code, and severity
 * - Implements exponential backoff retry logic
 * - Tracks error metrics and telemetry
 * - Provides debugging information
 * - Manages retry state and limits
 */
class DailyNotificationErrorHandler {
    
    // MARK: - Constants
    
    private static let TAG = "DailyNotificationErrorHandler"
    
    // Retry configuration
    private static let DEFAULT_MAX_RETRIES = 3
    private static let DEFAULT_BASE_DELAY_SECONDS: TimeInterval = 1.0
    private static let DEFAULT_MAX_DELAY_SECONDS: TimeInterval = 30.0
    private static let DEFAULT_BACKOFF_MULTIPLIER: Double = 2.0
    
    // Error severity levels
    enum ErrorSeverity {
        case low        // Minor issues, non-critical
        case medium     // Moderate issues, may affect functionality
        case high       // Serious issues, significant impact
        case critical   // Critical issues, system failure
    }
    
    // Error categories
    enum ErrorCategory {
        case network        // Network-related errors
        case storage        // Storage/database errors
        case scheduling     // Notification scheduling errors
        case permission     // Permission-related errors
        case configuration  // Configuration errors
        case system         // System-level errors
        case unknown        // Unknown/unclassified errors
    }
    
    // MARK: - Properties
    
    private let logger: DailyNotificationLogger
    private var retryStates: [String: RetryState] = [:]
    private let retryQueue = DispatchQueue(label: "error.retry", attributes: .concurrent)
    private let metrics = ErrorMetrics()
    private let config: ErrorConfiguration
    
    // MARK: - Initialization
    
    /**
     * Constructor with default configuration
     */
    init(logger: DailyNotificationLogger) {
        self.logger = logger
        self.config = ErrorConfiguration()
        
        logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): ErrorHandler initialized with max retries: \(config.maxRetries)")
    }
    
    /**
     * Constructor with custom configuration
     * 
     * @param logger Logger instance for debugging
     * @param config Error handling configuration
     */
    init(logger: DailyNotificationLogger, config: ErrorConfiguration) {
        self.logger = logger
        self.config = config
        
        logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): ErrorHandler initialized with max retries: \(config.maxRetries)")
    }
    
    // MARK: - Error Handling
    
    /**
     * Handle error with automatic retry logic
     * 
     * @param operationId Unique identifier for the operation
     * @param error Error to handle
     * @param retryable Whether this error is retryable
     * @return ErrorResult with handling information
     */
    func handleError(operationId: String, error: Error, retryable: Bool) -> ErrorResult {
        do {
            logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Handling error for operation: \(operationId)")
            
            // Categorize error
            let errorInfo = categorizeError(error)
            
            // Update metrics
            metrics.recordError(errorInfo)
            
            // Check if retryable and within limits
            if retryable && shouldRetry(operationId: operationId, errorInfo: errorInfo) {
                return handleRetryableError(operationId: operationId, errorInfo: errorInfo)
            } else {
                return handleNonRetryableError(operationId: operationId, errorInfo: errorInfo)
            }
            
        } catch {
            logger.log(.error, "\(DailyNotificationErrorHandler.TAG): Error in error handler: \(error)")
            return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
        }
    }
    
    /**
     * Handle error with custom retry configuration
     * 
     * @param operationId Unique identifier for the operation
     * @param error Error to handle
     * @param retryConfig Custom retry configuration
     * @return ErrorResult with handling information
     */
    func handleError(operationId: String, error: Error, retryConfig: RetryConfiguration) -> ErrorResult {
        do {
            logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Handling error with custom retry config for operation: \(operationId)")
            
            // Categorize error
            let errorInfo = categorizeError(error)
            
            // Update metrics
            metrics.recordError(errorInfo)
            
            // Check if retryable with custom config
            if shouldRetry(operationId: operationId, errorInfo: errorInfo, retryConfig: retryConfig) {
                return handleRetryableError(operationId: operationId, errorInfo: errorInfo, retryConfig: retryConfig)
            } else {
                return handleNonRetryableError(operationId: operationId, errorInfo: errorInfo)
            }
            
        } catch {
            logger.log(.error, "DailyNotificationErrorHandler.TAG: Error in error handler with custom config: \(error)")
            return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
        }
    }
    
    // MARK: - Error Categorization
    
    /**
     * Categorize error by type, code, and severity
     * 
     * @param error Error to categorize
     * @return ErrorInfo with categorization
     */
    private func categorizeError(_ error: Error) -> ErrorInfo {
        do {
            let category = determineCategory(error)
            let errorCode = determineErrorCode(error)
            let severity = determineSeverity(error, category: category)
            
            let errorInfo = ErrorInfo(
                error: error,
                category: category,
                errorCode: errorCode,
                severity: severity,
                timestamp: Date()
            )
            
            logger.log(.debug, "DailyNotificationErrorHandler.TAG: Error categorized: \(errorInfo)")
            return errorInfo
            
        } catch {
            logger.log(.error, "DailyNotificationErrorHandler.TAG: Error during categorization: \(error)")
            return ErrorInfo(
                error: error,
                category: .unknown,
                errorCode: "CATEGORIZATION_FAILED",
                severity: .high,
                timestamp: Date()
            )
        }
    }
    
    /**
     * Determine error category based on error type
     * 
     * @param error Error to analyze
     * @return ErrorCategory
     */
    private func determineCategory(_ error: Error) -> ErrorCategory {
        let errorType = String(describing: type(of: error))
        let errorMessage = error.localizedDescription
        
        // Network errors
        if errorType.contains("URLError") || errorType.contains("Network") || 
           errorType.contains("Connection") || errorType.contains("Timeout") {
            return .network
        }
        
        // Storage errors
        if errorType.contains("SQLite") || errorType.contains("Database") || 
           errorType.contains("Storage") || errorType.contains("File") {
            return .storage
        }
        
        // Permission errors
        if errorType.contains("Security") || errorType.contains("Permission") || 
           errorMessage.contains("permission") {
            return .permission
        }
        
        // Configuration errors
        if errorType.contains("IllegalArgument") || errorType.contains("Configuration") || 
           errorMessage.contains("config") {
            return .configuration
        }
        
        // System errors
        if errorType.contains("OutOfMemory") || errorType.contains("StackOverflow") || 
           errorType.contains("Runtime") {
            return .system
        }
        
        return .unknown
    }
    
    /**
     * Determine error code based on error details
     * 
     * @param error Error to analyze
     * @return Error code string
     */
    private func determineErrorCode(_ error: Error) -> String {
        let errorType = String(describing: type(of: error))
        let errorMessage = error.localizedDescription
        
        // Generate error code based on type and message
        if !errorMessage.isEmpty {
            return "\(errorType)_\(errorMessage.hashValue)"
        } else {
            return "\(errorType)_\(Date().timeIntervalSince1970)"
        }
    }
    
    /**
     * Determine error severity based on error and category
     * 
     * @param error Error to analyze
     * @param category Error category
     * @return ErrorSeverity
     */
    private func determineSeverity(_ error: Error, category: ErrorCategory) -> ErrorSeverity {
        let errorType = String(describing: type(of: error))
        
        // Critical errors
        if errorType.contains("OutOfMemory") || errorType.contains("StackOverflow") {
            return .critical
        }
        
        // High severity errors
        if category == .system || category == .storage {
            return .high
        }
        
        // Medium severity errors
        if category == .network || category == .permission {
            return .medium
        }
        
        // Low severity errors
        return .low
    }
    
    // MARK: - Retry Logic
    
    /**
     * Check if error should be retried
     * 
     * @param operationId Operation identifier
     * @param errorInfo Error information
     * @return true if should retry
     */
    private func shouldRetry(operationId: String, errorInfo: ErrorInfo) -> Bool {
        return shouldRetry(operationId: operationId, errorInfo: errorInfo, retryConfig: nil)
    }
    
    /**
     * Check if error should be retried with custom config
     * 
     * @param operationId Operation identifier
     * @param errorInfo Error information
     * @param retryConfig Custom retry configuration
     * @return true if should retry
     */
    private func shouldRetry(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> Bool {
        do {
            // Get retry state
            var attemptCount: Int = 0
            retryQueue.sync {
                if retryStates[operationId] == nil {
                    retryStates[operationId] = RetryState()
                }
                let state = retryStates[operationId]!
                attemptCount = state.attemptCount
            }
            
            // Check retry limits
            let maxRetries = retryConfig?.maxRetries ?? config.maxRetries
            if attemptCount >= maxRetries {
                logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Max retries exceeded for operation: \(operationId)")
                return false
            }
            
            // Check if error is retryable based on category
            let isRetryable = isErrorRetryable(errorInfo.category)
            
            logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Should retry: \(isRetryable) (attempt: \(attemptCount)/\(maxRetries))")
            return isRetryable
            
        } catch {
            logger.log(.error, "\(DailyNotificationErrorHandler.TAG): Error checking retry eligibility: \(error)")
            return false
        }
    }
    
    /**
     * Check if error category is retryable
     * 
     * @param category Error category
     * @return true if retryable
     */
    private func isErrorRetryable(_ category: ErrorCategory) -> Bool {
        switch category {
        case .network, .storage:
            return true
        case .scheduling, .permission, .configuration, .system, .unknown:
            return false
        }
    }
    
    /**
     * Handle retryable error
     * 
     * @param operationId Operation identifier
     * @param errorInfo Error information
     * @return ErrorResult with retry information
     */
    private func handleRetryableError(operationId: String, errorInfo: ErrorInfo) -> ErrorResult {
        return handleRetryableError(operationId: operationId, errorInfo: errorInfo, retryConfig: nil)
    }
    
    /**
     * Handle retryable error with custom config
     * 
     * @param operationId Operation identifier
     * @param errorInfo Error information
     * @param retryConfig Custom retry configuration
     * @return ErrorResult with retry information
     */
    private func handleRetryableError(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> ErrorResult {
        do {
            var state: RetryState!
            var attemptCount: Int = 0
            retryQueue.sync {
                if retryStates[operationId] == nil {
                    retryStates[operationId] = RetryState()
                }
                state = retryStates[operationId]!
                state.attemptCount += 1
                attemptCount = state.attemptCount
            }
            
            // Calculate delay with exponential backoff
            let delay = calculateRetryDelay(attemptCount: attemptCount, retryConfig: retryConfig)
            retryQueue.async(flags: .barrier) {
                state.nextRetryTime = Date().addingTimeInterval(delay)
            }
            
            logger.log(.info, "\(DailyNotificationErrorHandler.TAG): Retryable error handled - retry in \(delay)s (attempt \(attemptCount))")
            
            return ErrorResult.retryable(errorInfo: errorInfo, retryDelaySeconds: delay, attemptCount: attemptCount)
            
        } catch {
            logger.log(.error, "DailyNotificationErrorHandler.TAG: Error handling retryable error: \(error)")
            return ErrorResult.fatal(message: "Retry handling failure: \(error.localizedDescription)")
        }
    }
    
    /**
     * Handle non-retryable error
     * 
     * @param operationId Operation identifier
     * @param errorInfo Error information
     * @return ErrorResult with failure information
     */
    private func handleNonRetryableError(operationId: String, errorInfo: ErrorInfo) -> ErrorResult {
        do {
            logger.log(.warning, "\(DailyNotificationErrorHandler.TAG): Non-retryable error handled for operation: \(operationId)")
            
            // Clean up retry state
            retryQueue.async(flags: .barrier) {
                self.retryStates.removeValue(forKey: operationId)
            }
            
            return ErrorResult.fatal(errorInfo: errorInfo)
            
        } catch {
            logger.log(.error, "DailyNotificationErrorHandler.TAG: Error handling non-retryable error: \(error)")
            return ErrorResult.fatal(message: "Non-retryable error handling failure: \(error.localizedDescription)")
        }
    }
    
    /**
     * Calculate retry delay with exponential backoff
     * 
     * @param attemptCount Current attempt number
     * @param retryConfig Custom retry configuration
     * @return Delay in seconds
     */
    private func calculateRetryDelay(attemptCount: Int, retryConfig: RetryConfiguration?) -> TimeInterval {
        do {
            let baseDelay = retryConfig?.baseDelaySeconds ?? config.baseDelaySeconds
            let multiplier = retryConfig?.backoffMultiplier ?? config.backoffMultiplier
            let maxDelay = retryConfig?.maxDelaySeconds ?? config.maxDelaySeconds
            
            // Calculate exponential backoff: baseDelay * (multiplier ^ (attemptCount - 1))
            var delay = baseDelay * pow(multiplier, Double(attemptCount - 1))
            
            // Cap at maximum delay
            delay = min(delay, maxDelay)
            
            // Add jitter to prevent thundering herd
            let jitter = delay * 0.1 * Double.random(in: 0...1)
            delay += jitter
            
            logger.log(.debug, "DailyNotificationErrorHandler.TAG: Calculated retry delay: \(delay)s (attempt \(attemptCount))")
            return delay
            
        } catch {
            logger.log(.error, "DailyNotificationErrorHandler.TAG: Error calculating retry delay: \(error)")
            return config.baseDelaySeconds
        }
    }
    
    // MARK: - Metrics and Telemetry
    
    /**
     * Get error metrics
     * 
     * @return ErrorMetrics with current statistics
     */
    func getMetrics() -> ErrorMetrics {
        return metrics
    }
    
    /**
     * Reset error metrics
     */
    func resetMetrics() {
        metrics.reset()
        logger.log(.debug, "DailyNotificationErrorHandler.TAG: Error metrics reset")
    }
    
    /**
     * Get retry statistics
     * 
     * @return RetryStatistics with retry information
     */
    func getRetryStatistics() -> RetryStatistics {
        var totalOperations = 0
        var activeRetries = 0
        var totalRetries = 0
        
        retryQueue.sync {
            totalOperations = retryStates.count
            for state in retryStates.values {
                if state.attemptCount > 0 {
                    activeRetries += 1
                    totalRetries += state.attemptCount
                }
            }
        }
        
        return RetryStatistics(totalOperations: totalOperations, activeRetries: activeRetries, totalRetries: totalRetries)
    }
    
    /**
     * Clear retry states
     */
    func clearRetryStates() {
        retryQueue.async(flags: .barrier) {
            self.retryStates.removeAll()
        }
        logger.log(.debug, "DailyNotificationErrorHandler.TAG: Retry states cleared")
    }
    
    // MARK: - Data Classes
    
    /**
     * Error information
     */
    struct ErrorInfo {
        let error: Error
        let category: ErrorCategory
        let errorCode: String
        let severity: ErrorSeverity
        let timestamp: Date
        
        var description: String {
            return "ErrorInfo{category=\(category), code=\(errorCode), severity=\(severity), error=\(String(describing: type(of: error)))}"
        }
    }
    
    /**
     * Retry state for an operation
     */
    private class RetryState {
        var attemptCount = 0
        var nextRetryTime = Date()
    }
    
    /**
     * Error result
     */
    struct ErrorResult {
        let success: Bool
        let retryable: Bool
        let errorInfo: ErrorInfo?
        let retryDelaySeconds: TimeInterval
        let attemptCount: Int
        let message: String
        
        static func retryable(errorInfo: ErrorInfo, retryDelaySeconds: TimeInterval, attemptCount: Int) -> ErrorResult {
            return ErrorResult(success: false, retryable: true, errorInfo: errorInfo, retryDelaySeconds: retryDelaySeconds, attemptCount: attemptCount, message: "Retryable error")
        }
        
        static func fatal(errorInfo: ErrorInfo) -> ErrorResult {
            return ErrorResult(success: false, retryable: false, errorInfo: errorInfo, retryDelaySeconds: 0, attemptCount: 0, message: "Fatal error")
        }
        
        static func fatal(message: String) -> ErrorResult {
            return ErrorResult(success: false, retryable: false, errorInfo: nil, retryDelaySeconds: 0, attemptCount: 0, message: message)
        }
    }
    
    /**
     * Error configuration
     */
    struct ErrorConfiguration {
        let maxRetries: Int
        let baseDelaySeconds: TimeInterval
        let maxDelaySeconds: TimeInterval
        let backoffMultiplier: Double
        
        init() {
            self.maxRetries = DailyNotificationErrorHandler.DEFAULT_MAX_RETRIES
            self.baseDelaySeconds = DailyNotificationErrorHandler.DEFAULT_BASE_DELAY_SECONDS
            self.maxDelaySeconds = DailyNotificationErrorHandler.DEFAULT_MAX_DELAY_SECONDS
            self.backoffMultiplier = DailyNotificationErrorHandler.DEFAULT_BACKOFF_MULTIPLIER
        }
        
        init(maxRetries: Int, baseDelaySeconds: TimeInterval, maxDelaySeconds: TimeInterval, backoffMultiplier: Double) {
            self.maxRetries = maxRetries
            self.baseDelaySeconds = baseDelaySeconds
            self.maxDelaySeconds = maxDelaySeconds
            self.backoffMultiplier = backoffMultiplier
        }
    }
    
    /**
     * Retry configuration
     */
    struct RetryConfiguration {
        let maxRetries: Int
        let baseDelaySeconds: TimeInterval
        let maxDelaySeconds: TimeInterval
        let backoffMultiplier: Double
        
        init(maxRetries: Int, baseDelaySeconds: TimeInterval, maxDelaySeconds: TimeInterval, backoffMultiplier: Double) {
            self.maxRetries = maxRetries
            self.baseDelaySeconds = baseDelaySeconds
            self.maxDelaySeconds = maxDelaySeconds
            self.backoffMultiplier = backoffMultiplier
        }
    }
    
    /**
     * Error metrics
     */
    class ErrorMetrics {
        private var totalErrors = 0
        private var networkErrors = 0
        private var storageErrors = 0
        private var schedulingErrors = 0
        private var permissionErrors = 0
        private var configurationErrors = 0
        private var systemErrors = 0
        private var unknownErrors = 0
        
        func recordError(_ errorInfo: ErrorInfo) {
            totalErrors += 1
            
            switch errorInfo.category {
            case .network:
                networkErrors += 1
            case .storage:
                storageErrors += 1
            case .scheduling:
                schedulingErrors += 1
            case .permission:
                permissionErrors += 1
            case .configuration:
                configurationErrors += 1
            case .system:
                systemErrors += 1
            case .unknown:
                unknownErrors += 1
            }
        }
        
        func reset() {
            totalErrors = 0
            networkErrors = 0
            storageErrors = 0
            schedulingErrors = 0
            permissionErrors = 0
            configurationErrors = 0
            systemErrors = 0
            unknownErrors = 0
        }
        
        var totalErrorsCount: Int { return totalErrors }
        var networkErrorsCount: Int { return networkErrors }
        var storageErrorsCount: Int { return storageErrors }
        var schedulingErrorsCount: Int { return schedulingErrors }
        var permissionErrorsCount: Int { return permissionErrors }
        var configurationErrorsCount: Int { return configurationErrors }
        var systemErrorsCount: Int { return systemErrors }
        var unknownErrorsCount: Int { return unknownErrors }
    }
    
    /**
     * Retry statistics
     */
    struct RetryStatistics {
        let totalOperations: Int
        let activeRetries: Int
        let totalRetries: Int
        
        var description: String {
            return "RetryStatistics{totalOps=\(totalOperations), activeRetries=\(activeRetries), totalRetries=\(totalRetries)}"
        }
    }
}
