<?php

declare(strict_types=1);

namespace ShieldX\Licensing;

defined('ABSPATH') || exit;

final class LicenseManager
{
    private const SESSION_CACHE_PREFIX = 'shieldx_license_session_';
    private const SESSION_CACHE_TTL_SECONDS = 604800;
    private const SITE_BACKOFF_CACHE_PREFIX = 'shieldx_license_backoff_';
    private const VALIDATION_LOCK_PREFIX = 'shieldx_license_lock_';
    private const VALIDATION_LOCK_SECONDS = 30;
    private const REMOTE_ERROR_BACKOFF_SECONDS = 300;
    private const HTTP_TIMEOUT_SECONDS = 20;

    /** @var LicenseSettings */
    private $settings;

    /** @var callable|null */
    private $httpPost;

    /** @var callable|null */
    private $httpFallbackPost;

    /** @var callable|null */
    private $instanceResolver;

    /** @var bool */
    private $lastRemoteValidationFailed = false;

    public function __construct(
        LicenseSettings $settings,
        ?callable $httpPost = null,
        ?callable $instanceResolver = null,
        ?callable $httpFallbackPost = null
    ) {
        $this->settings = $settings;
        $this->httpPost = $httpPost;
        $this->instanceResolver = $instanceResolver;
        $this->httpFallbackPost = $httpFallbackPost;
    }

    public static function fromWordPress(): self
    {
        return new self(LicenseSettings::fromWordPress());
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    public function activate(string $licenseKey): array
    {
        $licenseKey = LicenseSettings::sanitizeLicenseKey($licenseKey);
        if ($licenseKey === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_INACTIVE, __('Enter an activation code.', 'xerotact-shield-x-security'));
        }

        $instance = $this->instance();
        if ($instance === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_ERROR, __('SHIELD-X could not determine this site instance.', 'xerotact-shield-x-security'));
        }

        $response = $this->request('activate_key', $licenseKey, $instance);
        if (empty($response['ok']) && !empty($response['transport_error'])) {
            $response = $this->recoverActivationAfterTransportFailure($licenseKey, $instance);
        }
        if (empty($response['ok'])) {
            $status = $this->statusFromFailure((array) ($response['payload'] ?? []), (string) ($response['message'] ?? ''));
            $message = $this->redactMessage((string) ($response['message'] ?? __('Activation failed.', 'xerotact-shield-x-security')), $licenseKey);
            $settings = [
                'status' => $status,
                'instance' => $instance,
                'license_key_mask' => LicenseSettings::maskLicenseKey($licenseKey),
                'last_checked_at' => LicenseSettings::now(),
                'last_message' => $message,
            ];

            if ($status === LicenseSettings::STATUS_ERROR) {
                $protected = LicenseSettings::protectLicenseKey($licenseKey);
                if ($protected !== '') {
                    $settings['license_key'] = $protected;
                }
            }

            $this->settings->save($settings);

            return ['ok' => false, 'status' => $status, 'message' => $message];
        }

        $payload = isset($response['payload']) && is_array($response['payload']) ? $response['payload'] : [];

