<?php
declare(strict_types=1);

namespace Ekesto\CacheWarmup\Warmup;

use Ekesto\CacheWarmup\Debug;

/**
 * WordPress HTTP API transport for targeted Brotli/browser-profile warming.
 */
final class TargetedBrotliTransport
{
    /** Maximum redirects followed for one warm request. */
    private const MAX_REDIRECTS = 3;

    /** Private request argument used to scope the cURL decoding override. */
    private const PROFILE_REQUEST_ARG = 'ekesto_ca_brotli_profile_request';

    /**
     * Determine if WordPress HTTP API warmup prerequisites are available.
     *
     * @return array{supported:bool,reason:string} Support state.
     */
    public static function supportStatus(): array
    {
        if (!function_exists('wp_safe_remote_get')) {
            return ['supported' => false, 'reason' => 'wordpress_http_api_unavailable'];
        }

        if (function_exists('curl_init') && !defined('CURLOPT_HTTP_CONTENT_DECODING')) {
            return ['supported' => false, 'reason' => 'curl_decoding_override_unavailable'];
        }

        return ['supported' => true, 'reason' => 'wordpress_http_api'];
    }

    /**
     * Warm one URL through the WordPress HTTP API with a browser-like Brotli request profile.
     *
     * @param string $url URL to warm.
     * @param int $timeoutSeconds Timeout in seconds.
     * @param string $userAgentSuffix User agent marker suffix (for example ECW).
     * @return array<string,mixed> Structured warm result.
     */
    public static function warm(string $url, int $timeoutSeconds, string $userAgentSuffix): array
    {
        $result = self::defaultResult();
        $support = self::supportStatus();

        if (!$support['supported']) {
            $result['error'] = (string) $support['reason'];
            return $result;
        }

        $url = trim($url);
        if ($url === '') {
            $result['error'] = 'empty_url';
            return $result;
        }

        $scheme = strtolower((string) wp_parse_url($url, PHP_URL_SCHEME));
        if ($scheme !== 'https') {
            $result['error'] = 'non_https_url';
            return $result;
        }

        $started = microtime(true);
        $result['attempted'] = true;
        $response = null;
        $disableAutomaticContentDecoding = static function ($handle, array $parsedArgs, string $requestUrl): void {
            unset($requestUrl);

            if (empty($parsedArgs[self::PROFILE_REQUEST_ARG])) {
                return;
            }

            if (!defined('CURLOPT_HTTP_CONTENT_DECODING')) {
                return;
            }

            // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt -- Scoped cURL option through WordPress's http_api_curl hook.
            curl_setopt($handle, CURLOPT_HTTP_CONTENT_DECODING, false);
        };

        add_action('http_api_curl', $disableAutomaticContentDecoding, PHP_INT_MAX, 3);
        try {
            $response = wp_safe_remote_get(
                $url,
                self::requestArgs(max(1, $timeoutSeconds), $userAgentSuffix)
            );
        } catch (\Throwable $error) {
            $result['error'] = 'wordpress_http_exception:' . $error->getMessage();
        } finally {
            remove_action('http_api_curl', $disableAutomaticContentDecoding, PHP_INT_MAX);
        }

        $result['ms'] = (int) round((microtime(true) - $started) * 1000);

        if (is_wp_error($response)) {
            $result['error'] = 'wordpress_http_error:' . $response->get_error_message();
        } elseif (is_array($response)) {
            $code = wp_remote_retrieve_response_code($response);
            $result['code'] = $code;
            $result['ok'] = $code >= 200 && $code < 400;
            $result['final_url'] = self::responseFinalUrl($response, $url);
            $result['redirects'] = self::responseRedirectCount($response);
            $result['content_encoding'] = (string) wp_remote_retrieve_header($response, 'content-encoding');
            $result['vary'] = (string) wp_remote_retrieve_header($response, 'vary');
            $result['etag'] = (string) wp_remote_retrieve_header($response, 'etag');
            $result['cache_ctrl'] = (string) wp_remote_retrieve_header($response, 'cache-control');
            $result['age'] = (string) wp_remote_retrieve_header($response, 'age');
            $result['lscache'] = (string) wp_remote_retrieve_header($response, 'x-litespeed-cache');
            $result['lscache_ctrl'] = (string) wp_remote_retrieve_header($response, 'x-litespeed-cache-control');
            $result['lscache_vary'] = (string) wp_remote_retrieve_header($response, 'x-litespeed-vary');
        }

        if (!$result['ok'] && $result['error'] === '') {
            $result['error'] = 'wordpress_http_request_unsuccessful';
        }

        Debug::verbose('TargetedBrotliTransport::warm', [
            'url' => $url,
            'final_url' => $result['final_url'],
            'code' => $result['code'],
            'ok' => $result['ok'],
            'ms' => $result['ms'],
            'error' => $result['error'],
            'redirects' => $result['redirects'],
            'content_encoding' => $result['content_encoding'],
            'vary' => $result['vary'],
            'etag' => $result['etag'],
            'lscache' => $result['lscache'],
        ]);

        return $result;
    }

