<?php
/**
 * Logger Service
 *
 * Enterprise-grade logging system for Mamba Cache with multiple log levels,
 * automatic rotation, and admin viewer integration.
 *
 * @package Mamba\Support
 * @since   1.0.0
 */

namespace Mamba\Support;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Class Logger
 *
 * Enterprise-grade logging system with features including:
 * - Multiple log levels (ERROR, WARNING, INFO, DEBUG)
 * - Automatic log rotation (configurable retention)
 * - Context-aware logging with request metadata
 * - Admin viewer integration
 * - Performance impact tracking
 * - Export functionality for support
 *
 * @since 1.0.0
 */
final class Logger {
    
    const LEVEL_ERROR   = 'ERROR';
    const LEVEL_WARNING = 'WARNING';
    const LEVEL_INFO    = 'INFO';
    const LEVEL_DEBUG   = 'DEBUG';
    
    private static ?self $instance = null;
    private string $logDir;
    private bool $debugMode;
    private int $retentionDays;
    private array $levelPriority = [
        self::LEVEL_ERROR   => 1,
        self::LEVEL_WARNING => 2,
        self::LEVEL_INFO    => 3,
        self::LEVEL_DEBUG   => 4,
    ];
    
    private function __construct() {
        $this->logDir = WP_CONTENT_DIR . '/cache/mamba/logs';
        $this->debugMode = (bool) get_option('mamba_debug_mode', 0);
        $this->retentionDays = (int) get_option('mamba_log_retention_days', 7);
        
        $this->ensureLogDirectory();
    }
    
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Log an error message
     */
    public static function error(string $message, array $context = []): void {
        self::getInstance()->log(self::LEVEL_ERROR, $message, $context);
    }
    
    /**
     * Log a warning message
     */
    public static function warning(string $message, array $context = []): void {
        self::getInstance()->log(self::LEVEL_WARNING, $message, $context);
    }
    
    /**
     * Log an info message
     */
    public static function info(string $message, array $context = []): void {
        self::getInstance()->log(self::LEVEL_INFO, $message, $context);
    }
    
    /**
     * Log a debug message (only when debug mode is enabled)
     */
    public static function debug(string $message, array $context = []): void {
        self::getInstance()->log(self::LEVEL_DEBUG, $message, $context);
    }
    
    /**
     * Log a cache event with standardized format
     */
    public static function cacheEvent(string $event, string $url, array $details = []): void {
        $context = array_merge([
            'event' => $event,
            'url' => $url,
        ], $details);
        
        $level = in_array($event, ['error', 'bypass', 'miss']) ? self::LEVEL_WARNING : self::LEVEL_INFO;
        self::getInstance()->log($level, "Cache {$event}: {$url}", $context);
    }
    
    /**
     * Log invalidation events
     */
    public static function invalidation(string $type, $identifier, array $details = []): void {
        $context = array_merge([
            'type' => $type,
            'identifier' => $identifier,
        ], $details);
        
        self::getInstance()->log(self::LEVEL_INFO, "Invalidation: {$type} - {$identifier}", $context);
    }
    
    /**
     * Log warmup events
     */
    public static function warmup(string $event, array $details = []): void {
        self::getInstance()->log(self::LEVEL_INFO, "Warmup: {$event}", $details);
    }
    
    /**
     * Log performance metrics
     */
    public static function performance(string $operation, float $durationMs, array $details = []): void {
        $context = array_merge([
            'operation' => $operation,
            'duration_ms' => round($durationMs, 2),
        ], $details);
        
        self::getInstance()->log(self::LEVEL_DEBUG, "Performance: {$operation} took {$durationMs}ms", $context);
    }
    
    /**
     * Core logging method
     */
    private function log(string $level, string $message, array $context = []): void {
        // Skip debug messages if debug mode is off
        if ($level === self::LEVEL_DEBUG && !$this->debugMode) {
            return;
        }
        
        // Skip if logging is completely disabled
        if (!get_option('mamba_enable_logging', 1)) {
            return;
        }
        
        $minLevel = get_option('mamba_log_level', self::LEVEL_INFO);
        if ($this->levelPriority[$level] > $this->levelPriority[$minLevel]) {
            return;
        }
        
        $timestamp = wp_date('Y-m-d H:i:s');
        $requestId = $this->getRequestId();
        
        // Build log entry
        $entry = [
            'timestamp' => $timestamp,
            'level' => $level,
            'request_id' => $requestId,
            'message' => $message,
        ];
        
        // Add context if not empty
        if (!empty($context)) {
            $entry['context'] = $context;
        }
        
        // Add request metadata for non-CLI requests
        if (!defined('WP_CLI') && isset($_SERVER['REQUEST_URI'])) {
            $entry['request'] = [
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_text_field
                'uri' => isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '',
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_text_field
                'method' => isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) : 'GET',
                'ip' => $this->getClientIp(),
            ];
        }
        
