<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Integrations\Caching\Adapters;

use Ekesto\CacheAutopilot\AccessControl;
use Ekesto\CacheInvalidator\Core\EmissionHelper;
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\UrlPurgeCapableAdapterInterface;
use Ekesto\CacheInvalidator\Support\Debug;

/**
 * Breeze adapter.
 *
 * Uses Breeze public purge APIs for URL and full cache purge.
 */
final class BreezeAdapter implements CacheAdapterInterface, UrlPurgeCapableAdapterInterface
{
    use EmitsFullInvalidationFromCacheClear;

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

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

    /**
     * Returns whether Breeze purge APIs are available.
     *
     * @return bool
     */
    public function isAvailable(): bool
    {
        return defined('BREEZE_VERSION');
    }

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

    /**
     * Returns whether Breeze 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('admin_init', [$this, 'handleManualPostIntent'], 5, 0);

        add_action('breeze_clear_all_cache', function (): void {
            $this->emitFullInvalidationFromCacheClear('breeze');
        }, 10, 0);

        // Admin bar "Purge All Cache" uses a direct GET flow with nonce
        // and bypasses breeze_clear_all_cache in current Breeze versions.
        add_action('admin_init', function (): void {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Third-party admin action detection; nonce is verified below before emitting.
            if (!isset($_GET['breeze_purge'], $_GET['_wpnonce'])) {
                return;
            }

            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Third-party admin action detection; nonce is verified below before emitting.
            if (sanitize_key((string) wp_unslash($_GET['breeze_purge'])) !== '1') {
                return;
            }

            if (!AccessControl::userCanManage()) {
                return;
            }

            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Third-party admin action detection; nonce is verified before emitting.
            $nonce = sanitize_text_field((string) wp_unslash($_GET['_wpnonce']));
            if ($nonce === '' || !wp_verify_nonce($nonce, 'breeze_purge_cache')) {
                return;
            }

            $this->emitFullInvalidationFromCacheClear('breeze');
        }, 10, 0);
    }

    /**
     * Handle Breeze manual post-level clear-cache intent from admin UI actions.
     *
     * @return void
     */
    public function handleManualPostIntent(): void
    {
        $classification = $this->classifyManualPostIntent();
        if ($classification === null) {
            return;
        }

        if ($classification['post_id'] <= 0) {
            $this->logIgnoredSingleTarget('admin_init', 'invalid_post_id');
            return;
        }

        $validated = $this->validateManualPostIntent($classification);
        if ($validated === null) {
            return;
        }

        if (!isset($validated['url']) || !is_string($validated['url']) || $validated['url'] === '') {
            $this->logIgnoredSingleTarget('admin_init', 'invalid_url', [
                'post_id' => $validated['post_id'],
            ]);
            return;
        }

        // If adapter introduces batch purge APIs in future, DO NOT reuse this path.
        // Trusted logic is strictly single-target only.
        $targetUrls = [$validated['url']];
        if (count($targetUrls) !== 1) {
            $this->logIgnoredSingleTarget('admin_init', 'invalid_url_count', [
                'post_id' => $validated['post_id'],
                'url_count' => count($targetUrls),
            ]);
            return;
        }

        // Guard dedupe identity depends on (adapter + hook + signal_type + url hash).
        // Do not alter $targetUrls shape or ordering before this call.
        if (!AdapterSignalGuard::shouldProcessSignal('breeze', 'admin_intent', 'targeted', $targetUrls)) {
            $this->logIgnoredSingleTarget('admin_init', 'guard_rejected', [
                'post_id' => $validated['post_id'],
                'url' => $validated['url'],
            ]);
            return;
        }

        Debug::log('cache_plugin_single_target_detected', [
            'source' => 'breeze',
            'hook' => 'admin_init',
            'accepted' => true,
            'mode' => 'post',
            'url' => $validated['url'],
            'post_id' => $validated['post_id'],
            'trusted' => true,
            'intent_only' => true,
        ]);

        EmissionHelper::emit($targetUrls, [
            'trigger_class' => 'cache_adapter',
            'trigger_event' => 'breeze_manual_post_purge',
            'trigger_post_id' => $validated['post_id'],
            'trigger_post_type' => $validated['post_type'],
            'reason' => 'cache_plugin_manual_intent',
            'source' => 'breeze',
            'trusted_source' => 'cache_adapter_single_target',
            'resolution' => 'TARGETED',
            'intent' => 'TARGETED',
        ]);
    }

