<?php
declare(strict_types=1);

/**
 * Comprehensive Audit Logging System
 * 
 * Provides secure logging of all significant system events with multiple fallback mechanisms
 */

class AuditLogger {
    private $pdo;
    private $logDir;
    private $appName;
    
    public function __construct(PDO $pdo, string $logDir = null, string $appName = 'Application') {
        $this->pdo = $pdo;
        $this->appName = $appName;
        $this->logDir = $logDir ?? __DIR__ . '/../logs/audit';
        
        // Ensure log directory exists
        if (!is_dir($this->logDir)) {
            mkdir($this->logDir, 0755, true);
        }
    }
    
    /**
     * Main audit logging method
     */
    public function log(int $userId, string $action, $details = null): bool {
        $logData = $this->prepareLogData($userId, $action, $details);
        
        try {
            // Try database logging first
            if ($this->logToDatabase($logData)) {
                return true;
            }
            
            // Fallback to file logging
            return $this->logToFile($logData);
        } catch (Exception $e) {
            // Ultimate fallback to error log
            error_log("Audit log failure: " . $e->getMessage());
            error_log("Log data: " . print_r($logData, true));
            return false;
        }
    }
    
    /**
     * Prepare standardized log data structure
     */
    private function prepareLogData(int $userId, string $action, $details): array {
        return [
            'timestamp' => date('c'),
            'application' => $this->appName,
            'userId' => $userId,
            'action' => $action,
            'ipAddress' => $this->getClientIp(),
            'userAgent' => substr($_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 0, 255),
            'requestUri' => substr($_SERVER['REQUEST_URI'] ?? 'unknown', 0, 255),
            'requestMethod' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
            'details' => $details
        ];
    }
    
    /**
     * Get client IP address with security considerations
     */
    private function getClientIp(): string {
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        
        // Validate IP address
        if (filter_var($ip, FILTER_VALIDATE_IP, 
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return $ip;
        }
        
        return 'invalid_ip';
    }
    
    /**
     * Primary logging method - database
     */
    private function logToDatabase(array $logData): bool {
        try {
            $stmt = $this->pdo->prepare("
                INSERT INTO audit_logs (
                    application, user_id, action, details, 
                    ip_address, user_agent, request_uri, request_method, 
                    created_at
                ) VALUES (
                    :application, :user_id, :action, :details,
                    :ip_address, :user_agent, :request_uri, :request_method,
                    NOW()
                )
            ");
            
            return $stmt->execute([
                ':application' => $logData['application'],
                ':user_id' => $logData['userId'],
                ':action' => substr($logData['action'], 0, 255),
                ':details' => $this->formatDetails($logData['details']),
                ':ip_address' => $logData['ipAddress'],
                ':user_agent' => $logData['userAgent'],
                ':request_uri' => $logData['requestUri'],
                ':request_method' => $logData['requestMethod']
            ]);
        } catch (PDOException $e) {
            error_log("Database audit log failed: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Format details for storage
     */
    private function formatDetails($details) {
        if ($details === null) {
            return null;
        }
        
        if (is_scalar($details)) {
            return (string)$details;
        }
        
        try {
            return json_encode($details, 
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | 
                JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
        } catch (Exception $e) {
            return print_r($details, true);
        }
    }
    
    /**
     * Fallback logging method - file system
     */
    private function logToFile(array $logData): bool {
        $logFile = $this->logDir . '/' . date('Y-m-d') . '.log';
        
        try {
            $logEntry = sprintf(
                "[%s] %s - %s (User ID: %d, IP: %s)\nAction: %s\nDetails: %s\n%s\n\n",
                $logData['timestamp'],
                $logData['application'],
                $logData['requestMethod'],
                $logData['userId'],
                $logData['ipAddress'],
                $logData['action'],
                $this->formatDetails($logData['details']),
                str_repeat('-', 80)
            );
            
            return file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX) !== false;
        } catch (Exception $e) {
            error_log("File audit log failed: " . $e->getMessage());
            return false;
        }
    }
}

/**
 * Global audit log function for convenience
 */
function auditLog(int $userId, string $action, $details = null): bool {
    global $auditLogger;
    
    if (!isset($auditLogger)) {
        // Initialize with default configuration if not set
        global $pdo;
        $auditLogger = new AuditLogger($pdo);
    }
    
    return $auditLogger->log($userId, $action, $details);
}