<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Integrations\Caching\Adapters;

use Ekesto\CacheInvalidator\Integrations\Caching\AdapterSignalGuard;
use Ekesto\CacheInvalidator\Integrations\Caching\CacheAdapterInterface;
use Ekesto\CacheInvalidator\Integrations\Caching\CacheAdapterUrlNormalizer;
use Ekesto\CacheInvalidator\Integrations\Caching\EmitsFullInvalidationFromCacheClear;
use Ekesto\CacheInvalidator\Integrations\Caching\EmitsSingleTargetCacheAdapterInvalidation;
use Ekesto\CacheInvalidator\Integrations\Caching\UrlPurgeCapableAdapterInterface;

/**
 * WP Rocket adapter.
 *
 * Uses WP Rocket public purge functions for post, URL, and full-domain purge.
 */
final class WpRocketAdapter implements CacheAdapterInterface, UrlPurgeCapableAdapterInterface
{
    use EmitsFullInvalidationFromCacheClear;
    use EmitsSingleTargetCacheAdapterInvalidation;

    /**
     * Returns a human-readable adapter label.
     *
     * @return string
     */
    public function getLabel(): string
    {
        return 'WP Rocket';
    }

    /**
     * Returns a unique adapter key.
     *
     * @return string
     */
    public function getKey(): string
    {
        return 'wp-rocket';
    }

    /**
     * Returns whether WP Rocket purge APIs are available.
     *
     * @return bool
     */
    public function isAvailable(): bool
    {
        return function_exists('rocket_clean_post')
            && function_exists('rocket_clean_domain');
    }

    /**
     * Returns whether WP Rocket supports runtime cache-status detection.
     *
     * @return bool
     */
    public function supportsCacheStatusDetection(): bool
    {
        return false;
    }

    /**
     * Returns whether WP Rocket cache should be treated as enabled.
     *
     * @return bool
     */
    public function isCacheEnabled(): bool
    {
        return true;
    }

    /**
     * Registers adapter-specific hooks.
     *
     * @return void
     */
    public function registerHooks(): void
    {
        add_action('rocket_after_clean_domain', function (): void {
            $this->emitFullInvalidationFromCacheClear('wp_rocket');
        }, 10, 0);

        add_action('rocket_purge_cache', [$this, 'handleManualSingleTargetPurge'], 10, 2);
    }

    /**
     * Handle WP Rocket manual single-target purge events.
     *
     * @param mixed $type Signal type.
     * @param mixed $payload Signal payload.
     * @return void
     */
    public function handleManualSingleTargetPurge($type, $payload): void
    {
        $hook = (string) current_filter();

        $classification = $this->classifyManualSignal($type, $payload);
        if ($classification === null) {
            $this->logIgnoredSingleTarget($hook, 'ambiguous', [
                'request_type' => 'manual_endpoint',
                'classification_path' => 'none',
                'signal_type' => sanitize_key((string) $type),
            ]);
            return;
        }

        if (!$this->isManualSingleTargetRequest($classification)) {
            $this->logIgnoredSingleTarget($hook, 'not_manual', [
                'request_type' => 'manual_endpoint',
                'classification_path' => (string) $classification['type'],
            ]);
            return;
        }

        $validationFailureReason = '';
        $validated = $this->validateManualSignal($classification, $validationFailureReason);
        if ($validated === null) {
            $this->logIgnoredSingleTarget($hook, $validationFailureReason !== '' ? $validationFailureReason : 'invalid_payload', [
                'request_type' => 'manual_endpoint',
                'classification_path' => (string) $classification['type'],
                'post_id' => isset($classification['post_id']) ? (int) $classification['post_id'] : 0,
            ]);
            return;
        }

        if (!isset($validated['url']) || !is_string($validated['url']) || $validated['url'] === '') {
            $this->logIgnoredSingleTarget($hook, 'invalid_payload', [
                'request_type' => 'manual_endpoint',
                'classification_path' => (string) $validated['mode'],
                'post_id' => $validated['post_id'],
            ]);
            return;
        }

        $this->emitSingleTargetInvalidation(
            $hook,
            [$validated['url']],
            $validated,
            [
                'request_type' => 'manual_endpoint',
                'classification_path' => $validated['mode'],
            ],
            [
                'trigger_class' => 'cache_adapter',
                'trigger_event' => 'wp_rocket_manual_single_target_purge',
                'trigger_post_id' => $validated['post_id'],
                'trigger_post_type' => $validated['post_type'],
                'reason' => 'supported_cache_plugin_single_target_purge',
                'source' => 'wp_rocket',
                'trusted_source' => 'cache_adapter_single_target',
                'resolution' => 'TARGETED',
                'intent' => 'TARGETED',
            ]
        );
    }

