<?php
/**
 * Store API File Cache Service
 *
 * Provides file-based caching for WooCommerce Store API responses,
 * enabling fast REST API response times for product and cart data.
 *
 * @package Mamba\Modules\Caching\Services
 * @since   1.0.0
 */

namespace Mamba\Modules\Caching\Services;

use Mamba\Support\CacheAdapter;

/**
 * Class StoreApiFileCache
 *
 * File-based cache for Store API responses with automatic directory
 * creation, protection files, and TTL-based expiration.
 *
 * @since 1.0.0
 */
final class StoreApiFileCache {
    
    private static function base(): string {
        $dir = WP_CONTENT_DIR . '/cache/mamba/api';
        if (!is_dir($dir)) {
            wp_mkdir_p($dir);
            // Create protection files when directory is created
            self::createProtectionFiles($dir);
        }
        return $dir;
    }
    
    private static function path(string $key): string {
        $b = self::base();
        $h = sha1($key);
        $d = $b . '/' . substr($h, 0, 2); // Subfolder by first 2 chars
        if (!is_dir($d)) {
            wp_mkdir_p($d);
            // Create protection files in subdirectory
            self::createProtectionFiles($d);
        }
        return $d . '/' . $h . '.json';
    }
    
    public static function get(string $key, int $ttl): ?array {
        // Use universal cache adapter
        // Note: TTL is handled by CacheAdapter, so not needed here
        return CacheAdapter::get($key, 'mamba_store_api');
    }
    
    public static function set(string $key, array $payload, ?int $ttl = null): bool {
        // Use universal cache adapter with TTL
        if ($ttl === null) {
            $ttl = (int)get_option('mamba_store_api_ttl', 600);
        }
        return CacheAdapter::set($key, $payload, 'mamba_store_api', $ttl);
    }
    
    public static function clear(): void {
        // Use universal cache adapter to clear group
        CacheAdapter::clearGroup('mamba_store_api');
    }
    
    public static function gc(int $maxFiles = 5000, int $maxAge = 3600): void {
        $base = self::base();
        if (!is_dir($base)) {
            return;
        }
        
        $it = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($base, \FilesystemIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );
        
        $count = 0;
        $toDelete = [];
        
        foreach ($it as $file) {
            if ($file->isFile() && substr($file->getFilename(), -5) === '.json') {
                $count++;
                if ((time() - $file->getMTime()) > $maxAge) {
                    $toDelete[] = $file->getPathname();
                }
            }
        }
        
        // If too many files, delete oldest beyond cap
        if ($count > $maxFiles) {
            usort($toDelete, function($a, $b) {
                return filemtime($a) <=> filemtime($b);
            });
            $excess = $count - $maxFiles;
            $toDelete = array_slice($toDelete, 0, max($excess, 0));
        }
        
        foreach ($toDelete as $path) {
            @unlink($path);
        }
    }
    
    public static function isWritable(): bool {
        $base = self::base();
        return is_dir($base) && is_writable($base);
    }

    /**
     * Serve cached Store API response with unified logic
     * Unified serving logic to prevent code drift between AdminPage and StoreApiFileCache
     */
    public static function serveCache(string $key, int $ttl, string $route, array $queryParams, bool $isMicroCache = false): ?\WP_REST_Response {
        $cached = self::get($key, $ttl);
        if (!is_array($cached) || !isset($cached['data'])) {
            return null; // Cache miss
        }

        // Generate ETag for browser revalidation
        $etag = self::generateETag($key, $cached['data']);
        
        // Check If-None-Match header for 304 response
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_text_field
        $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_IF_NONE_MATCH'])) : '';
        $matches = array_map('trim', explode(',', $ifNoneMatch));
        if (in_array($etag, $matches, true)) {
            \Mamba\Modules\Caching\Services\Stats::recordHit('store_api');
            $resp = new \WP_REST_Response(null, 304);
            $resp->header('ETag', $etag);
            $resp->header('X-Mamba-Cache', 'HIT');
            return $resp;
        }
        
        $resp = new \WP_REST_Response($cached['data'], (int)($cached['status'] ?? 200));
        
        // Add cache header for transparency
        $cacheType = $isMicroCache ? 'MICRO-HIT' : 'HIT';
        $resp->header('X-Mamba-Cache', $cacheType);
        
        // Add ETag header for browser revalidation
        $resp->header('ETag', $etag);
        
        // Add Vary headers for proxy correctness
        // Removed broad Cookie Vary to prevent variant explosion - currency/language handled by variance signature
        $vary = apply_filters('mamba_store_api_vary', [
            'Accept-Language', // Language variants
            'User-Agent', // Device variants
            // Note: Cookie removed - currency/language variants handled by variance signature
        ]);
        
        // Add geolocation headers if used
        $geoHeaders = ['MAMBA_GEO', 'X-Geo-Country', 'GeoIP-Country-Code'];
        foreach ($geoHeaders as $header) {
            if (!empty($_SERVER['HTTP_' . str_replace('-', '_', strtoupper($header))])) {
                $vary[] = $header;
                break; // Only include the first one found
            }
        }
        
        // Always include Accept-Encoding for compression variants
        $vary[] = 'Accept-Encoding';
        
        if (!empty($vary)) {
            $resp->header('Vary', implode(', ', array_unique($vary)));
        }
        
        if (!empty($cached['headers']) && is_array($cached['headers'])) {
            foreach ($cached['headers'] as $name => $value) {
                $resp->header($name, $value);
            }
        }
        
        // Add protective Cache-Control for per-cart responses if not already set
        if ($isMicroCache && empty($cached['headers']['cache-control'])) {
            $resp->header('Cache-Control', 'private, max-age=0, must-revalidate');
        }
        
        \Mamba\Modules\Caching\Services\Stats::recordHit('store_api');
        return $resp;
    }

