<?php

namespace DeskMoloni\Libraries;

/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 * 
 * Error Handler
 * Comprehensive error handling and logging system for sync operations
 * 
 * @package DeskMoloni
 * @subpackage Libraries
 * @category ErrorHandling
 * @author Descomplicar® - PHP Fullstack Engineer
 * @version 1.0.0
 */

defined('BASEPATH') or exit('No direct script access allowed');

class ErrorHandler
{
    protected $CI;
    protected $model;
    
    // Error severity levels
    const SEVERITY_LOW = 'low';
    const SEVERITY_MEDIUM = 'medium';
    const SEVERITY_HIGH = 'high';
    const SEVERITY_CRITICAL = 'critical';
    
    // Error categories
    const CATEGORY_SYNC = 'sync';
    const CATEGORY_API = 'api';
    const CATEGORY_QUEUE = 'queue';
    const CATEGORY_MAPPING = 'mapping';
    const CATEGORY_VALIDATION = 'validation';
    const CATEGORY_AUTHENTICATION = 'authentication';
    const CATEGORY_SYSTEM = 'system';
    
    // Error codes
    const ERROR_API_CONNECTION = 'API_CONNECTION_FAILED';
    const ERROR_API_TIMEOUT = 'API_TIMEOUT';
    const ERROR_API_AUTHENTICATION = 'API_AUTHENTICATION_FAILED';
    const ERROR_API_RATE_LIMIT = 'API_RATE_LIMIT_EXCEEDED';
    const ERROR_API_INVALID_RESPONSE = 'API_INVALID_RESPONSE';
    const ERROR_SYNC_CONFLICT = 'SYNC_CONFLICT';
    const ERROR_SYNC_VALIDATION = 'SYNC_VALIDATION_FAILED';
    const ERROR_MAPPING_NOT_FOUND = 'MAPPING_NOT_FOUND';
    const ERROR_QUEUE_PROCESSING = 'QUEUE_PROCESSING_FAILED';
    const ERROR_DATA_CORRUPTION = 'DATA_CORRUPTION';
    const ERROR_SYSTEM_RESOURCE = 'SYSTEM_RESOURCE_EXHAUSTED';
    
    // Notification settings
    protected $notification_thresholds = [
        self::SEVERITY_CRITICAL => 1,
        self::SEVERITY_HIGH => 3,
        self::SEVERITY_MEDIUM => 10,
        self::SEVERITY_LOW => 50
    ];
    
    public function __construct()
    {
        $this->CI = &get_instance();
        $this->CI->load->model('desk_moloni_model');
        $this->model = $this->CI->desk_moloni_model;
        
        log_activity('ErrorHandler initialized');
    }
    
    /**
     * Log error with context and severity
     * 
     * @param string $category
     * @param string $error_code
     * @param string $message
     * @param array $context
     * @param string $severity
     * @return int Error log ID
     */
    public function log_error($category, $error_code, $message, $context = [], $severity = self::SEVERITY_MEDIUM)
    {
        try {
            // Validate inputs
            if (!$this->is_valid_category($category)) {
                $category = self::CATEGORY_SYSTEM;
            }
            
            if (!$this->is_valid_severity($severity)) {
                $severity = self::SEVERITY_MEDIUM;
            }
            
            // Prepare error data
            $error_data = [
                'category' => $category,
                'error_code' => $error_code,
                'severity' => $severity,
                'message' => $this->sanitize_message($message),
                'context' => json_encode($this->sanitize_context($context)),
                'stack_trace' => $this->get_sanitized_stack_trace(),
                'occurred_at' => date('Y-m-d H:i:s'),
                'user_id' => get_staff_user_id() ?: null,
                'ip_address' => $this->CI->input->ip_address(),
                'user_agent' => $this->CI->input->user_agent(),
                'request_uri' => $this->CI->uri->uri_string(),
                'memory_usage' => memory_get_usage(true),
                'peak_memory' => memory_get_peak_usage(true),
                'processing_time' => $this->get_processing_time()
            ];
            
            // Store error in database
            $error_id = $this->model->log_error($error_data);
            
            // Log to file system as backup
            $this->log_to_file($error_data);
            
            // Check if notification is needed
            $this->check_notification_threshold($category, $severity, $error_code);
            
            // Trigger hooks for error handling
            hooks()->do_action('desk_moloni_error_logged', $error_id, $error_data);
            
            return $error_id;
            
        } catch (\Exception $e) {
            // Fallback error logging
            log_message('error', 'ErrorHandler failed: ' . $e->getMessage());
            error_log("DeskMoloni Error Handler Failure: {$e->getMessage()}");
            
            return false;
        }
    }
    
