<?php
/**
 * Security Configuration for Zen Zone Spa API
 * Comprehensive security measures for production deployment
 */

class SecurityConfig {
    
    /**
     * Load environment variables
     */
    public static function loadEnv() {
        $envFile = __DIR__ . '/../.env';
        if (file_exists($envFile)) {
            $lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
            foreach ($lines as $line) {
                if (strpos($line, '#') === 0) continue; // Skip comments
                if (strpos($line, '=') !== false) {
                    list($key, $value) = explode('=', $line, 2);
                    $_ENV[trim($key)] = trim($value);
                }
            }
        }
    }
    
    /**
     * Get environment variable with fallback
     */
    public static function env($key, $default = null) {
        return $_ENV[$key] ?? getenv($key) ?: $default;
    }
    
    /**
     * Set security headers
     */
    public static function setSecurityHeaders() {
        // Prevent clickjacking
        header('X-Frame-Options: DENY');
        
        // Prevent MIME type sniffing
        header('X-Content-Type-Options: nosniff');
        
        // XSS Protection
        header('X-XSS-Protection: 1; mode=block');
        
        // Referrer Policy
        header('Referrer-Policy: strict-origin-when-cross-origin');
        
        // Content Security Policy
        $csp = self::env('CONTENT_SECURITY_POLICY', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net; font-src 'self' fonts.gstatic.com cdn.jsdelivr.net data:; img-src 'self' data: https:; connect-src 'self';");
        header("Content-Security-Policy: $csp");
        
        // Hide server information
        header_remove('X-Powered-By');
        header_remove('Server');
        
        // Force HTTPS in production
        if (self::env('API_ENVIRONMENT') === 'production' && !self::isHTTPS()) {
            header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301);
            exit;
        }
        
        // HSTS Header for HTTPS
        if (self::isHTTPS()) {
            header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
        }
    }
    
    /**
     * Check if request is over HTTPS
     */
    public static function isHTTPS() {
        return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || $_SERVER['SERVER_PORT'] == 443
            || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
    }
    