    /**
     * Store Store API response with unified logic
     * Unified storing logic to prevent code drift between AdminPage and StoreApiFileCache
     */
    public static function storeCache(string $key, \WP_REST_Response $response, int $ttl, bool $isWarmup = false, bool $isMicroCache = false): void {
        // Store API Caching requires the dedicated option to be enabled
        // Note: Premium gate is handled at the AdminPage level (maybeServeRestCache/maybeStoreRestCache)
        if (!get_option('mamba_enable_store_api_cache', 0)) {
            return;
        }

        $status  = method_exists($response,'get_status')  ? (int)$response->get_status()  : 200;
        $data    = method_exists($response,'get_data')    ? $response->get_data()         : $response;
        $headers = method_exists($response,'get_headers') ? (array)$response->get_headers(): [];

        // Whitelist & normalize headers (and drop 'etag' and 'vary' so we can set our own later)
        $safeHeaders = ['content-type','cache-control','last-modified','link','allow']; // <- removed 'etag' and 'vary'
        $filteredHeaders = [];
        if (is_array($headers)) {
            $headers = array_change_key_case($headers, CASE_LOWER);
            $filteredHeaders = array_intersect_key($headers, array_flip($safeHeaders));
        }

        $payload = ['status'=>$status,'data'=>$data,'headers'=>$filteredHeaders,'t'=>time()];
        self::set($key, $payload, $ttl);

        // Add response headers for cache transparency
        if (method_exists($response, 'header')) {
            $cacheType = $isWarmup ? 'WARMUP' : ($isMicroCache ? 'MICRO-MISS' : 'MISS');
            $response->header('X-Mamba-Cache', $cacheType);
            
            // Set/merge Vary only once
            // Removed broad Cookie Vary to prevent variant explosion - currency/language handled by variance signature
            $includeAcceptLanguage = get_option('mamba_include_accept_language_vary', true);
        $defaultVary = ['User-Agent','Accept-Encoding'];
        if ($includeAcceptLanguage && (function_exists('pll_current_language') || defined('ICL_LANGUAGE_CODE'))) {
            $defaultVary[] = 'Accept-Language';
        }
        $vary = apply_filters('mamba_store_api_vary', $defaultVary);
            // append one geo header if present
            foreach (['MAMBA_GEO','X-Geo-Country','GeoIP-Country-Code'] as $h) {
                if (!empty($_SERVER['HTTP_' . str_replace('-','_',strtoupper($h))])) { $vary[] = $h; break; }
            }
            $response->header('Vary', implode(', ', array_unique($vary)));
        }
    }

    /**
     * Generate ETag for Store API responses
     * Enables browser revalidation (304 responses)
     */
    private static function generateETag(string $key, $data): string {
        // Hybrid approach: combine cache key and data for accurate ETag
        // This ensures ETag changes when either the request or response changes
        $content = $key . serialize($data);
        return '"' . md5($content) . '"';
    }

    /**
     * Create protection files in cache directory
     */
    private static function createProtectionFiles(string $cacheDir): void {
        // Create index.html to prevent directory listing
        $indexFile = $cacheDir . '/index.html';
        if (!file_exists($indexFile)) {
            @file_put_contents($indexFile, '<!-- Directory access denied -->');
        }
        
        // Create .htaccess for Apache protection - DENY ALL ACCESS
        $htaccessFile = $cacheDir . '/.htaccess';
        if (!file_exists($htaccessFile)) {
            $htaccessContent = "<IfModule mod_authz_core.c>\n  Require all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\n  Deny from all\n</IfModule>\n";
            @file_put_contents($htaccessFile, $htaccessContent);
        }
        
        // Create web.config for IIS protection - DENY ALL ACCESS
        $webConfigFile = $cacheDir . '/web.config';
        if (!file_exists($webConfigFile)) {
            $webConfigContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n  <system.webServer>\n    <security>\n      <requestFiltering>\n        <denyUrlSequences>\n          <add sequence=\".\" />\n        </denyUrlSequences>\n      </requestFiltering>\n    </security>\n    <httpErrors errorMode=\"Custom\">\n      <remove statusCode=\"403\" />\n      <error statusCode=\"403\" path=\"\" responseMode=\"ExecuteURL\" />\n    </httpErrors>\n  </system.webServer>\n</configuration>";
            @file_put_contents($webConfigFile, $webConfigContent);
        }
    }
}