    /**
     * Log API error with specific handling
     * 
     * @param string $endpoint
     * @param int $status_code
     * @param string $response_body
     * @param array $request_data
     * @param string $error_message
     * @return int
     */
    public function log_api_error($endpoint, $status_code, $response_body, $request_data = [], $error_message = '')
    {
        $error_code = $this->determine_api_error_code($status_code, $response_body);
        $severity = $this->determine_api_error_severity($status_code, $error_code);
        
        $context = [
            'endpoint' => $endpoint,
            'status_code' => $status_code,
            'response_body' => $this->truncate_response_body($response_body),
            'request_data' => $this->sanitize_request_data($request_data),
            'response_headers' => $this->get_last_response_headers()
        ];
        
        $message = $error_message ?: "API request failed: {$endpoint} returned {$status_code}";
        
        return $this->log_error(self::CATEGORY_API, $error_code, $message, $context, $severity);
    }
    
    /**
     * Log sync error with entity context
     * 
     * @param string $entity_type
     * @param int $entity_id
     * @param string $direction
     * @param string $error_message
     * @param array $additional_context
     * @return int
     */
    public function log_sync_error($entity_type, $entity_id, $direction, $error_message, $additional_context = [])
    {
        $error_code = $this->determine_sync_error_code($error_message);
        $severity = $this->determine_sync_error_severity($error_code, $entity_type);
        
        $context = array_merge([
            'entity_type' => $entity_type,
            'entity_id' => $entity_id,
            'sync_direction' => $direction,
            'sync_attempt' => $additional_context['attempt'] ?? 1
        ], $additional_context);
        
        return $this->log_error(self::CATEGORY_SYNC, $error_code, $error_message, $context, $severity);
    }
    
    /**
     * Log validation error
     * 
     * @param string $field_name
     * @param mixed $field_value
     * @param string $validation_rule
     * @param string $entity_type
     * @return int
     */
    public function log_validation_error($field_name, $field_value, $validation_rule, $entity_type = null)
    {
        $context = [
            'field_name' => $field_name,
            'field_value' => $this->sanitize_field_value($field_value),
            'validation_rule' => $validation_rule,
            'entity_type' => $entity_type
        ];
        
        $message = "Validation failed for field '{$field_name}' with rule '{$validation_rule}'";
        
        return $this->log_error(
            self::CATEGORY_VALIDATION,
            self::ERROR_SYNC_VALIDATION,
            $message,
            $context,
            self::SEVERITY_LOW
        );
    }
    
    /**
     * Get error statistics
     * 
     * @param array $filters
     * @return array
     */
    public function get_error_statistics($filters = [])
    {
        return [
            'total_errors' => $this->model->count_errors($filters),
            'by_category' => $this->model->count_errors_by_category($filters),
            'by_severity' => $this->model->count_errors_by_severity($filters),
            'by_error_code' => $this->model->count_errors_by_code($filters),
            'recent_errors' => $this->model->get_recent_errors(10, $filters),
            'error_trends' => $this->model->get_error_trends($filters),
            'top_error_codes' => $this->model->get_top_error_codes(10, $filters)
        ];
    }
    
    /**
     * Get errors by criteria
     * 
     * @param array $criteria
     * @param int $limit
     * @param int $offset
     * @return array
     */
    public function get_errors($criteria = [], $limit = 50, $offset = 0)
    {
        return $this->model->get_errors($criteria, $limit, $offset);
    }
    
    /**
     * Mark error as resolved
     * 
     * @param int $error_id
     * @param string $resolution_notes
     * @param int $resolved_by
     * @return bool
     */
    public function mark_error_resolved($error_id, $resolution_notes = '', $resolved_by = null)
    {
        $resolution_data = [
            'resolved' => 1,
            'resolved_at' => date('Y-m-d H:i:s'),
            'resolved_by' => $resolved_by ?: get_staff_user_id(),
            'resolution_notes' => $resolution_notes
        ];
        
        $result = $this->model->update_error($error_id, $resolution_data);
        
        if ($result) {
            log_activity("Error #{$error_id} marked as resolved");
            hooks()->do_action('desk_moloni_error_resolved', $error_id, $resolution_data);
        }
        
        return $result;
    }
    
    /**
     * Bulk mark errors as resolved
     * 
     * @param array $error_ids
     * @param string $resolution_notes
     * @return array
     */
    public function bulk_mark_resolved($error_ids, $resolution_notes = '')
    {
        $results = [
            'total' => count($error_ids),
            'success' => 0,
            'errors' => 0
        ];
        
        foreach ($error_ids as $error_id) {
            if ($this->mark_error_resolved($error_id, $resolution_notes)) {
                $results['success']++;
            } else {
                $results['errors']++;
            }
        }
        
        return $results;
    }
    
