<?php

/**
 * Simple file-based caching system for frequent queries
 * Optimizes performance by caching database results
 */
class Cache {
    private static $cacheDir = __DIR__ . '/../../cache/';
    private static $defaultTTL = 3600; // 1 hour default
    
    /**
     * Initialize cache directory
     */
    public static function init() {
        if (!file_exists(self::$cacheDir)) {
            mkdir(self::$cacheDir, 0755, true);
            
            // Create .htaccess to protect cache directory
            $htaccess = "Order deny,allow\nDeny from all";
            file_put_contents(self::$cacheDir . '.htaccess', $htaccess);
        }
    }
    
    /**
     * Generate cache key from data
     */
    private static function generateKey($key) {
        return md5($key);
    }
    
    /**
     * Get cache file path
     */
    private static function getCacheFile($key) {
        return self::$cacheDir . self::generateKey($key) . '.cache';
    }
    
    /**
     * Store data in cache
     */
    public static function set($key, $data, $ttl = null) {
        self::init();
        
        $ttl = $ttl ?? self::$defaultTTL;
        $cacheFile = self::getCacheFile($key);
        
        $cacheData = [
            'data' => $data,
            'expires' => time() + $ttl,
            'created' => time()
        ];
        
        return file_put_contents($cacheFile, serialize($cacheData)) !== false;
    }
    
    /**
     * Get data from cache
     */
    public static function get($key) {
        $cacheFile = self::getCacheFile($key);
        
        if (!file_exists($cacheFile)) {
            // Track cache miss
            if (class_exists('PerformanceMonitor')) {
                PerformanceMonitor::trackCacheMiss();
            }
            return null;
        }
        
        $cacheData = unserialize(file_get_contents($cacheFile));
        
        // Check if cache has expired
        if (time() > $cacheData['expires']) {
            self::delete($key);
            // Track cache miss (expired)
            if (class_exists('PerformanceMonitor')) {
                PerformanceMonitor::trackCacheMiss();
            }
            return null;
        }
        
        // Track cache hit
        if (class_exists('PerformanceMonitor')) {
            PerformanceMonitor::trackCacheHit();
        }
        
        return $cacheData['data'];
    }
    
    /**
     * Check if cache exists and is valid
     */
    public static function has($key) {
        return self::get($key) !== null;
    }
    
    /**
     * Delete cache entry
     */
    public static function delete($key) {
        $cacheFile = self::getCacheFile($key);
        
        if (file_exists($cacheFile)) {
            return unlink($cacheFile);
        }
        
        return true;
    }
    
    /**
     * Clear all cache
     */
    public static function clear() {
        if (!file_exists(self::$cacheDir)) {
            return true;
        }
        
        $files = glob(self::$cacheDir . '*.cache');
        foreach ($files as $file) {
            unlink($file);
        }
        
        return true;
    }
    
    /**
     * Get cache statistics
     */
    public static function getStats() {
        if (!file_exists(self::$cacheDir)) {
            return ['files' => 0, 'size' => 0];
        }
        
        $files = glob(self::$cacheDir . '*.cache');
        $totalSize = 0;
        
        foreach ($files as $file) {
            $totalSize += filesize($file);
        }
        
        return [
            'files' => count($files),
            'size' => $totalSize,
            'size_formatted' => self::formatBytes($totalSize)
        ];
    }
    
    /**
     * Format bytes to human readable format
     */
    private static function formatBytes($bytes, $precision = 2) {
        $units = array('B', 'KB', 'MB', 'GB', 'TB');
        
        for ($i = 0; $bytes > 1024; $i++) {
            $bytes /= 1024;
        }
        
        return round($bytes, $precision) . ' ' . $units[$i];
    }
    
    /**
     * Cache wrapper for database queries
     */
    public static function remember($key, $callback, $ttl = null) {
        $cached = self::get($key);
        
        if ($cached !== null) {
            return $cached;
        }
        
        $result = $callback();
        self::set($key, $result, $ttl);
        
        return $result;
    }
    
    /**
     * Invalidate cache by pattern
     */
    public static function invalidatePattern($pattern) {
        if (!file_exists(self::$cacheDir)) {
            return true;
        }
        
        $files = glob(self::$cacheDir . '*.cache');
        $deleted = 0;
        
        foreach ($files as $file) {
            $content = unserialize(file_get_contents($file));
            if (isset($content['key']) && fnmatch($pattern, $content['key'])) {
                unlink($file);
                $deleted++;
            }
        }
        
        return $deleted;
    }
    
    /**
     * Enhanced cache storage with key tracking
     */
    public static function setWithKey($key, $data, $ttl = null) {
        self::init();
        
        $ttl = $ttl ?? self::$defaultTTL;
        $cacheFile = self::getCacheFile($key);
        
        $cacheData = [
            'key' => $key,
            'data' => $data,
            'expires' => time() + $ttl,
            'created' => time()
        ];
        
        return file_put_contents($cacheFile, serialize($cacheData)) !== false;
    }
    
    /**
     * Cache database query results with automatic invalidation
     */
    public static function cacheQuery($key, $query, $params = [], $ttl = null) {
        return self::remember($key, function() use ($query, $params) {
            global $pdo;
            
            try {
                $stmt = $pdo->prepare($query);
                $stmt->execute($params);
                return $stmt->fetchAll(PDO::FETCH_ASSOC);
            } catch (PDOException $e) {
                error_log("Cache query error: " . $e->getMessage());
                return [];
            }
        }, $ttl);
    }
    
    /**
     * Get cache tags for organized invalidation
     */
    public static function tag($tags, $key, $data, $ttl = null) {
        if (!is_array($tags)) {
            $tags = [$tags];
        }
        
        // Store data with tags
        $result = self::setWithKey($key, $data, $ttl);
        
        // Store tag mappings
        foreach ($tags as $tag) {
            $tagKey = "tag:$tag";
            $taggedKeys = self::get($tagKey) ?: [];
            $taggedKeys[] = $key;
            self::setWithKey($tagKey, array_unique($taggedKeys), 86400); // Tags expire in 24h
        }
        
        return $result;
    }
    
    /**
     * Invalidate all cache entries with specific tags
     */
    public static function invalidateTag($tag) {
        $tagKey = "tag:$tag";
        $taggedKeys = self::get($tagKey);
        
        if (!$taggedKeys) {
            return 0;
        }
        
        $deleted = 0;
        foreach ($taggedKeys as $key) {
            if (self::delete($key)) {
                $deleted++;
            }
        }
        
        // Clear the tag itself
        self::delete($tagKey);
        
        return $deleted;
    }
    
    /**
     * Cleanup expired cache entries
     */
    public static function cleanup() {
        if (!file_exists(self::$cacheDir)) {
            return 0;
        }
        
        $files = glob(self::$cacheDir . '*.cache');
        $deleted = 0;
        
        foreach ($files as $file) {
            $content = unserialize(file_get_contents($file));
            if (time() > $content['expires']) {
                unlink($file);
                $deleted++;
            }
        }
        
        return $deleted;
    }
}