<?php

/**
 * Guard Dog Debug System
 * 
 * Provides centralized debugging and logging functionality that integrates
 * with WordPress's native debug logging system. All debug messages are
 * written to WordPress's standard debug.log file when both WP_DEBUG and
 * WP_DEBUG_LOG are enabled.
 *
 * 
 * @package GuardDog
 * @since 1.2.0
 */

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

class Guard_Dog_Debug {
    
    /**
     * Debug levels
     */
    const GUARD_DOG_LEVEL_ERROR = 'error';
    const GUARD_DOG_LEVEL_WARNING = 'warning';
    const GUARD_DOG_LEVEL_INFO = 'info';
    const GUARD_DOG_LEVEL_DEBUG = 'debug';
    
    /**
     * Instance of this class
     *
     * @var Guard_Dog_Debug
     */
    private static $instance;
    
    /**
     * Debug enabled flag
     *
     * @var bool
     */
    private $debug_enabled;
    
    /**
     * Debug level
     *
     * @var string
     */
    private $debug_level;
    
    /**
     * Initialize the debug system
     */
    public function __construct() {
        $this->debug_enabled = get_option('guard_dog_debug_enabled', false);
        $this->debug_level = get_option('guard_dog_debug_level', self::GUARD_DOG_LEVEL_ERROR);
        
    }
    