    /**
     * Clean up old errors
     * 
     * @param int $retention_days
     * @param bool $keep_critical
     * @return int
     */
    public function cleanup_old_errors($retention_days = 90, $keep_critical = true)
    {
        $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$retention_days} days"));
        
        $criteria = [
            'occurred_before' => $cutoff_date,
            'resolved' => 1
        ];
        
        if ($keep_critical) {
            $criteria['exclude_severity'] = self::SEVERITY_CRITICAL;
        }
        
        $deleted = $this->model->delete_errors($criteria);
        
        if ($deleted > 0) {
            log_activity("Cleaned up {$deleted} old error logs older than {$retention_days} days");
        }
        
        return $deleted;
    }
    
    /**
     * Export errors to CSV
     * 
     * @param array $filters
     * @param int $limit
     * @return string
     */
    public function export_errors_csv($filters = [], $limit = 1000)
    {
        $errors = $this->model->get_errors($filters, $limit);
        
        $output = fopen('php://temp', 'r+');
        
        // CSV Header
        fputcsv($output, [
            'ID',
            'Category',
            'Error Code',
            'Severity',
            'Message',
            'Occurred At',
            'Resolved',
            'User ID',
            'IP Address',
            'Request URI',
            'Memory Usage',
            'Context'
        ]);
        
        foreach ($errors as $error) {
            fputcsv($output, [
                $error->id,
                $error->category,
                $error->error_code,
                $error->severity,
                $error->message,
                $error->occurred_at,
                $error->resolved ? 'Yes' : 'No',
                $error->user_id,
                $error->ip_address,
                $error->request_uri,
                $this->format_memory_usage($error->memory_usage),
                $this->sanitize_context_for_export($error->context)
            ]);
        }
        
        rewind($output);
        $csv_content = stream_get_contents($output);
        fclose($output);
        
        return $csv_content;
    }
    
    /**
     * Check if notification threshold is reached
     * 
     * @param string $category
     * @param string $severity
     * @param string $error_code
     */
    protected function check_notification_threshold($category, $severity, $error_code)
    {
        $threshold = $this->notification_thresholds[$severity] ?? 10;
        
        // Count recent errors of same type
        $recent_count = $this->model->count_recent_errors($category, $error_code, 3600); // Last hour
        
        if ($recent_count >= $threshold) {
            $this->trigger_error_notification($category, $severity, $error_code, $recent_count);
        }
    }
    
    /**
     * Trigger error notification
     * 
     * @param string $category
     * @param string $severity
     * @param string $error_code
     * @param int $error_count
     */
    protected function trigger_error_notification($category, $severity, $error_code, $error_count)
    {
        $notification_data = [
            'category' => $category,
            'severity' => $severity,
            'error_code' => $error_code,
            'error_count' => $error_count,
            'time_period' => '1 hour'
        ];
        
        // Send email notification if configured
        if (get_option('desk_moloni_error_notifications') == '1') {
            $this->send_error_notification_email($notification_data);
        }
        
        // Trigger webhook if configured
        if (get_option('desk_moloni_error_webhooks') == '1') {
            $this->trigger_error_webhook($notification_data);
        }
        
        hooks()->do_action('desk_moloni_error_threshold_reached', $notification_data);
    }
    
    /**
     * Send error notification email
     * 
     * @param array $notification_data
     */
    protected function send_error_notification_email($notification_data)
    {
        $admin_emails = explode(',', get_option('desk_moloni_admin_emails', ''));
        
        if (empty($admin_emails)) {
            return;
        }
        
        $subject = "Desk-Moloni Error Threshold Reached: {$notification_data['error_code']}";
        $message = $this->build_error_notification_message($notification_data);
        
        foreach ($admin_emails as $email) {
            $email = trim($email);
            if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
                send_mail_template('desk_moloni_error_notification', $email, [
                    'subject' => $subject,
                    'message' => $message,
                    'notification_data' => $notification_data
                ]);
            }
        }
    }
    
    /**
     * Determine API error code from response
     * 
     * @param int $status_code
     * @param string $response_body
     * @return string
     */
    protected function determine_api_error_code($status_code, $response_body)
    {
        switch ($status_code) {
            case 401:
            case 403:
                return self::ERROR_API_AUTHENTICATION;
            case 429:
                return self::ERROR_API_RATE_LIMIT;
            case 408:
            case 504:
                return self::ERROR_API_TIMEOUT;
            case 0:
                return self::ERROR_API_CONNECTION;
            default:
                if ($status_code >= 500) {
                    return self::ERROR_API_CONNECTION;
                } elseif ($status_code >= 400) {
                    return self::ERROR_API_INVALID_RESPONSE;
                }
                return 'API_UNKNOWN_ERROR';
        }
    }
    
    /**
     * Determine API error severity
     * 
     * @param int $status_code
     * @param string $error_code
     * @return string
     */
    protected function determine_api_error_severity($status_code, $error_code)
    {
        if (in_array($error_code, [self::ERROR_API_AUTHENTICATION, self::ERROR_API_CONNECTION])) {
            return self::SEVERITY_CRITICAL;
        }
        
        if ($error_code === self::ERROR_API_RATE_LIMIT) {
            return self::SEVERITY_HIGH;
        }
        
        if ($status_code >= 500) {
            return self::SEVERITY_HIGH;
        }
        
        return self::SEVERITY_MEDIUM;
    }
    
    /**
     * Sanitize error message
     * 
     * @param string $message
     * @return string
     */
    protected function sanitize_message($message)
    {
        // Remove sensitive information patterns
        $patterns = [
            '/password[\'"\s]*[:=][\'"\s]*[^\s\'",}]+/i',
            '/token[\'"\s]*[:=][\'"\s]*[^\s\'",}]+/i',
            '/key[\'"\s]*[:=][\'"\s]*[^\s\'",}]+/i',
            '/secret[\'"\s]*[:=][\'"\s]*[^\s\'",}]+/i'
        ];
        
        $message = preg_replace($patterns, '[REDACTED]', $message);
        
        return substr(trim($message), 0, 1000);
    }
    
    /**
     * Sanitize context data
     * 
     * @param array $context
     * @return array
     */
    protected function sanitize_context($context)
    {
        $sensitive_keys = ['password', 'token', 'key', 'secret', 'auth', 'credential'];
        
        array_walk_recursive($context, function(&$value, $key) use ($sensitive_keys) {
            if (is_string($key) && in_array(strtolower($key), $sensitive_keys)) {
                $value = '[REDACTED]';
            }
        });
        
        return $context;
    }
    
    /**
     * Get sanitized stack trace
     * 
     * @return string
     */
    protected function get_sanitized_stack_trace()
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
        
        $clean_trace = [];
        foreach ($trace as $frame) {
            $clean_frame = [
                'file' => basename($frame['file'] ?? 'unknown'),
                'line' => $frame['line'] ?? 0,
                'function' => $frame['function'] ?? 'unknown'
            ];
            
            if (isset($frame['class'])) {
                $clean_frame['class'] = $frame['class'];
            }
            
            $clean_trace[] = $clean_frame;
        }
        
        return json_encode($clean_trace);
    }
    
    /**
     * Validate error category
     * 
     * @param string $category
     * @return bool
     */
    protected function is_valid_category($category)
    {
        return in_array($category, [
            self::CATEGORY_SYNC,
            self::CATEGORY_API,
            self::CATEGORY_QUEUE,
            self::CATEGORY_MAPPING,
            self::CATEGORY_VALIDATION,
            self::CATEGORY_AUTHENTICATION,
            self::CATEGORY_SYSTEM
        ]);
    }
    
    /**
     * Validate error severity
     * 
     * @param string $severity
     * @return bool
     */
    protected function is_valid_severity($severity)
    {
        return in_array($severity, [
            self::SEVERITY_LOW,
            self::SEVERITY_MEDIUM,
            self::SEVERITY_HIGH,
            self::SEVERITY_CRITICAL
        ]);
    }
    
    /**
     * Log error to file as backup
     * 
     * @param array $error_data
     */
    protected function log_to_file($error_data)
    {
        $log_file = FCPATH . 'uploads/desk_moloni/logs/errors_' . date('Y-m-d') . '.log';
        
        $log_entry = sprintf(
            "[%s] %s/%s: %s\n",
            $error_data['occurred_at'],
            $error_data['category'],
            $error_data['severity'],
            $error_data['message']
        );
        
        if (!empty($error_data['context'])) {
            $log_entry .= "Context: " . $error_data['context'] . "\n";
        }
        
        $log_entry .= "---\n";
        
        file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
    }
    
    /**
     * Get current processing time
     * 
     * @return float
     */
    protected function get_processing_time()
    {
        if (defined('APP_START_TIME')) {
            return microtime(true) - APP_START_TIME;
        }
        
        return 0;
    }
}