<?php
/**
 * Cloudflare CDN Adapter
 *
 * Implements CDN provider interface for Cloudflare integration including
 * URL purging, tag-based purging, APO support, and cache-everything purging.
 *
 * @package Mamba\Modules\CDN\Services
 * @since   1.0.0
 */

namespace Mamba\Modules\CDN\Services;

/**
 * Class CloudflareAdapter
 *
 * Cloudflare CDN adapter that handles cache purging via the Cloudflare API,
 * supports APO detection, and provides tag-based invalidation.
 *
 * @since 1.0.0
 */
final class CloudflareAdapter implements Provider {
    private string $zoneId;
    private string $apiToken;
    private bool $detectApo;
    private bool $sendCacheTags;
    private const API_BASE = 'https://api.cloudflare.com/client/v4';
    
    public function __construct(
        string $zoneId,
        string $apiToken,
        bool $detectApo = true,
        bool $sendCacheTags = true
    ) {
        $this->zoneId = $zoneId;
        $this->apiToken = $apiToken;
        $this->detectApo = $detectApo;
        $this->sendCacheTags = $sendCacheTags;
    }
    
    public function isConnected(): bool {
        return !empty($this->zoneId) && !empty($this->apiToken);
    }
    
    public function getName(): string {
        return 'cloudflare';
    }
    
    public function purgeUrls(array $urls, array $headerCombos = []): Result {
        if (empty($urls)) {
            return Result::success('No URLs to purge');
        }
        
        $files = [];
        
        if ($headerCombos && $this->detectApo) {
            // Include header combinations for APO/custom cache keys
            foreach (array_unique($urls) as $url) {
                foreach ($headerCombos as $h) {
                    $files[] = ['url' => $url, 'headers' => $h];
                }
            }
        } else {
            // Simple URL purge
            foreach (array_unique($urls) as $url) {
                $files[] = $url;
            }
        }
        
        return $this->purgeInBatches($files);
    }
    
    public function purgeTags(array $tags): Result {
        if (empty($tags)) {
            return Result::success('No tags to purge');
        }

        // tags don't need header combos
        $chunks = array_chunk(array_values(array_unique($tags)), 50);
        $firstError = null;

        foreach ($chunks as $chunk) {
            $res = $this->post("/zones/{$this->zoneId}/purge_cache", ['tags' => $chunk]);
            if (!$res->isSuccess()) {
                $firstError ??= $res;
            }
        }
        return $firstError ?: Result::success('Tag purge successful');
    }
    
    public function purgeAll(): Result {
        $payload = ['purge_everything' => true];
        return $this->post("/zones/{$this->zoneId}/purge_cache", $payload);
    }
    
    public function applyRecommendedSettings(): Result {
        $settings = [
            'caching_level' => 'standard',
            'rocket_loader' => 'off',
            'browser_cache_ttl' => 31536000 // 1 year
        ];
        
        $results = [];
        foreach ($settings as $setting => $value) {
            $result = $this->patch("/zones/{$this->zoneId}/settings/{$setting}", ['value' => $value]);
            $results[$setting] = $result;
            
            // Track Rocket Loader status for cache key generation
            if ($setting === 'rocket_loader' && $result->isSuccess()) {
                update_option('mamba_cloudflare_rocket_loader_status', $value, false);
            }
        }
        
        $success = true;
        $messages = [];
        foreach ($results as $setting => $result) {
            if (!$result->isSuccess()) {
                $success = false;
                $messages[] = "Failed to set {$setting}: " . $result->getMessage();
            }
        }
        
        if ($success) {
            return Result::success('Recommended Cloudflare settings applied successfully');
        } else {
            return Result::error('Failed to apply some settings: ' . implode(', ', $messages));
        }
    }
    
    public function testConnection(): Result {
        $result = $this->get("/zones/{$this->zoneId}");
        if ($result->isSuccess()) {
            $data = $result->getData();
            $zoneName = $data['result']['name'] ?? 'Unknown';
            
            // Also check APO status
            $apoResult = $this->get("/zones/{$this->zoneId}/settings/automatic_platform_optimization");
            if ($apoResult->isSuccess()) {
                $apoData = $apoResult->getData();
                $apoEnabled = $apoData['result']['value']['enabled'] ?? false;
                
                // Cache APO status for detection
                update_option('mamba_cloudflare_apo_status', $apoEnabled ? 'enabled' : 'disabled', false);
                
                if ($apoEnabled) {
                    return Result::success("Connected to Cloudflare zone: {$zoneName} (APO enabled)");
                }
            }
            
            return Result::success("Connected to Cloudflare zone: {$zoneName}");
        }
        return $result;
    }
    
    /**
     * Purge files in batches with retry logic
     */
    private function purgeInBatches(array $filesPayload): Result {
        $batches = array_chunk($filesPayload, 50); // safe headroom under 100
        $firstError = null;

        foreach ($batches as $batch) {
            $res = $this->post("/zones/{$this->zoneId}/purge_cache", ['files' => $batch]);
            if (!$res->isSuccess()) {
                $firstError ??= $res;
                // basic backoff on 429/5xx
                $code = $res->getStatusCode();
                if ($code === 429 || $code >= 500) {
                    usleep(300000); // 300ms, simple backoff
                }
            }
        }

        return $firstError ?: Result::success('Purge successful');
    }

    /**
     * Make a GET request to Cloudflare API
     */
    private function get(string $endpoint): Result {
        return $this->request('GET', $endpoint);
    }
    
    /**
     * Make a POST request to Cloudflare API
     */
    private function post(string $endpoint, array $data): Result {
        return $this->request('POST', $endpoint, $data);
    }
    
    /**
     * Make a PATCH request to Cloudflare API
     */
    private function patch(string $endpoint, array $data): Result {
        return $this->request('PATCH', $endpoint, $data);
    }
    
    /**
     * Make a request to Cloudflare API with retries and error handling
     */
    private function request(string $method, string $endpoint, array $data = []): Result {
        $url = self::API_BASE . $endpoint;
        
        $args = [
            'method' => $method,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->apiToken,
                'Content-Type' => 'application/json',
                'User-Agent' => 'Mamba-WooCommerce-CDN/1.0'
            ],
            'timeout' => 30,
            'sslverify' => true
        ];
        
        if (!empty($data)) {
            $args['body'] = wp_json_encode($data);
        }
        
        $response = wp_remote_request($url, $args);
        
        if (is_wp_error($response)) {
            return Result::error('Request failed: ' . $response->get_error_message());
        }
        
        $statusCode = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if ($statusCode >= 200 && $statusCode < 300) {
            return Result::success('Request successful', $data ?: []);
        }
        
        $errorMessage = 'API request failed';
        if ($data && isset($data['errors'][0]['message'])) {
            $errorMessage = $data['errors'][0]['message'];
        }
        
        return Result::error($errorMessage, $statusCode, $data ?: []);
    }
}
