<?php
//auth/AuditLogger.php
declare(strict_types=1);

/**
 * Comprehensive Audit Logging System
 * 
 * Provides secure logging of all significant system events to both database and file simultaneously
 */

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);
        }
        
        // Ensure database table exists
        $this->ensureTableExists();
    }
    
    /**
     * Main audit logging method - logs to both database and file
     */
    public function log(int $userId, string $action, $details = null): bool {
        $logData = $this->prepareLogData($userId, $action, $details);
        
        $dbSuccess = false;
        $fileSuccess = false;
        
        // Always attempt both logging methods
        try {
            $dbSuccess = $this->logToDatabase($logData);
        } catch (Exception $e) {
            error_log("Database audit log failed: " . $e->getMessage());
        }
        
        try {
            $fileSuccess = $this->logToFile($logData);
        } catch (Exception $e) {
            error_log("File audit log failed: " . $e->getMessage());
        }
        
        // Log to system error log if both methods failed
        if (!$dbSuccess && !$fileSuccess) {
            error_log("CRITICAL: All audit logging failed for action: " . $action);
            error_log("Log data: " . print_r($logData, true));
            return false;
        }
        
        return true;
    }
    
    /**
     * 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' => $this->sanitizeUserAgent($_SERVER['HTTP_USER_AGENT'] ?? 'unknown'),
            'requestUri' => substr($_SERVER['REQUEST_URI'] ?? 'unknown', 0, 255),
            'requestMethod' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
            'details' => $this->anonymizeDetails($details)
        ];
    }
    
    /**
     * Ensure database table exists
     */
    private function ensureTableExists(): void {
        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS audit_logs (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                application VARCHAR(100) NOT NULL,
                user_id INT NOT NULL,
                action VARCHAR(255) NOT NULL,
                details TEXT NULL,
                ip_address VARCHAR(45) NOT NULL,
                user_agent VARCHAR(255) NOT NULL,
                request_uri VARCHAR(255) NOT NULL,
                request_method VARCHAR(10) NOT NULL,
                created_at DATETIME NOT NULL,
                INDEX idx_user_id (user_id),
                INDEX idx_created_at (created_at),
                INDEX idx_action (action),
                INDEX idx_ip_address (ip_address)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ");
    }
    
    /**
     * Get client IP address with security considerations
     */
    private function getClientIp(): string {
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        
        // Check for forwarded IP in a secure way
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $forwardedIps = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $firstForwardedIp = trim($forwardedIps[0]);
            if (filter_var($firstForwardedIp, FILTER_VALIDATE_IP, 
                FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                $ip = $firstForwardedIp;
            }
        }
        
        // Validate IP address
        if (filter_var($ip, FILTER_VALIDATE_IP)) {
            return $ip;
        }
        
        return 'invalid_ip';
    }
    
    /**
     * Sanitize user agent to prevent log injection
     */
    private function sanitizeUserAgent(string $userAgent): string {
        return substr(preg_replace('/[^\x20-\x7E]/', '', $userAgent), 0, 255);
    }
    
    /**
     * Anonymize sensitive data in details
     */
    private function anonymizeDetails($details) {
        if (is_string($details)) {
            // Remove potential sensitive data
            $details = preg_replace('/\b\d{4}-\d{4}-\d{4}-\d{4}\b/', '[CREDIT_CARD]', $details);
            $details = preg_replace('/\b\d{3}-\d{2}-\d{4}\b/', '[SSN]', $details);
            $details = preg_replace('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '[EMAIL]', $details);
        }
        return $details;
    }
    
    /**
     * Database logging method
     */
    private function logToDatabase(array $logData): bool {
        $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']
        ]);
    }
    
    /**
     * 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);
        }
    }
    
    /**
     * File system logging method
     */
    private function logToFile(array $logData): bool {
        $logFile = $this->logDir . '/' . date('Y-m-d') . '.log';
        
        $logEntry = sprintf(
            "[%s] %s - %s %s (User ID: %d, IP: %s, Agent: %s)\nAction: %s\nDetails: %s\n%s\n\n",
            $logData['timestamp'],
            $logData['application'],
            $logData['requestMethod'],
            $logData['requestUri'],
            $logData['userId'],
            $logData['ipAddress'],
            $logData['userAgent'],
            $logData['action'],
            $this->formatDetailsForFile($logData['details']),
            str_repeat('-', 80)
        );
        
        return file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX) !== false;
    }
    
    /**
     * Format details for file output (more human-readable)
     */
    private function formatDetailsForFile($details): string {
        if ($details === null) {
            return 'NULL';
        }
        
        if (is_scalar($details)) {
            return (string)$details;
        }
        
        try {
            return json_encode($details, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        } catch (Exception $e) {
            return print_r($details, true);
        }
    }
    
    /**
     * Search logs from database
     */
    public function searchLogs(array $filters = [], int $limit = 100): array {
        $where = [];
        $params = [];
        
        if (!empty($filters['userId'])) {
            $where[] = 'user_id = :user_id';
            $params[':user_id'] = $filters['userId'];
        }
        
        if (!empty($filters['action'])) {
            $where[] = 'action LIKE :action';
            $params[':action'] = '%' . $filters['action'] . '%';
        }
        
        if (!empty($filters['ipAddress'])) {
            $where[] = 'ip_address = :ip_address';
            $params[':ip_address'] = $filters['ipAddress'];
        }
        
        if (!empty($filters['dateFrom'])) {
            $where[] = 'created_at >= :date_from';
            $params[':date_from'] = $filters['dateFrom'];
        }
        
        if (!empty($filters['dateTo'])) {
            $where[] = 'created_at <= :date_to';
            $params[':date_to'] = $filters['dateTo'];
        }
        
        $whereClause = $where ? 'WHERE ' . implode(' AND ', $where) : '';
        
        $stmt = $this->pdo->prepare("
            SELECT * FROM audit_logs 
            {$whereClause} 
            ORDER BY created_at DESC 
            LIMIT :limit
        ");
        
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        foreach ($params as $key => $value) {
            $stmt->bindValue($key, $value);
        }
        
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    /**
     * Log rotation for file logs
     */
    public function rotateLogs(int $maxDays = 30): void {
        $files = glob($this->logDir . '/*.log');
        $now = time();
        
        foreach ($files as $file) {
            if (is_file($file)) {
                if ($now - filemtime($file) >= ($maxDays * 86400)) {
                    unlink($file);
                }
            }
        }
    }
}

/**
 * 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);
}