    /**
     * Build WordPress HTTP API request arguments for the browser profile.
     *
     * @param int $timeoutSeconds Timeout in seconds.
     * @param string $userAgentSuffix User agent marker suffix.
     * @return array<string,mixed> Request arguments.
     */
    private static function requestArgs(int $timeoutSeconds, string $userAgentSuffix): array
    {
        $suffix = trim($userAgentSuffix);
        $userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';
        if ($suffix !== '') {
            $userAgent .= ' ' . $suffix;
        }

        return [
            'timeout' => $timeoutSeconds,
            'redirection' => self::MAX_REDIRECTS,
            'httpversion' => '1.1',
            'sslverify' => true,
            'decompress' => false,
            'user-agent' => $userAgent,
            self::PROFILE_REQUEST_ARG => true,
            'headers' => [
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
                'Accept-Encoding' => 'gzip, deflate, br',
                'Accept-Language' => 'en-US,en;q=0.9',
                'Cache-Control' => 'no-cache',
                'Pragma' => 'no-cache',
                'Upgrade-Insecure-Requests' => '1',
                'Sec-Fetch-Dest' => 'document',
                'Sec-Fetch-Mode' => 'navigate',
                'Sec-Fetch-Site' => 'none',
            ],
        ];
    }

    /**
     * Extract the final response URL when the active WordPress transport exposes it.
     *
     * @param array<string,mixed> $response WordPress HTTP response.
     * @param string $fallbackUrl Original request URL.
     * @return string Final response URL or original request URL.
     */
    private static function responseFinalUrl(array $response, string $fallbackUrl): string
    {
        $responseObject = self::responseObject($response);
        if ($responseObject !== null && isset($responseObject->url) && is_string($responseObject->url)) {
            $finalUrl = trim($responseObject->url);
            if ($finalUrl !== '') {
                return $finalUrl;
            }
        }

        return $fallbackUrl;
    }

    /**
     * Extract the redirect count when the active WordPress transport exposes history.
     *
     * @param array<string,mixed> $response WordPress HTTP response.
     * @return int Redirect count.
     */
    private static function responseRedirectCount(array $response): int
    {
        $responseObject = self::responseObject($response);
        if ($responseObject === null || !isset($responseObject->history) || !is_array($responseObject->history)) {
            return 0;
        }

        return count($responseObject->history);
    }

    /**
     * Retrieve the underlying Requests response object defensively.
     *
     * @param array<string,mixed> $response WordPress HTTP response.
     * @return object|null Requests response object when available.
     */
    private static function responseObject(array $response): ?object
    {
        $httpResponse = $response['http_response'] ?? null;
        if (!is_object($httpResponse) || !method_exists($httpResponse, 'get_response_object')) {
            return null;
        }

        try {
            $responseObject = $httpResponse->get_response_object();
        } catch (\Throwable $error) {
            unset($error);
            return null;
        }

        return is_object($responseObject) ? $responseObject : null;
    }

    /**
     * Return the stable structured result shape used by Runner diagnostics.
     *
     * @return array<string,mixed> Default warm result.
     */
    private static function defaultResult(): array
    {
        return [
            'attempted' => false,
            'ok' => false,
            'code' => 0,
            'ms' => 0,
            'error' => '',
            'final_url' => '',
            'redirects' => 0,
            'content_encoding' => '',
            'vary' => '',
            'etag' => '',
            'cache_ctrl' => '',
            'age' => '',
            'lscache' => '',
            'lscache_ctrl' => '',
            'lscache_vary' => '',
        ];
    }
}