        return $this->storeActivationSuccess($payload, (string) ($response['message'] ?? __('SHIELD-X activation is active.', 'xerotact-shield-x-security')), $licenseKey, $instance);
    }

    /**
     * A timed-out activation may have completed remotely after the local
     * transport stopped waiting. Reconcile through the alternate transport
     * before sending another activation request.
     *
     * @return array{ok:bool,message:string,payload:array<string,mixed>,transport_error?:bool}
     */
    private function recoverActivationAfterTransportFailure(string $licenseKey, string $instance): array
    {
        $validation = $this->request('validate_key', $licenseKey, $instance, true);
        if (!empty($validation['ok'])) {
            return $validation;
        }

        $status = $this->statusFromFailure(
            isset($validation['payload']) && is_array($validation['payload']) ? $validation['payload'] : [],
            (string) ($validation['message'] ?? '')
        );
        if ($status !== LicenseSettings::STATUS_INVALID) {
            return $validation;
        }

        return $this->request('activate_key', $licenseKey, $instance, true);
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    public function retryActivation(): array
    {
        $licenseKey = $this->settings->licenseKey();
        if ($licenseKey === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_ERROR, __('Enter the activation code again before retrying.', 'xerotact-shield-x-security'));
        }

        return $this->activate($licenseKey);
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    public function validateStored(bool $preserveRemoteErrors = false): array
    {
        $licenseKey = $this->settings->licenseKey();
        if ($licenseKey === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_ERROR, __('Enter the activation code again to check status.', 'xerotact-shield-x-security'));
        }

        return $this->validate($licenseKey, $preserveRemoteErrors);
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    public function validateIfDue(int $seconds = 86400): array
    {
        $summary = $this->settings->summary();
        if ((string) ($summary['status'] ?? '') !== LicenseSettings::STATUS_ACTIVE || empty($summary['has_stored_key'])) {
            return ['ok' => true, 'status' => (string) ($summary['status'] ?? LicenseSettings::STATUS_INACTIVE), 'message' => 'Skipped.'];
        }

        $lastChecked = strtotime((string) ($summary['last_checked_at'] ?? ''));
        if ($lastChecked !== false && (time() - $lastChecked) < $seconds) {
            return ['ok' => true, 'status' => LicenseSettings::STATUS_ACTIVE, 'message' => 'Fresh.'];
        }

        return $this->validateStored();
    }

    /**
     * Performs one real remote validation for each WordPress login session.
     *
     * A new WordPress session token gets a different cache key, so revoked,
     * refunded, or deactivated licenses are checked again when the admin logs
     * in and opens SHIELD-X.
     *
     * @return array{ok:bool,status:string,message:string}
     */
    public function validateForCurrentSession(bool $force = false): array
    {
        $summary = $this->settings->summary();
        $status = LicenseSettings::sanitizeStatus((string) ($summary['status'] ?? LicenseSettings::STATUS_INACTIVE));
        $isTerminalStatus = in_array($status, [LicenseSettings::STATUS_INACTIVE, LicenseSettings::STATUS_INVALID, LicenseSettings::STATUS_EXPIRED], true);
        if (empty($summary['has_stored_key']) || (!$force && $isTerminalStatus)) {
            return $this->skippedValidationResult($summary);
        }

        $sessionKey = $this->sessionValidationCacheKey();
        if (!$force && $sessionKey !== '' && function_exists('get_transient')) {
            $cached = get_transient($sessionKey);
            if (is_array($cached)) {
                return $this->normalizeValidationResult($cached, $summary);
            }
        }

        $lockKey = $this->validationLockCacheKey();
        if ($lockKey !== '' && function_exists('get_transient') && get_transient($lockKey)) {
            return [
                'ok' => true,
                'status' => LicenseSettings::sanitizeStatus((string) ($summary['status'] ?? LicenseSettings::STATUS_INACTIVE)),
                'message' => 'Already checking.',
            ];
        }

        if ($lockKey !== '' && function_exists('set_transient')) {
            set_transient($lockKey, '1', self::VALIDATION_LOCK_SECONDS);
        }

        try {
            $result = $this->normalizeValidationResult($this->validateStored(true), $summary);
            $this->storeSessionValidationResult($sessionKey, $result);

            return $result;
        } finally {
            if ($lockKey !== '' && function_exists('delete_transient')) {
                delete_transient($lockKey);
            }
        }
    }

    /**
     * Returns the stored license summary, overlaid with the current WordPress
     * login session's live-validation result when one exists. This method never
     * performs a remote request.
     *
     * @return array<string,mixed>
     */
    public function summaryForCurrentSession(): array
    {
        $summary = $this->settings->summary();
        $storedStatus = LicenseSettings::sanitizeStatus((string) ($summary['status'] ?? LicenseSettings::STATUS_INACTIVE));
        if (in_array($storedStatus, [LicenseSettings::STATUS_INACTIVE, LicenseSettings::STATUS_INVALID, LicenseSettings::STATUS_EXPIRED], true)) {
            return $summary;
        }

        $sessionKey = $this->sessionValidationCacheKey();
        if ($sessionKey === '' || !function_exists('get_transient')) {
            return $summary;
        }

        $cached = get_transient($sessionKey);
        if (!is_array($cached)) {
            return $summary;
        }

        $result = $this->normalizeValidationResult($cached, $summary);

        return $this->summaryWithValidationResult($summary, $result);
    }

    /**
     * Forces a fresh remote license validation for actions that install private
     * executable packages. Session cache is intentionally bypassed here, but
     * transient remote errors preserve an already-active stored license so a
     * short vendor outage does not block a signed private package URL.
     *
     * @return array{ok:bool,status:string,message:string}
     */
    public function validateForPrivilegedAction(): array
    {
        $summary = $this->settings->summary();
        $result = $this->normalizeValidationResult($this->validateStored(true), $summary);
        $sessionKey = $this->sessionValidationCacheKey();
        $this->storeSessionValidationResult($sessionKey, $result);

        return $result;
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    public function deactivate(): array
    {
        $licenseKey = $this->settings->licenseKey();
        if ($licenseKey === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_ERROR, __('Enter the activation code again before deactivating.', 'xerotact-shield-x-security'));
        }

        $instance = $this->instance();
        if ($instance === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_ERROR, __('SHIELD-X could not determine this site instance.', 'xerotact-shield-x-security'));
        }

        $response = $this->request('deactivate_key', $licenseKey, $instance);
        if (empty($response['ok'])) {
            $message = $this->redactMessage((string) ($response['message'] ?? __('Deactivation failed.', 'xerotact-shield-x-security')), $licenseKey);
            $this->settings->save([
                'status' => LicenseSettings::STATUS_ERROR,
                'instance' => $instance,
                'last_checked_at' => LicenseSettings::now(),
                'last_message' => $message,
            ]);

            return ['ok' => false, 'status' => LicenseSettings::STATUS_ERROR, 'message' => $message];
        }

        $message = $this->redactMessage((string) ($response['message'] ?? __('SHIELD-X activation was removed from this site.', 'xerotact-shield-x-security')), $licenseKey);
        $this->settings->save([
            'status' => LicenseSettings::STATUS_INACTIVE,
            'instance' => $instance,
            'license_key' => '',
            'license_key_mask' => '',
            'expires' => '',
            'activation_limit' => 0,
            'activation_count' => 0,
            'activation_remaining' => 0,
            'activated_at' => '',
            'last_checked_at' => LicenseSettings::now(),
            'last_message' => $message,
        ]);

        return ['ok' => true, 'status' => LicenseSettings::STATUS_INACTIVE, 'message' => $message];
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    private function validate(string $licenseKey, bool $preserveRemoteErrors = false): array
    {
        $instance = $this->instance();
        if ($instance === '') {
            return $this->storeLocalError(LicenseSettings::STATUS_ERROR, __('SHIELD-X could not determine this site instance.', 'xerotact-shield-x-security'));
        }

        $response = $this->request('validate_key', $licenseKey, $instance);
        if (empty($response['ok'])) {
            $status = $this->statusFromFailure((array) ($response['payload'] ?? []), (string) ($response['message'] ?? ''));
            $this->lastRemoteValidationFailed = $status === LicenseSettings::STATUS_ERROR;
            $message = $this->redactMessage((string) ($response['message'] ?? __('Activation check failed.', 'xerotact-shield-x-security')), $licenseKey);
            if ($status === LicenseSettings::STATUS_INVALID) {
                $activation = $this->request('activate_key', $licenseKey, $instance);
                if (!empty($activation['ok'])) {
                    $payload = isset($activation['payload']) && is_array($activation['payload']) ? $activation['payload'] : [];

                    return $this->storeActivationSuccess($payload, (string) ($activation['message'] ?? __('SHIELD-X activation is active.', 'xerotact-shield-x-security')), $licenseKey, $instance);
                }
            }

            if ($preserveRemoteErrors && $status === LicenseSettings::STATUS_ERROR) {
                return $this->preserveCurrentStatus($message);
            }

            $this->settings->save([
                'status' => $status,
                'instance' => $instance,
                'last_checked_at' => LicenseSettings::now(),
                'last_message' => $message,
            ]);

            return ['ok' => false, 'status' => $status, 'message' => $message];
        }

        $payload = isset($response['payload']) && is_array($response['payload']) ? $response['payload'] : [];
        $status = $this->statusFromPayload($payload);
        $message = $this->redactMessage((string) ($response['message'] ?? __('Activation status checked.', 'xerotact-shield-x-security')), $licenseKey);
        $this->settings->save(array_merge($this->settingsFromPayload($payload), [
            'status' => $status,
            'instance' => $instance,
            'last_checked_at' => LicenseSettings::now(),
            'last_message' => $message,
        ]));

        return ['ok' => $status === LicenseSettings::STATUS_ACTIVE, 'status' => $status, 'message' => $message];
    }

    /**
     * @param array<string,mixed> $payload
     * @return array{ok:bool,status:string,message:string}
     */
    private function storeActivationSuccess(array $payload, string $message, string $licenseKey, string $instance): array
    {
        $checkedAt = LicenseSettings::now();
        $message = $this->redactMessage($message, $licenseKey);
        $status = $this->statusFromActivationResponse($payload, $instance);

        if ($status !== LicenseSettings::STATUS_ACTIVE) {
            $this->settings->save(array_merge($this->settingsFromPayload($payload), [
                'status' => $status,
                'instance' => $instance,
                'license_key_mask' => LicenseSettings::maskLicenseKey($licenseKey),
                'last_checked_at' => $checkedAt,
                'last_message' => $message,
            ]));

            return ['ok' => false, 'status' => $status, 'message' => $message];
        }

        $this->settings->save(array_merge($this->settingsFromPayload($payload), [
            'status' => $status,
            'instance' => $instance,
            'license_key' => LicenseSettings::protectLicenseKey($licenseKey),
            'license_key_mask' => LicenseSettings::maskLicenseKey($licenseKey),
            'activated_at' => $this->activatedAtFromPayload($payload, $checkedAt),
            'last_checked_at' => $checkedAt,
            'last_message' => $message,
        ]));

        $result = ['ok' => true, 'status' => LicenseSettings::STATUS_ACTIVE, 'message' => $message];
        $this->storeSessionValidationResult($this->sessionValidationCacheKey(), $result);

        return $result;
    }

    /**
     * @param array<string,mixed> $summary
     * @return array{ok:bool,status:string,message:string}
     */
    private function skippedValidationResult(array $summary): array
    {
        $status = LicenseSettings::sanitizeStatus((string) ($summary['status'] ?? LicenseSettings::STATUS_INACTIVE));

        return [
            'ok' => $status === LicenseSettings::STATUS_ACTIVE,
            'status' => $status,
            'message' => 'Skipped.',
        ];
    }

    /**
     * @param array<string,mixed> $result
     * @param array<string,mixed> $summary
     * @return array{ok:bool,status:string,message:string}
     */
    private function normalizeValidationResult(array $result, array $summary): array
    {
        $fallback = $this->skippedValidationResult($summary);
        $status = isset($result['status']) && is_scalar($result['status'])
            ? LicenseSettings::sanitizeStatus((string) $result['status'])
            : $fallback['status'];
        $message = isset($result['message']) && is_scalar($result['message'])
            ? $this->redactMessage((string) $result['message'], '')
            : $fallback['message'];

        return [
            'ok' => array_key_exists('ok', $result) ? (bool) $result['ok'] : $status === LicenseSettings::STATUS_ACTIVE,
            'status' => $status,
            'message' => $message,
        ];
    }

    /**
     * @param array<string,mixed> $summary
     * @param array{ok:bool,status:string,message:string} $result
     * @return array<string,mixed>
     */
    private function summaryWithValidationResult(array $summary, array $result): array
    {
        $status = LicenseSettings::sanitizeStatus((string) $result['status']);
        if (empty($result['ok']) && $status === LicenseSettings::STATUS_ACTIVE) {
            $status = LicenseSettings::STATUS_ERROR;
        }

        $summary['status'] = $status;
        $summary['label'] = LicenseSettings::statusLabel($status);
        $summary['tone'] = LicenseSettings::statusTone($status);
        $summary['session_validated'] = true;
        if ($result['message'] !== '') {
            $summary['last_message'] = $result['message'];
        }

        return $summary;
    }

    /**
     * @param array{ok:bool,status:string,message:string} $result
     */
    private function storeSessionValidationResult(string $sessionKey, array $result): void
    {
        if ($sessionKey !== '' && function_exists('set_transient')) {
            set_transient($sessionKey, $result, self::SESSION_CACHE_TTL_SECONDS);
        }
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    private function preserveCurrentStatus(string $message): array
    {
        $summary = $this->settings->summary();
        $status = LicenseSettings::sanitizeStatus((string) ($summary['status'] ?? LicenseSettings::STATUS_INACTIVE));

        return [
            'ok' => $status === LicenseSettings::STATUS_ACTIVE,
            'status' => $status,
            'message' => $this->redactMessage($message, ''),
        ];
    }

    private function sessionValidationCacheKey(): string
    {
        if (function_exists('is_admin') && !is_admin() && !(defined('WP_CLI') && WP_CLI)) {
            return '';
        }

        $capability = defined('SHIELDX_MANAGE_CAPABILITY') ? (string) SHIELDX_MANAGE_CAPABILITY : 'manage_options';
        if (function_exists('current_user_can') && !current_user_can($capability)) {
            return '';
        }

        $userId = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
        $sessionToken = function_exists('wp_get_session_token') ? trim((string) wp_get_session_token()) : '';
        if ($userId <= 0 || $sessionToken === '') {
            return '';
        }

        $settings = $this->settings->all();
        $licenseIdentity = $this->licenseCacheIdentity();
        if ($licenseIdentity === '') {
            return '';
        }

        $instance = (string) ($settings['instance'] ?? '');
        if ($instance === '') {
            $instance = $this->instance();
        }

        return self::SESSION_CACHE_PREFIX . hash('sha256', (string) $userId . '|' . $sessionToken . '|' . $licenseIdentity . '|' . $instance);
    }

    private function validationLockCacheKey(): string
    {
        $identity = $this->validationCacheIdentity();

        return $identity !== '' ? self::VALIDATION_LOCK_PREFIX . $identity : '';
    }

    private function validationCacheIdentity(): string
    {
        $settings = $this->settings->all();
        $licenseIdentity = $this->licenseCacheIdentity();
        if ($licenseIdentity === '') {
            return '';
        }

        $instance = (string) ($settings['instance'] ?? '');
        if ($instance === '') {
            $instance = $this->instance();
        }

        if ($instance === '') {
            return '';
        }

        return hash('sha256', $licenseIdentity . '|' . $instance);
    }

    private function licenseCacheIdentity(): string
    {
        $licenseKey = $this->settings->licenseKey();
        if ($licenseKey === '') {
            return '';
        }

        return hash('sha256', $licenseKey);
    }

    /**
     * @return array{ok:bool,message:string,payload:array<string,mixed>,transport_error?:bool}
     */
    private function request(string $apiAction, string $licenseKey, string $instance, bool $alternateOnly = false): array
    {
        $url = $this->endpointUrl($apiAction);
        $args = [
            'timeout' => self::HTTP_TIMEOUT_SECONDS,
            'redirection' => 0,
            'reject_unsafe_urls' => true,
            'sslverify' => true,
            // WordPress' HTTP API uses this canonical argument when it emits
            // the request header; putting it only in `headers` can be lost by
            // a transport implementation and make a valid activation look
            // like a headerless bot probe to the licensing server's WAF.
            'user-agent' => 'SHIELD-X/' . (defined('SHIELDX_VERSION') ? SHIELDX_VERSION : 'unknown'),
            'headers' => [
                'Accept' => 'application/json',
            ],
            'body' => [
                'key' => $licenseKey,
                'instance' => $instance,
            ],
        ];

        $attempt = $this->sendRequest($url, $args, $apiAction, $alternateOnly);
        $usedAlternate = $alternateOnly;
        if (!$alternateOnly && $apiAction === 'validate_key' && $this->isRetryableTransportAttempt($attempt)) {
            $attempt = $this->sendRequest($url, $args, $apiAction, true);
            $usedAlternate = true;
        }

        $transportMessage = $this->transportAttemptMessage($attempt);
        if ($transportMessage !== '') {
            $retryable = $this->isRetryableTransportMessage($transportMessage);
            if ($usedAlternate && $retryable) {
                $transportMessage = __('The license server connection failed using both available WordPress HTTP transports. Check outbound HTTPS access and try again.', 'xerotact-shield-x-security');
            }

            return [
                'ok' => false,
                'message' => $this->redactMessage($transportMessage, $licenseKey),
                'payload' => [],
                'transport_error' => $retryable,
            ];
        }

        $response = $attempt['response'];
        $code = $this->responseCode($response);
        $body = $this->responseBody($response);
        if ($code < 200 || $code >= 300) {
            return [
                'ok' => false,
                'message' => sprintf(
                    /* translators: %d: HTTP status code returned by the license server. */
                    __('License server returned HTTP %d.', 'xerotact-shield-x-security'),
                    $code
                ),
                'payload' => [],
            ];
        }

        $payload = \ShieldX\Support\Json::decodeArray($body);
        if (!is_array($payload)) {
            return [
                'ok' => false,
                'message' => __('License server response was not valid JSON.', 'xerotact-shield-x-security'),
                'payload' => [],
            ];
        }

        $message = $this->messageFromPayload($payload, $licenseKey);
        if (!$this->payloadSucceeded($payload)) {
            return [
                'ok' => false,
                'message' => $message !== '' ? $message : __('License server rejected the request.', 'xerotact-shield-x-security'),
                'payload' => $payload,
            ];
        }

        return [
            'ok' => true,
            'message' => $message !== '' ? $message : __('License server accepted the request.', 'xerotact-shield-x-security'),
            'payload' => $payload,
        ];
    }

    /**
     * @param array<string,mixed> $args
     * @return array{response:mixed,error:string}
     */
    private function sendRequest(string $url, array $args, string $apiAction, bool $alternate): array
    {
        $callback = $alternate ? $this->httpFallbackPost : $this->httpPost;
        if ($callback !== null) {
            try {
                return [
                    'response' => call_user_func($callback, $url, $args, $apiAction),
                    'error' => '',
                ];
            } catch (\Throwable $error) {
                return ['response' => null, 'error' => $error->getMessage()];
            }
        }

        if ($alternate && $this->httpPost !== null) {
            return [
                'response' => null,
                'error' => __('The alternate WordPress HTTP transport is unavailable.', 'xerotact-shield-x-security'),
            ];
        }
        if (!function_exists('wp_remote_post')) {
            return [
                'response' => null,
                'error' => __('WordPress HTTP API is unavailable.', 'xerotact-shield-x-security'),
            ];
        }
        if ($alternate && !$this->alternateTransportAvailable()) {
            return [
                'response' => null,
                'error' => __('The alternate WordPress HTTP transport is unavailable.', 'xerotact-shield-x-security'),
            ];
        }

        try {
            return [
                'response' => $this->wordPressPost($url, $args, $alternate),
                'error' => '',
            ];
        } catch (\Throwable $error) {
            return ['response' => null, 'error' => $error->getMessage()];
        }
    }

    /**
     * @param array<string,mixed> $args
     * @return mixed
     */
    private function wordPressPost(string $url, array $args, bool $alternate)
    {
        if (!function_exists('add_action') || !function_exists('remove_action')) {
            return wp_remote_post($url, $args);
        }

        $targetHost = strtolower((string) wp_parse_url($url, PHP_URL_HOST));
        $targetQuery = [];
        parse_str((string) wp_parse_url($url, PHP_URL_QUERY), $targetQuery);
        $targetAction = isset($targetQuery['wckm-api']) && is_scalar($targetQuery['wckm-api'])
            ? (string) $targetQuery['wckm-api']
            : '';
        $requestHook = static function (&$requestUrl, &$headers, &$data, &$type, &$options) use ($targetHost, $targetAction, $alternate): void {
            $requestHost = is_scalar($requestUrl)
                ? strtolower((string) wp_parse_url((string) $requestUrl, PHP_URL_HOST))
                : '';
            $requestQuery = [];
            if (is_scalar($requestUrl)) {
                parse_str((string) wp_parse_url((string) $requestUrl, PHP_URL_QUERY), $requestQuery);
            }
            $requestAction = isset($requestQuery['wckm-api']) && is_scalar($requestQuery['wckm-api'])
                ? (string) $requestQuery['wckm-api']
                : '';
            if ($requestHost !== $targetHost || $requestAction !== $targetAction || !is_array($options)) {
                return;
            }

            $options['connect_timeout'] = self::HTTP_TIMEOUT_SECONDS;
            if ($alternate) {
                $options['transport'] = new \WpOrg\Requests\Transport\Fsockopen();
            }
        };

        add_action('requests-requests.before_request', $requestHook, PHP_INT_MAX, 5);
        try {
            return wp_remote_post($url, $args);
        } finally {
            remove_action('requests-requests.before_request', $requestHook, PHP_INT_MAX);
        }
    }

    private function alternateTransportAvailable(): bool
    {
        return function_exists('fsockopen')
            && extension_loaded('openssl')
            && class_exists('\WpOrg\Requests\Transport\Fsockopen');
    }

    /** @param array{response:mixed,error:string} $attempt */
    private function isRetryableTransportAttempt(array $attempt): bool
    {
        return $this->isRetryableTransportMessage($this->transportAttemptMessage($attempt));
    }

    /** @param array{response:mixed,error:string} $attempt */
    private function transportAttemptMessage(array $attempt): string
    {
        if ($attempt['error'] !== '') {
            return $attempt['error'];
        }

        $response = $attempt['response'];
        if (function_exists('is_wp_error') && is_wp_error($response)) {
            return method_exists($response, 'get_error_message')
                ? (string) $response->get_error_message()
                : __('Remote request failed.', 'xerotact-shield-x-security');
        }

        return '';
    }

    private function isRetryableTransportMessage(string $message): bool
    {
        $message = strtolower(trim($message));
        if ($message === '') {
            return false;
        }
        if (preg_match('/curl error (?:6|7|28)\b/i', $message) === 1) {
            return true;
        }

        foreach ([
            'timed out',
            'timeout',
            'could not resolve host',
            'could not resolve proxy',
            'failed to connect',
            'connection refused',
            'network is unreachable',
            'no route to host',
            'php_network_getaddresses',
            'name or service not known',
            'temporary failure in name resolution',
        ] as $fragment) {
            if (strpos($message, $fragment) !== false) {
                return true;
            }
        }

        return false;
    }

    private function endpointUrl(string $apiAction): string
    {
        if (function_exists('add_query_arg')) {
            return add_query_arg(['wckm-api' => $apiAction], LicenseSettings::SERVER_URL);
        }

        return LicenseSettings::SERVER_URL . '?wckm-api=' . rawurlencode($apiAction);
    }

    /**
     * @param mixed $response
     */
    private function responseCode($response): int
    {
        if (function_exists('wp_remote_retrieve_response_code')) {
            return (int) wp_remote_retrieve_response_code($response);
        }

        return is_array($response) ? (int) ($response['response']['code'] ?? 0) : 0;
    }

    /**
     * @param mixed $response
     */
    private function responseBody($response): string
    {
        if (function_exists('wp_remote_retrieve_body')) {
            return (string) wp_remote_retrieve_body($response);
        }

        return is_array($response) ? (string) ($response['body'] ?? '') : '';
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function payloadSucceeded(array $payload): bool
    {
        if (!array_key_exists('success', $payload)) {
            return false;
        }

        $success = $payload['success'];
        if (is_bool($success)) {
            return $success;
        }

        if (is_scalar($success)) {
            return in_array(strtolower((string) $success), ['1', 'true', 'yes', 'ok', 'success'], true);
        }

        return false;
    }

    /**
     * @param array<string,mixed> $payload
     * @return array<string,mixed>
     */
    private function settingsFromPayload(array $payload): array
    {
        return [
            'expires' => isset($payload['expires']) && is_scalar($payload['expires']) ? (string) $payload['expires'] : '',
            'activation_limit' => max(0, (int) ($payload['activation_limit'] ?? 0)),
            'activation_count' => max(0, (int) ($payload['activation_count'] ?? 0)),
            'activation_remaining' => max(0, (int) ($payload['activation_remaining'] ?? 0)),
        ];
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function activatedAtFromPayload(array $payload, string $fallback): string
    {
        foreach (['activated_at', 'activation_date', 'activated'] as $key) {
            if (isset($payload[$key]) && is_scalar($payload[$key]) && trim((string) $payload[$key]) !== '') {
                return (string) $payload[$key];
            }
        }

        return $fallback;
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function statusFromPayload(array $payload): string
    {
        $instanceStatus = isset($payload['instance_status']) && is_scalar($payload['instance_status'])
            ? strtolower((string) $payload['instance_status'])
            : '';
        $statusValue = $payload['status'] ?? $payload['license_status'] ?? '';
        $status = is_scalar($statusValue)
            ? strtolower((string) $statusValue)
            : '';

        if (in_array($instanceStatus, ['inactive', 'deactivated'], true) || in_array($status, ['inactive', 'deactivated'], true)) {
            return LicenseSettings::STATUS_INACTIVE;
        }

        if (in_array($status, ['expired'], true) || $this->payloadExpired($payload)) {
            return LicenseSettings::STATUS_EXPIRED;
        }

        if (in_array($instanceStatus, ['active', 'valid', 'activated'], true) || in_array($status, ['active', 'valid', 'activated'], true)) {
            return LicenseSettings::STATUS_ACTIVE;
        }

        return LicenseSettings::STATUS_INACTIVE;
    }

    /**
     * The current WC Key Manager activate_key response identifies a successful
     * activation through a bound instance, checksum, and expiry instead of a
     * separate status field. Validation responses still require explicit state.
     *
     * @param array<string,mixed> $payload
     */
    private function statusFromActivationResponse(array $payload, string $instance): string
    {
        $status = $this->statusFromPayload($payload);
        if ($status !== LicenseSettings::STATUS_INACTIVE) {
            return $status;
        }

        return $this->isBoundActivationSuccess($payload, $instance)
            ? LicenseSettings::STATUS_ACTIVE
            : LicenseSettings::STATUS_INACTIVE;
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function isBoundActivationSuccess(array $payload, string $instance): bool
    {
        if (
            !array_key_exists('success', $payload)
            || $payload['success'] !== true
            || $instance === ''
        ) {
            return false;
        }

        foreach (['status', 'license_status', 'instance_status'] as $stateKey) {
            if (array_key_exists($stateKey, $payload)) {
                return false;
            }
        }

        $responseInstance = isset($payload['instance']) && is_scalar($payload['instance'])
            ? (string) $payload['instance']
            : '';
        if (!$this->activationInstanceMatches($instance, $responseInstance)) {
            return false;
        }

        $checksum = isset($payload['checksum']) && is_scalar($payload['checksum'])
            ? trim((string) $payload['checksum'])
            : '';
        if (preg_match('/\A[a-f0-9]{32}\z/i', $checksum) !== 1) {
            return false;
        }

        if (!isset($payload['expires']) || !is_scalar($payload['expires'])) {
            return false;
        }

        $expires = trim((string) $payload['expires']);
        if (strtolower($expires) === 'never') {
            return true;
        }

        $expiresAt = strtotime($expires);

        return $expiresAt !== false && $expiresAt >= time();
    }

    private function activationInstanceMatches(string $requestedInstance, string $responseInstance): bool
    {
        $requestedInstance = trim($requestedInstance);
        $responseInstance = trim($responseInstance);
        if ($requestedInstance === '' || $responseInstance === '') {
            return false;
        }

        if (hash_equals($requestedInstance, $responseInstance)) {
            return true;
        }

        $parts = wp_parse_url($requestedInstance);
        if (!is_array($parts) || empty($parts['host'])) {
            return false;
        }

        $path = trim((string) ($parts['path'] ?? ''), '/');
        if ($path !== '') {
            return false;
        }

        $host = strtolower(trim((string) $parts['host']));

        return $host !== '' && hash_equals($host, strtolower($responseInstance));
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function statusFromFailure(array $payload, string $message): string
    {
        $code = '';
        foreach (['error_code', 'code', 'error', 'status'] as $key) {
            if (isset($payload[$key]) && is_scalar($payload[$key])) {
                $code = strtolower((string) $payload[$key]);
                break;
            }
        }

        $haystack = $code . ' ' . strtolower($message);
        if (strpos($haystack, 'expired') !== false) {
            return LicenseSettings::STATUS_EXPIRED;
        }

        if (strpos($haystack, 'invalid') !== false || strpos($haystack, 'not found') !== false) {
            return LicenseSettings::STATUS_INVALID;
        }

        if (strpos($haystack, 'no_activations_left') !== false || strpos($haystack, 'no activations') !== false || strpos($haystack, 'inactive') !== false || strpos($haystack, 'deactivated') !== false) {
            return LicenseSettings::STATUS_INACTIVE;
        }

        return LicenseSettings::STATUS_ERROR;
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function payloadExpired(array $payload): bool
    {
        if (!isset($payload['expires']) || !is_scalar($payload['expires'])) {
            return false;
        }

        $expires = trim((string) $payload['expires']);
        if ($expires === '' || strtolower($expires) === 'never') {
            return false;
        }

        $timestamp = strtotime($expires);

        return $timestamp !== false && $timestamp < time();
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function messageFromPayload(array $payload, string $licenseKey): string
    {
        foreach (['message', 'error', 'error_message'] as $key) {
            if (isset($payload[$key]) && is_scalar($payload[$key])) {
                return $this->redactMessage((string) $payload[$key], $licenseKey);
            }
        }

        return '';
    }

    private function redactMessage(string $message, string $licenseKey): string
    {
        $message = function_exists('wp_strip_all_tags') ? wp_strip_all_tags($message) : (preg_replace('/<[^>]*>/', '', $message) ?: '');
        $message = preg_replace('/[\x00-\x1F\x7F]+/', ' ', $message) ?: '';
        $message = trim($message);
        $licenseKey = LicenseSettings::sanitizeLicenseKey($licenseKey);
        if ($licenseKey !== '') {
            $message = str_replace($licenseKey, LicenseSettings::maskLicenseKey($licenseKey), $message);
            $message = str_replace(rawurlencode($licenseKey), LicenseSettings::maskLicenseKey($licenseKey), $message);
        }
        $message = preg_replace('/([?&\s]key=)[^\s&]+/i', '$1[redacted]', $message) ?: $message;

        return substr($message, 0, 220);
    }

    /**
     * @return array{ok:bool,status:string,message:string}
     */
    private function storeLocalError(string $status, string $message): array
    {
        $message = $this->redactMessage($message, '');
        $this->settings->save([
            'status' => $status,
            'instance' => $this->instance(),
            'last_checked_at' => LicenseSettings::now(),
            'last_message' => $message,
        ]);

        return ['ok' => false, 'status' => $status, 'message' => $message];
    }

    private function instance(): string
    {
        if ($this->instanceResolver) {
            $instance = call_user_func($this->instanceResolver);

            return is_scalar($instance) ? trim((string) $instance) : '';
        }

        return LicenseSettings::currentInstance();
    }
}