    /**
     * Classify Breeze admin intent requests.
     *
     * @return array{post_id:int}|null
     */
    private function classifyManualPostIntent(): ?array
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only third-party action classification; nonce is verified before emission.
        if (!is_admin() || !isset($_GET['action'])) {
            return null;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only third-party action classification; nonce is verified before emission.
        $action = sanitize_key((string) wp_unslash($_GET['action']));
        if ($action !== 'clear-breeze-cache') {
            return null;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only third-party action classification; nonce is verified before emission.
        $postId = isset($_GET['post']) ? absint(wp_unslash($_GET['post'])) : 0;

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

    /**
     * Validate Breeze admin intent payload and resolve one normalized target URL.
     *
     * @param array{post_id:int} $classification Classified signal.
     * @return array{url:string,post_id:int,post_type:string}|null
     */
    private function validateManualPostIntent(array $classification): ?array
    {
        $postId = $classification['post_id'];
        if ($postId <= 0) {
            return null;
        }

        if (!current_user_can('edit_post', $postId)) {
            $this->logIgnoredSingleTarget('admin_init', 'insufficient_permissions', [
                'post_id' => $postId,
            ]);
            return null;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Third-party action nonce read; verified immediately below before emission.
        $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field((string) wp_unslash($_GET['_wpnonce'])) : '';
        if ($nonce === '' || !wp_verify_nonce($nonce, 'clear-cache-post_' . $postId)) {
            $this->logIgnoredSingleTarget('admin_init', 'invalid_nonce', [
                'post_id' => $postId,
            ]);
            return null;
        }

        $url = CacheAdapterUrlNormalizer::normalize(get_permalink($postId));
        if ($url === '') {
            $this->logIgnoredSingleTarget('admin_init', 'invalid_or_cross_origin_url', [
                'post_id' => $postId,
            ]);
            return null;
        }

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

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

    /**
     * Log an ignored Breeze single-target signal.
     *
     * @param string               $hook Hook name.
     * @param string               $reason Ignore reason key.
     * @param array<string,mixed> $context Additional context.
     * @return void
     */
    private function logIgnoredSingleTarget(string $hook, string $reason, array $context = []): void
    {
        if (Debug::verboseEnabled()) {
            Debug::log('cache_plugin_single_target_ignored', array_merge([
                'source' => 'breeze',
                'hook' => $hook,
                'accepted' => false,
                'reason' => $reason,
                'url' => '',
                'post_id' => 0,
            ], $context));
        }
    }

    /**
     * Purges cache for a specific post ID by resolving and purging its permalink URL.
     *
     * @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;
        }

        $url = get_permalink($postId);
        if (!is_string($url) || $url === '') {
            return false;
        }

        $url = trim($url);
        if ($url === '') {
            return false;
        }

        return $this->purgeUrl($url);
    }

    /**
     * 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;
        }

        $hasLocalUrlPurge = function_exists('breeze_varnish_purge_cache');
        $hasCloudflareUrlPurge = class_exists('Breeze_CloudFlare_Helper')
            && method_exists('Breeze_CloudFlare_Helper', 'purge_cloudflare_cache_urls');

        if (!$hasLocalUrlPurge && !$hasCloudflareUrlPurge) {
            return false;
        }

        AdapterSignalGuard::runWithSuppressedSignals(static function () use (
            $url,
            $hasLocalUrlPurge,
            $hasCloudflareUrlPurge
        ): void {
            if ($hasLocalUrlPurge) {
                \breeze_varnish_purge_cache($url);
            }

            if ($hasCloudflareUrlPurge) {
                \Breeze_CloudFlare_Helper::purge_cloudflare_cache_urls([$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 {
            // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- External Breeze cache plugin hook required for adapter interoperability.
            do_action('breeze_clear_all_cache');
        });

        return true;
    }
}