    /**
     * Get singleton instance
     *
     * @return Guard_Dog_Debug
     */
    public static function instance(): Guard_Dog_Debug {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Log a debug message
     *
     * @param string $message The message to log
     * @param string $level The debug level
     * @param string $component The component/feature name
     * @param array $context Additional context data
     */
    public function log(string $message, string $level = self::GUARD_DOG_LEVEL_INFO, string $component = 'general', array $context = []): void {
        static $in_log = false;
        if ($in_log) {
            return;
        }
        $in_log = true;
        // Check if Guard Dog debug is enabled and level is appropriate
        if (!$this->debug_enabled || !$this->should_log($level)) {
            $in_log = false;
            return;
        }
        
        // Respect WordPress debug settings - must have both WP_DEBUG and WP_DEBUG_LOG enabled
        if (!$this->is_wordpress_debug_enabled()) {
            $in_log = false;
            return;
        }
        
        $timestamp = current_time('Y-m-d H:i:s');
        $formatted_message = $this->format_message($timestamp, $level, $component, $message, $context);
        
        // Log to WordPress debug.log using WordPress-compliant method
        $this->write_to_debug_log($formatted_message);
        $in_log = false;
    }
    
    /**
     * Write message to debug log using WordPress-compliant method
     *
     * @param string $message
     */
    private function write_to_debug_log(string $message): void {
        // Use WP_Filesystem for WordPress-compliant file operations
        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        
        $filesystem = WP_Filesystem();
        
        if ($filesystem && is_object($GLOBALS['wp_filesystem'])) {
            $log_destination = WP_CONTENT_DIR . '/debug.log';
            $log_entry = $message . PHP_EOL;
            
            // Read existing content if file exists
            $existing_content = '';
            if ($GLOBALS['wp_filesystem']->exists($log_destination)) {
                $existing_content = $GLOBALS['wp_filesystem']->get_contents($log_destination);
            }
            
            // Append new content and write back
            $new_content = $existing_content . $log_entry;
            $GLOBALS['wp_filesystem']->put_contents($log_destination, $new_content, FS_CHMOD_FILE);
        }
    }
    
    /**
     * Log an error message
     *
     * @param string $message
     * @param string $component
     * @param array $context
     */
    public function error(string $message, string $component = 'general', array $context = []): void {
        $this->log($message, self::GUARD_DOG_LEVEL_ERROR, $component, $context);
    }
    
    /**
     * Log a warning message
     *
     * @param string $message
     * @param string $component
     * @param array $context
     */
    public function warning(string $message, string $component = 'general', array $context = []): void {
        $this->log($message, self::GUARD_DOG_LEVEL_WARNING, $component, $context);
    }
    
    /**
     * Log an info message
     *
     * @param string $message
     * @param string $component
     * @param array $context
     */
    public function info(string $message, string $component = 'general', array $context = []): void {
        $this->log($message, self::GUARD_DOG_LEVEL_INFO, $component, $context);
    }
    
    /**
     * Log a debug message
     *
     * @param string $message
     * @param string $component
     * @param array $context
     */
    public function debug(string $message, string $component = 'general', array $context = []): void {
        static $in_log = false;
        if ($in_log) return;
        $in_log = true;
        $this->log($message, self::GUARD_DOG_LEVEL_INFO, $component, $context);
        $in_log = false;
    }
    
    /**
     * Check if a message should be logged based on level
     *
     * @param string $level
     * @return bool
     */
    private function should_log(string $level): bool {
        $levels = [
            self::GUARD_DOG_LEVEL_ERROR => 1,
            self::GUARD_DOG_LEVEL_WARNING => 2,
            self::GUARD_DOG_LEVEL_INFO => 3,
            self::GUARD_DOG_LEVEL_DEBUG => 4,
        ];
        
        $current_level = $levels[$this->debug_level] ?? 1;
        $message_level = $levels[$level] ?? 1;
        
        return $message_level <= $current_level;
    }
    
    /**
     * Format a log message with WordPress timezone and consistent structure
     *
     * @param string $timestamp
     * @param string $level
     * @param string $component
     * @param string $message
     * @param array $context
     * @return string
     */
    private function format_message(string $timestamp, string $level, string $component, string $message, array $context): string {
        // Use WordPress timezone for timestamp (enhanced)
        $wp_timestamp = $this->get_wordpress_timestamp();
        
        // Sanitize all components for security (enhanced)
        $sanitized_message = $this->sanitize_log_message($message);
        $sanitized_component = $this->sanitize_log_component($component);
        $sanitized_level = $this->sanitize_log_level($level);
        
        // Build consistent message structure: [Guard Dog] [TIMESTAMP] [LEVEL] [component] message
        // This format ensures easy identification and proper structure per requirements 5.1, 5.2, 5.4
        $formatted = sprintf(
            '[Guard Dog] [%s] [%s] [%s] %s',
            $wp_timestamp,
            $sanitized_level,
            $sanitized_component,
            $sanitized_message
        );
        
        // Add context data if provided (enhanced formatting per requirement 5.3)
        if (!empty($context)) {
            $formatted_context = $this->format_context_data($context);
            if (!empty($formatted_context)) {
                $formatted .= ' Context: ' . $formatted_context;
            }
        }
        
        return $formatted;
    }
    
    /**
     * Get WordPress timezone-aware timestamp with enhanced formatting
     *
     * @return string
     */
    private function get_wordpress_timestamp(): string {
        // Use WordPress timezone setting for consistent timestamps
        // current_time() respects WordPress timezone settings automatically
        $timestamp = current_time('Y-m-d H:i:s');
        
        // Ensure consistent format and validate timestamp
        $datetime = DateTime::createFromFormat('Y-m-d H:i:s', $timestamp);
        if ($datetime === false) {
            // Fallback to current time if parsing fails
            return gmdate('Y-m-d H:i:s');
        }
        
        return $timestamp;
    }
    
    /**
     * Sanitize log message for security
     *
     * @param string $message
     * @return string
     */
    private function sanitize_log_message(string $message): string {
        // Remove any potential script tags and sanitize
        $message = wp_strip_all_tags($message);
        $message = sanitize_text_field($message);
        
        // Limit message length to prevent log bloat
        if (strlen($message) > 1000) {
            $message = substr($message, 0, 997) . '...';
        }
        
        return $message;
    }
    
    /**
     * Sanitize log component name
     *
     * @param string $component
     * @return string
     */
    private function sanitize_log_component(string $component): string {
        // Ensure component name is safe and consistent
        $component = sanitize_key($component);
        
        // Default to 'general' if empty after sanitization
        return !empty($component) ? $component : 'general';
    }
    
    /**
     * Sanitize and normalize log level
     *
     * @param string $level
     * @return string
     */
    private function sanitize_log_level(string $level): string {
        // Normalize to uppercase and validate against known levels
        $level = strtoupper(sanitize_text_field($level));
        
        $valid_levels = ['ERROR', 'WARNING', 'INFO', 'DEBUG'];
        
        return in_array($level, $valid_levels, true) ? $level : 'INFO';
    }
    
    /**
     * Format context data with improved JSON encoding and security
     *
     * @param array $context
     * @return string
     */
    private function format_context_data(array $context): string {
        // Sanitize context data first
        $sanitized_context = $this->sanitize_context($context);
        
        // Use WordPress JSON encoding with proper flags
        $json_flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
        
        // Add pretty print for better readability in debug logs
        if (defined('JSON_PRETTY_PRINT')) {
            $json_flags |= JSON_PRETTY_PRINT;
        }
        
        $json_string = wp_json_encode($sanitized_context, $json_flags);
        
        // Fallback if JSON encoding fails
        if (false === $json_string) {
            return 'Context encoding failed';
        }
        
        // Limit context size to prevent log bloat
        if (strlen($json_string) > 2000) {
            $truncated_context = array_slice($sanitized_context, 0, 5, true);
            $truncated_context['_truncated'] = 'Context data truncated for log size';
            $json_string = wp_json_encode($truncated_context, $json_flags);
        }
        
        return $json_string;
    }
    
    /**
     * Sanitize context data for logging with enhanced security
     *
     * @param array $context
     * @param int $depth Current recursion depth
     * @return array
     */
    private function sanitize_context(array $context, int $depth = 0): array {
        // Prevent infinite recursion
        if ($depth > 5) {
            return ['_error' => 'Max recursion depth reached'];
        }
        
        $sanitized = [];
        $count = 0;
        
        foreach ($context as $key => $value) {
            // Limit number of context items to prevent log bloat
            if ($count >= 20) {
                $sanitized['_truncated'] = 'Additional context items truncated';
                break;
            }
            
            $key = sanitize_key($key);
            
            // Skip empty keys after sanitization
            if (empty($key)) {
                continue;
            }
            
            if (is_string($value)) {
                // Remove sensitive patterns and sanitize
                $value = $this->sanitize_sensitive_data($value);
                $sanitized[$key] = sanitize_text_field($value);
            } elseif (is_numeric($value)) {
                $sanitized[$key] = $value;
            } elseif (is_bool($value)) {
                $sanitized[$key] = $value;
            } elseif (is_array($value)) {
                // Recursively sanitize nested arrays with depth tracking
                $sanitized[$key] = $this->sanitize_context($value, $depth + 1);
            } elseif (is_null($value)) {
                $sanitized[$key] = null;
            } else {
                // Convert other types to string and sanitize
                $string_value = (string) $value;
                $string_value = $this->sanitize_sensitive_data($string_value);
                $sanitized[$key] = sanitize_text_field($string_value);
            }
            
            $count++;
        }
        
        return $sanitized;
    }
    
    /**
     * Remove or mask sensitive data from log values
     *
     * @param string $value
     * @return string
     */
    private function sanitize_sensitive_data(string $value): string {
        // Patterns for sensitive data
        $sensitive_patterns = [
            '/password/i',
            '/passwd/i',
            '/secret/i',
            '/token/i',
            '/key/i',
            '/auth/i',
            '/session/i',
            '/cookie/i',
        ];
        
        // Check if value contains sensitive keywords
        foreach ($sensitive_patterns as $pattern) {
            if (preg_match($pattern, $value)) {
                return '[REDACTED]';
            }
        }
        
        // Mask potential email addresses and IP addresses for privacy
        $value = preg_replace('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '[EMAIL]', $value);
        $value = preg_replace('/\b(?:\d{1,3}\.){3}\d{1,3}\b/', '[IP]', $value);
        
        return $value;
    }
    
    /**
     * Get the contents of the debug log
     *
     * @param int $lines Number of lines to retrieve (0 = all)
     * @return string
     */
    public function get_log_contents(int $lines = 100): string {
        return __('Guard Dog now logs to wp-content/debug.log. Please view that file directly.', 'guard-dog');
    }
    
    /**
     * Clear the debug log
     */
    public function clear_log(): void {
        // No-op: cannot clear debug.log programmatically
    }
    
    /**
     * Get debug statistics
     *
     * @return array
     */
    public function get_debug_stats(): array {
        $log_file = WP_CONTENT_DIR . '/debug.log';
        $log_exists = file_exists($log_file);
        
        // Check write permissions using WP_Filesystem
        $log_writable = $this->check_log_directory_writable();
        
        return [
            'enabled' => $this->debug_enabled,
            'level' => $this->debug_level,
            'log_file' => $log_file,
            'log_exists' => $log_exists,
            'log_size' => $log_exists ? filesize($log_file) : 0,
            'log_writable' => $log_writable,
        ];
    }
    
    /**
     * Check if log directory is writable using WP_Filesystem
     *
     * @return bool
     */
    private function check_log_directory_writable(): bool {
        // Try to use WP_Filesystem for proper file permission checking
        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        
        // Initialize filesystem with direct method (no credentials needed for read operations)
        $filesystem = WP_Filesystem();
        
        if ($filesystem && is_object($GLOBALS['wp_filesystem'])) {
            // Use WP_Filesystem to check if directory is writable
            return $GLOBALS['wp_filesystem']->is_writable(WP_CONTENT_DIR);
        }
        
        // Fallback: assume writable if we can't determine (safer for functionality)
        return true;
    }
    
    /**
     * Get available debug levels
     *
     * @return array
     */
    public static function get_debug_levels(): array {
        return [
            self::GUARD_DOG_LEVEL_ERROR => __('Error Only', 'guard-dog'),
            self::GUARD_DOG_LEVEL_WARNING => __('Warning & Error', 'guard-dog'),
            self::GUARD_DOG_LEVEL_INFO => __('Info, Warning & Error', 'guard-dog'),
            self::GUARD_DOG_LEVEL_DEBUG => __('All Messages', 'guard-dog'),
        ];
    }
    
    /**
     * Check if debugging is enabled
     *
     * @return bool
     */
    public function is_enabled(): bool {
        return filter_var($this->debug_enabled, FILTER_VALIDATE_BOOLEAN);
    }
    
    /**
     * Check if WordPress debugging is properly enabled for logging
     *
     * @return bool
     */
    public function is_wordpress_debug_enabled(): bool {
        return defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG;
    }
    
    /**
     * Get comprehensive WordPress debug status information
     *
     * @return array
     */
    public function get_wordpress_debug_status(): array {
        return [
            'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
            'WP_DEBUG_LOG' => defined('WP_DEBUG_LOG') ? WP_DEBUG_LOG : false,
            'WP_DEBUG_DISPLAY' => defined('WP_DEBUG_DISPLAY') ? WP_DEBUG_DISPLAY : null,
            'is_logging_enabled' => $this->is_wordpress_debug_enabled(),
            'debug_log_path' => WP_CONTENT_DIR . '/debug.log',
        ];
    }
    
    /**
     * Get comprehensive debug environment information
     *
     * @return array
     */
    public function get_debug_environment_info(): array {
        $wordpress_status = $this->get_wordpress_debug_status();
        $debug_stats = $this->get_debug_stats();
        
        return array_merge($wordpress_status, [
            'guard_dog_debug_enabled' => $this->debug_enabled,
            'guard_dog_debug_level' => $this->debug_level,
            'log_file_exists' => $debug_stats['log_exists'],
            'log_file_size' => $debug_stats['log_size'],
            'log_file_writable' => $debug_stats['log_writable'],
            'should_show_warning' => $this->should_show_debug_warning(),
        ]);
    }
    
    /**
     * Determine if debug warning should be shown in admin
     *
     * @return bool
     */
    public function should_show_debug_warning(): bool {
        // Show warning if Guard Dog debug is enabled but WordPress debug is not properly configured
        return $this->debug_enabled && !$this->is_wordpress_debug_enabled();
    }
    
    /**
     * Log CAPTCHA specific debug information
     *
     * @param string $message
     * @param array $context
     */
    public function captcha(string $message, array $context = []): void {
        $this->debug($message, 'captcha', $context);
    }
    
    /**
     * Log activity log specific debug information
     *
     * @param string $message
     * @param array $context
     */
    public function activity_log(string $message, array $context = []): void {
        $this->debug($message, 'activity_log', $context);
    }
    
    /**
     * Log login URL specific debug information
     *
     * @param string $message
     * @param array $context
     */
    public function login_url(string $message, array $context = []): void {
        $this->debug($message, 'login_url', $context);
    }
    
    /**
     * Log 2FA specific debug information
     *
     * @param string $message
     * @param array $context
     */
    public function two_factor(string $message, array $context = []): void {
        $this->debug($message, 'two_factor', $context);
    }
} 