        // Format as single line JSON for easy parsing
        $logLine = wp_json_encode($entry, JSON_UNESCAPED_SLASHES) . "\n";
        
        // Write to daily log file
        $logFile = $this->getLogFilePath();
        @file_put_contents($logFile, $logLine, FILE_APPEND | LOCK_EX);
        
        // Also write errors to WordPress debug.log if WP_DEBUG is on
        if ($level === self::LEVEL_ERROR && defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Mamba Cache: {$message}");
        }
    }
    
    /**
     * Get current log file path
     */
    private function getLogFilePath(): string {
        $date = wp_date('Y-m-d');
        return $this->logDir . "/mamba-{$date}.log";
    }
    
    /**
     * Get unique request ID for correlating log entries
     */
    private function getRequestId(): string {
        static $requestId = null;
        if ($requestId === null) {
            $requestId = substr(md5(uniqid((string)mt_rand(), true)), 0, 8);
        }
        return $requestId;
    }
    
    /**
     * Get client IP address
     */
    private function getClientIp(): string {
        $headers = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_text_field
                $ip = sanitize_text_field(wp_unslash($_SERVER[$header]));
                // Handle comma-separated IPs (X-Forwarded-For)
                if (strpos($ip, ',') !== false) {
                    $ip = trim(explode(',', $ip)[0]);
                }
                return $ip;
            }
        }
        return 'unknown';
    }
    
    /**
     * Ensure log directory exists with proper protection
     */
    private function ensureLogDirectory(): void {
        if (!is_dir($this->logDir)) {
            wp_mkdir_p($this->logDir);
            
            // Add protection files
            @file_put_contents($this->logDir . '/.htaccess', "Deny from all\n");
            @file_put_contents($this->logDir . '/index.html', '');
        }
    }
    
    /**
     * Get available log files
     */
    public function getLogFiles(): array {
        if (!is_dir($this->logDir)) {
            return [];
        }
        
        $files = glob($this->logDir . '/mamba-*.log');
        if (!$files) {
            return [];
        }
        
        $result = [];
        foreach ($files as $file) {
            $filename = basename($file);
            preg_match('/mamba-(\d{4}-\d{2}-\d{2})\.log/', $filename, $matches);
            $date = $matches[1] ?? 'unknown';
            
            $result[] = [
                'path' => $file,
                'filename' => $filename,
                'date' => $date,
                'size' => filesize($file),
                'size_human' => size_format(filesize($file)),
                'lines' => $this->countLines($file),
            ];
        }
        
        // Sort by date descending
        usort($result, fn($a, $b) => strcmp($b['date'], $a['date']));
        
        return $result;
    }
    
    /**
     * Count lines in a file efficiently
     */
    private function countLines(string $file): int {
        $count = 0;
        $handle = @fopen($file, 'r');
        if ($handle) {
            while (!feof($handle)) {
                $line = fgets($handle);
                if ($line !== false) {
                    $count++;
                }
            }
            fclose($handle);
        }
        return $count;
    }
    
    /**
     * Read log entries with filtering
     */
    public function readLogs(string $date = '', string $level = '', int $limit = 500, int $offset = 0): array {
        $logFile = $date ? $this->logDir . "/mamba-{$date}.log" : $this->getLogFilePath();
        
        if (!file_exists($logFile)) {
            return ['entries' => [], 'total' => 0, 'has_more' => false];
        }
        
        $entries = [];
        $total = 0;
        $skipped = 0;
        
        $handle = @fopen($logFile, 'r');
        if (!$handle) {
            return ['entries' => [], 'total' => 0, 'has_more' => false];
        }
        
        // Read file in reverse order for most recent first
        $lines = [];
        while (($line = fgets($handle)) !== false) {
            $lines[] = $line;
        }
        fclose($handle);
        
        $lines = array_reverse($lines);
        
        foreach ($lines as $line) {
            $entry = json_decode(trim($line), true);
            if (!$entry) {
                continue;
            }
            
            // Filter by level if specified
            if ($level && ($entry['level'] ?? '') !== $level) {
                continue;
            }
            
            $total++;
            
            // Handle offset
            if ($skipped < $offset) {
                $skipped++;
                continue;
            }
            
            // Handle limit
            if (count($entries) >= $limit) {
                continue; // Keep counting total
            }
            
            $entries[] = $entry;
        }
        
        return [
            'entries' => $entries,
            'total' => $total,
            'has_more' => $total > ($offset + $limit),
        ];
    }
    
    /**
     * Get log statistics
     */
    public function getStats(): array {
        $files = $this->getLogFiles();
        
        $stats = [
            'total_files' => count($files),
            'total_size' => 0,
            'total_entries' => 0,
            'by_level' => [
                self::LEVEL_ERROR => 0,
                self::LEVEL_WARNING => 0,
                self::LEVEL_INFO => 0,
                self::LEVEL_DEBUG => 0,
            ],
            'recent_errors' => [],
        ];
        
        foreach ($files as $file) {
            $stats['total_size'] += $file['size'];
            $stats['total_entries'] += $file['lines'];
        }
        
        // Count by level from today's log
        $todayLog = $this->readLogs('', '', 1000);
        foreach ($todayLog['entries'] as $entry) {
            $level = $entry['level'] ?? self::LEVEL_INFO;
            if (isset($stats['by_level'][$level])) {
                $stats['by_level'][$level]++;
            }
            
            // Collect recent errors
            if ($level === self::LEVEL_ERROR && count($stats['recent_errors']) < 5) {
                $stats['recent_errors'][] = [
                    'timestamp' => $entry['timestamp'] ?? '',
                    'message' => $entry['message'] ?? '',
                ];
            }
        }
        
        $stats['total_size_human'] = size_format($stats['total_size']);
        
        return $stats;
    }
    
    /**
     * Export logs for support
     */
    public function exportLogs(int $days = 3): string {
        $export = [
            'generated_at' => wp_date('Y-m-d H:i:s'),
            'site_url' => home_url(),
            'wp_version' => get_bloginfo('version'),
            'php_version' => PHP_VERSION,
            'mamba_version' => defined('MAMBA_VERSION') ? MAMBA_VERSION : 'unknown',
            'debug_mode' => $this->debugMode,
            'settings' => $this->getSettingsSnapshot(),
            'logs' => [],
        ];
        
        $files = $this->getLogFiles();
        $cutoff = wp_date('Y-m-d', strtotime("-{$days} days"));
        
        foreach ($files as $file) {
            if ($file['date'] >= $cutoff) {
                $content = @file_get_contents($file['path']);
                if ($content) {
                    $export['logs'][$file['date']] = array_filter(
                        array_map('json_decode', explode("\n", trim($content))),
                        fn($e) => $e !== null
                    );
                }
            }
        }
        
        return wp_json_encode($export, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }
    
    /**
     * Get current settings snapshot for debugging
     */
    private function getSettingsSnapshot(): array {
        return [
            'page_cache_enabled' => (bool) get_option('mamba_enable_page_cache', 0),
            'cache_ttl' => (int) get_option('mamba_cache_ttl', 7200),
            'store_api_ttl' => (int) get_option('mamba_store_api_ttl', 600),
            'adaptive_ttl' => (bool) get_option('mamba_enable_adaptive_ttl', 0),
            'html_minify' => (bool) get_option('mamba_enable_html_minify', 0),
            'object_cache' => ObjectCache::hasPersistentCache() ? ObjectCache::getCacheType() : 'none',
        ];
    }
    
    /**
     * Clean up old log files
     */
    public function cleanup(): int {
        $files = $this->getLogFiles();
        $cutoff = wp_date('Y-m-d', strtotime("-{$this->retentionDays} days"));
        $deleted = 0;
        
        foreach ($files as $file) {
            if ($file['date'] < $cutoff) {
                if (@unlink($file['path'])) {
                    $deleted++;
                }
            }
        }
        
        return $deleted;
    }
    
    /**
     * Clear all logs
     */
    public function clearAll(): int {
        $files = $this->getLogFiles();
        $deleted = 0;
        
        foreach ($files as $file) {
            if (@unlink($file['path'])) {
                $deleted++;
            }
        }
        
        return $deleted;
    }
    
    /**
     * Check if debug mode is enabled
     */
    public function isDebugMode(): bool {
        return $this->debugMode;
    }
    
    /**
     * Get log directory path
     */
    public function getLogDir(): string {
        return $this->logDir;
    }
}