    /**
     * Classify a WP Rocket manual purge signal.
     *
     * @param mixed $type Signal type.
     * @param mixed $payload Signal payload.
     * @return array{type:string,urls:array<int,mixed>,post_id:int|null}|null
     */
    private function classifyManualSignal($type, $payload): ?array
    {
        $signalType = sanitize_key((string) $type);
        if ($signalType === 'url' && is_scalar($payload)) {
            return [
                'type' => 'url',
                'urls' => [(string) $payload],
                'post_id' => null,
            ];
        }

        if ($signalType === 'post' && is_scalar($payload)) {
            $postId = absint($payload);
            if ($postId <= 0) {
                return null;
            }

            return [
                'type' => 'post',
                'urls' => [],
                'post_id' => $postId,
            ];
        }

        return null;
    }

    /**
     * Determine whether the classified signal is a manual request.
     *
     * @param array{type:string,urls:array<int,mixed>,post_id:int|null} $classification Classified signal.
     * @return bool
     */
    private function isManualSingleTargetRequest(array $classification): bool
    {
        return in_array($classification['type'], ['url', 'post'], true);
    }

    /**
     * Validate one classified signal and resolve one normalized target URL.
     *
     * @param array{type:string,urls:array<int,mixed>,post_id:int|null} $classification Classified signal.
     * @return array{
     *   urls:array<int,string>,
     *   url:string,
     *   post_id:int,
     *   post_type:string,
     *   mode:string
     * }|null
     */
    private function validateManualSignal(array $classification, string &$failureReason = ''): ?array
    {
        if ($classification['type'] === 'url') {
            if (count($classification['urls']) !== 1) {
                return null;
            }

            $url = CacheAdapterUrlNormalizer::normalize($classification['urls'][0]);
            if ($url === '') {
                $failureReason = 'invalid_or_cross_origin_url';
                return null;
            }

            return [
                'urls' => [$url],
                'url' => $url,
                'post_id' => 0,
                'post_type' => '',
                'mode' => 'url',
            ];
        }

        if ($classification['type'] !== 'post' || !is_int($classification['post_id']) || $classification['post_id'] <= 0) {
            return null;
        }

        $postId = $classification['post_id'];
        $url = CacheAdapterUrlNormalizer::normalize(get_permalink($postId));
        if ($url === '') {
            $failureReason = 'invalid_or_cross_origin_url';
            return null;
        }

        $postType = get_post_type($postId);
        $postType = is_string($postType) ? sanitize_key($postType) : '';

        return [
            'urls' => [$url],
            'url' => $url,
            'post_id' => $postId,
            'post_type' => $postType,
            'mode' => 'post',
        ];
    }

    /**
     * Returns the adapter source key used in single-target signal logs.
     *
     * @return string
     */
    protected function getSingleTargetSignalSource(): string
    {
        return 'wp_rocket';
    }

    /**
     * Purges cache for a specific post ID.
     *
     * @param int $postId Post/page ID to purge.
     * @return bool True when purge call was executed.
     */
    public function purgePostId(int $postId): bool
    {
        $postId = absint($postId);
        if ($postId <= 0) {
            return false;
        }

        if (!$this->isAvailable()) {
            return false;
        }

        AdapterSignalGuard::runWithSuppressedSignals(static function () use ($postId): void {
            rocket_clean_post($postId);
        });

        return true;
    }

    /**
     * Purges cache for a specific absolute URL.
     *
     * @param string $url Absolute URL.
     * @return bool True when purge call was executed.
     */
    public function purgeUrl(string $url): bool
    {
        $url = trim($url);
        if ($url === '') {
            return false;
        }

        if (!$this->isAvailable()) {
            return false;
        }

        if (!function_exists('rocket_clean_files')) {
            return false;
        }

        AdapterSignalGuard::runWithSuppressedSignals(static function () use ($url): void {
            rocket_clean_files([$url]);
        });

        return true;
    }

    /**
     * Purges all cache entries for the active site.
     *
     * @return bool True when purge call was executed.
     */
    public function purgeAll(): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        AdapterSignalGuard::runWithSuppressedSignals(static function (): void {
            rocket_clean_domain();
        });

        return true;
    }
}