    /**
     * Rate limiting implementation
     */
    public static function checkRateLimit($identifier = null) {
        $identifier = $identifier ?: self::getClientIP();
        $cacheDir = __DIR__ . '/../cache/rate_limits';
        
        // Create cache directory if it doesn't exist
        if (!is_dir($cacheDir)) {
            @mkdir($cacheDir, 0755, true);
        }
        
        // Skip rate limiting if cache directory is not writable
        if (!is_dir($cacheDir) || !is_writable($cacheDir)) {
            return true; // Allow request if rate limiting can't be enforced
        }
        
        $file = $cacheDir . '/' . md5($identifier) . '.json';
        $maxRequests = (int)self::env('RATE_LIMIT_REQUESTS', 100);
        $window = (int)self::env('RATE_LIMIT_WINDOW', 3600);
        $now = time();
        
        $data = [];
        if (file_exists($file)) {
            $data = json_decode(file_get_contents($file), true) ?: [];
        }
        
        // Clean old entries
        $data = array_filter($data, function($timestamp) use ($now, $window) {
            return ($now - $timestamp) < $window;
        });
        
        // Check if rate limit exceeded
        if (count($data) >= $maxRequests) {
            http_response_code(429);
            header('Retry-After: ' . $window);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Rate limit exceeded',
                'message' => "Maximum $maxRequests requests per hour allowed"
            ]);
            exit;
        }
        
        // Add current request
        $data[] = $now;
        file_put_contents($file, json_encode($data));
    }
    
    /**
     * Get client IP address
     */
    public static function getClientIP() {
        $ipKeys = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 
                   'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
        
        foreach ($ipKeys as $key) {
            if (array_key_exists($key, $_SERVER) === true) {
                $ip = $_SERVER[$key];
                if (strpos($ip, ',') !== false) {
                    $ip = explode(',', $ip)[0];
                }
                $ip = trim($ip);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
    }
    
    /**
     * Sanitize input data
     */
    public static function sanitizeInput($data) {
        if (is_array($data)) {
            $sanitized = [];
            foreach ($data as $key => $value) {
                $sanitized[$key] = self::sanitizeInput($value);
            }
            return $sanitized;
        }
        
        // Handle null values - return as is for API compatibility
        if ($data === null) {
            return null;
        }
        
        // Ensure data is a string before processing
        if (!is_string($data)) {
            $data = (string)$data;
        }
        
        // Remove null bytes
        $data = str_replace(chr(0), '', $data);
        
        // Trim whitespace
        $data = trim($data);
        
        // Convert special characters to HTML entities
        $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
        
        return $data;
    }
    
    /**
     * Validate and sanitize email
     */
    public static function sanitizeEmail($email) {
        $email = filter_var($email, FILTER_SANITIZE_EMAIL);
        return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : false;
    }
    
    /**
     * Generate secure random string
     */
    public static function generateSecureToken($length = 32) {
        return bin2hex(random_bytes($length));
    }
    
    /**
     * Hash password securely
     */
    public static function hashPassword($password) {
        return password_hash($password, PASSWORD_ARGON2ID, [
            'memory_cost' => 65536, // 64 MB
            'time_cost' => 4,       // 4 iterations
            'threads' => 3          // 3 threads
        ]);
    }
    
    /**
     * Verify password
     */
    public static function verifyPassword($password, $hash) {
        return password_verify($password, $hash);
    }
    
    /**
     * Log security events
     */
    public static function logSecurityEvent($event, $details = []) {
        try {
            $logDir = __DIR__ . '/../logs';
            if (!is_dir($logDir)) {
                @mkdir($logDir, 0755, true);
            }
            
            // Only log if directory is writable
            if (is_dir($logDir) && is_writable($logDir)) {
                $logFile = $logDir . '/security.log';
                $timestamp = date('Y-m-d H:i:s');
                $ip = self::getClientIP();
                $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
                
                $logEntry = [
                    'timestamp' => $timestamp,
                    'event' => $event,
                    'ip' => $ip,
                    'user_agent' => $userAgent,
                    'details' => $details
                ];
                
                @file_put_contents($logFile, json_encode($logEntry) . PHP_EOL, FILE_APPEND | LOCK_EX);
            }
        } catch (Exception $e) {
            // Silently fail - don't break application if logging fails
            error_log("Security logging failed: " . $e->getMessage());
        }
    }
    
    /**
     * Validate file upload
     */
    public static function validateFileUpload($file) {
        $allowedTypes = explode(',', self::env('ALLOWED_FILE_TYPES', 'jpg,jpeg,png,pdf'));
        $maxSize = self::parseSize(self::env('MAX_UPLOAD_SIZE', '10M'));
        
        // Check file size
        if ($file['size'] > $maxSize) {
            return ['valid' => false, 'error' => 'File too large'];
        }
        
        // Check file type
        $fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($fileExt, $allowedTypes)) {
            return ['valid' => false, 'error' => 'File type not allowed'];
        }
        
        // Check MIME type
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);
        
        $allowedMimes = [
            'jpg' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'pdf' => 'application/pdf'
        ];
        
        if (!isset($allowedMimes[$fileExt]) || $mimeType !== $allowedMimes[$fileExt]) {
            return ['valid' => false, 'error' => 'Invalid file type'];
        }
        
        return ['valid' => true];
    }
    
    /**
     * Parse size string (e.g., "10M" to bytes)
     */
    private static function parseSize($size) {
        $unit = strtoupper(substr($size, -1));
        $value = (int)substr($size, 0, -1);
        
        switch ($unit) {
            case 'G': return $value * 1024 * 1024 * 1024;
            case 'M': return $value * 1024 * 1024;
            case 'K': return $value * 1024;
            default: return (int)$size;
        }
    }
    
    /**
     * Check if request is from allowed origin - Updated for zenzone.rw
     */
    public static function validateOrigin() {
        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
        
        // No origin header means direct API call (like Postman/curl) - allow it
        if (empty($origin)) {
            return;
        }
        
        // Production allowed origins
        $allowedOrigins = [
            self::env('APP_URL'),
            'https://zenzone.rw',
            'https://www.zenzone.rw'
        ];
        
        // Development mode: allow localhost and local IPs
        $isDevelopment = self::env('API_ENVIRONMENT') !== 'production';
        if ($isDevelopment || self::env('ALLOW_DEV_ORIGINS', true)) {
            // Allow localhost on any port
            if (preg_match('/^https?:\/\/(localhost|127\.0\.0\.1|192\.168\.\d+\.\d+)(:\d+)?$/i', $origin)) {
                return; // Allow local development
            }
        }
        
        // Check if origin is in allowed list
        if (!in_array($origin, array_filter($allowedOrigins))) {
            self::logSecurityEvent('invalid_origin', ['origin' => $origin]);
            
            // Send CORS headers even when blocking (required for proper error handling in browsers)
            header('Access-Control-Allow-Origin: *');
            header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
            header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
            header('Content-Type: application/json; charset=utf-8');
            
            http_response_code(403);
            echo json_encode([
                'success' => false,
                'message' => 'Forbidden origin',
                'timestamp' => date('Y-m-d H:i:s'),
                'status_code' => 403
            ]);
            exit;
        }
    }
    
    /**
     * Clean up old cache and log files
     */
    public static function cleanup() {
        $dirs = [
            __DIR__ . '/../cache/rate_limits',
            __DIR__ . '/../logs'
        ];
        
        $maxAge = 24 * 3600; // 24 hours
        
        foreach ($dirs as $dir) {
            if (!is_dir($dir)) continue;
            
            $files = glob($dir . '/*');
            foreach ($files as $file) {
                if (is_file($file) && (time() - filemtime($file)) > $maxAge) {
                    unlink($file);
                }
            }
        }
    }
}

// Initialize security on include
SecurityConfig::loadEnv();
// Note: Security headers are set in Response::send() to avoid "headers already sent" errors