<?php

namespace FleekDash\Controllers;

use FleekDash\Controllers\License\LicenseHelpers;
use FleekDash\Controllers\License\LicenseAPIClient;
use FleekDash\Controllers\License\RemoteActivationService;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

// Prevent direct access to this file
defined("ABSPATH") || exit();

/** Error message when Supabase config is missing. */
const MSG_SUPABASE_CONFIG_MISSING = 'Supabase configuration is missing';
/** Bearer token prefix for Authorization header. */
const AUTH_BEARER_PREFIX = 'Bearer ';
/** Content-Type for JSON requests. */
const CONTENT_TYPE_JSON = 'application/json';
/** Supabase Prefer header for representation. */
const PREFER_REPRESENTATION = 'return=representation';
/** Default message for inactive license. */
const MSG_LICENSE_INACTIVE = 'License is inactive';

// Load debug helpers
if (!function_exists('fleekdash_log_debug')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}

class LicenseController
{
    private static ?self $instance = null;

    private string $supabaseUrl;

    private string $supabaseKey;

    private string $apiVersion = 'v1';

    private LicenseAPIClient $apiClient;

    private RemoteActivationService $remoteActivation;

    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct()
    {
        $this->supabaseUrl = defined('FLEEKDASH_SUPABASE_URL') ? FLEEKDASH_SUPABASE_URL : '';
        $this->supabaseKey = defined('FLEEKDASH_SUPABASE_KEY') ? FLEEKDASH_SUPABASE_KEY : '';
        $this->apiVersion = 'v1';

        // Initialize services
        $this->apiClient = new LicenseAPIClient($this->supabaseUrl, $this->supabaseKey, $this->apiVersion);
        $this->remoteActivation = new RemoteActivationService($this->supabaseUrl, $this->supabaseKey, $this->apiVersion);

        // REST: {@see \FleekDash\Controllers\License\LicenseRestController} (routes in FeaturesRoutes).

        // Hook into WordPress actions
        \add_action('wp_ajax_check_remote_activation', [$this, 'ajaxCheckRemoteActivation']);
        \add_action('wp_ajax_nopriv_check_remote_activation', [$this, 'ajaxCheckRemoteActivation']);

        // Schedule daily check for remote activations
        if (!wp_next_scheduled('fleekdash_check_remote_activation')) {
            \wp_schedule_event(time(), 'daily', 'fleekdash_check_remote_activation');
        }
        \add_action('fleekdash_check_remote_activation', [$this, 'maybeCheckRemoteActivation']);

        // Check for remote activation on admin pages
        \add_action('admin_init', [$this, 'maybeCheckRemoteActivation']);

        // V2.2: Auto-clear features cache when license changes
        \add_action('update_option_fleekdash_v2_license_status', [$this, 'clearFeaturesCacheOnLicenseChange'], 10, 2);
        \add_action('update_option_fleekdash_v2_license_data', [$this, 'clearFeaturesCacheOnLicenseChange'], 10, 2);
        \add_action('update_option_fleekdash_v2_license_key', [$this, 'clearFeaturesCacheOnLicenseChange'], 10, 2);
    }

    public function activateLicense(WP_REST_Request $request) // NOSONAR php:S1142 - License activation flow requires multiple exit points
    {
        if (empty(FLEEKDASH_SUPABASE_URL) || empty(FLEEKDASH_SUPABASE_KEY)) {
            return new WP_Error(
                'supabase_config_missing',
                MSG_SUPABASE_CONFIG_MISSING,
                ['status' => 500],
            );
        }

        $license_key = $request->get_param('license_key');
        $instance_name = $request->get_param('instance_name');

        fleekdash_log_debug('FleekDash V2: Attempting to activate license: ' . $license_key . ' for instance: ' . $instance_name);

        if (empty($license_key)) {
            return new WP_Error(
                'missing_license_key',
                'License key is required',
                ['status' => 400],
            );
        }

        // First verify the license exists and is valid using the existing verify_license function
        $license = $this->fetchLicense($license_key);
        if (is_wp_error($license)) {
            fleekdash_log_error('FleekDash V2: License validation failed during activation: ' . $license->get_error_message());
            return $license;
        }

        // Use the new RPC function to activate the license instance
        $response = \wp_remote_post(FLEEKDASH_SUPABASE_URL . '/rest/v1/rpc/activate_license_instance', [
            'headers' => [
                'apikey' => FLEEKDASH_SUPABASE_KEY,
                'Authorization' => AUTH_BEARER_PREFIX . FLEEKDASH_SUPABASE_KEY,
                'Content-Type' => CONTENT_TYPE_JSON,
                'Accept' => CONTENT_TYPE_JSON,
                'Prefer' => PREFER_REPRESENTATION
            ],
            'body' => json_encode([
                'p_license_key' => $license_key,
                'p_instance_name' => $instance_name ?: \get_bloginfo('name'),
                'p_site_url' => \get_site_url()
            ]),
            'timeout' => 30,
            'sslverify' => false // Disable SSL verification for self-signed certificates
        ]);

        if (is_wp_error($response)) {
            fleekdash_log_error('FleekDash V2: Failed to activate license via RPC: ' . $response->get_error_message());

            // Fall back to local storage if RPC fails
            $instance_id = wp_generate_uuid4();
            \update_option('fleekdash_v2_license_key', $license_key);
            \update_option('fleekdash_v2_instance_id', $instance_id);
            \update_option('fleekdash_v2_license_status', 'active');
            $license_row = LicenseHelpers::getCanonicalLicenseRecord(\is_array($license) ? $license : null) ?? $license;
            \update_option('fleekdash_v2_license_data', $license_row);
            \update_option('fleekdash_v2_activation_date', \current_time('mysql'));
            \update_option('fleekdash_v2_license_last_verified', time());
            \delete_option('fleekdash_v2_user_deactivated_at');

            $this->invalidatePermissionCachesForLicenseEvent('activated');

            return new WP_REST_Response([
                'activated' => true,
                'instance' => [
                    'id' => $instance_id,
                    'license_key' => $license_key,
                    'label' => $instance_name ?: \get_bloginfo('name'),
                    'site' => \get_site_url(),
                    'status' => 'active'
                ],
                'license' => $license,
                'storage_method' => 'local_fallback'
            ], 200);
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $body = json_decode(wp_remote_retrieve_body($response), true);

        fleekdash_log_debug('FleekDash V2: License activation RPC response: ' . print_r([
            'status' => $status_code,
            'body' => $body
        ], true));

        $rpcSucceeded = $status_code === 200
            && is_array($body)
            && isset($body['success'])
            && $body['success'] === true;

        if (!$rpcSucceeded) {
            fleekdash_log_error('FleekDash V2: RPC activation did not succeed; returning server message to client');

            $failure = LicenseHelpers::buildActivationFailurePayload(
                $status_code,
                is_array($body) ? $body : null
            );

            return new WP_REST_Response($failure, 200);
        }

        // Success! Store both remotely and locally
        \update_option('fleekdash_v2_instance_id', $body['instance_id']);
        \update_option('fleekdash_v2_license_key', $license_key);
        \update_option('fleekdash_v2_license_status', 'active');
        $license_row = LicenseHelpers::getCanonicalLicenseRecord(\is_array($license) ? $license : null) ?? $license;
        \update_option('fleekdash_v2_license_data', $license_row);
        \update_option('fleekdash_v2_license_last_verified', time());
        \delete_option('fleekdash_v2_user_deactivated_at');

        // Prefer RPC-enriched license row when present; keep same canonical shape as FeatureController / verify paths.
        if (isset($body['license_data']) && \is_array($body['license_data'])) {
            $canonicalRpc = LicenseHelpers::getCanonicalLicenseRecord($body['license_data']);
            if ($canonicalRpc !== null) {
                \update_option('fleekdash_v2_license_data', $canonicalRpc);
            }
        }

        $this->invalidatePermissionCachesForLicenseEvent('activated');

        return new WP_REST_Response([
            'activated' => true,
            'instance' => [
                'id' => $body['instance_id'],
                'license_key' => $license_key,
                'label' => $body['label'],
                'site' => $body['site'],
                'status' => $body['status']
            ],
            'license' => $license,
            'storage_method' => 'remote_rpc'
        ], 200);
    }

    public function deactivateLicense(WP_REST_Request $request) // NOSONAR php:S1142 - license flow
    {
        if (empty(FLEEKDASH_SUPABASE_URL) || empty(FLEEKDASH_SUPABASE_KEY)) {
            return new WP_Error(
                'supabase_config_missing',
                MSG_SUPABASE_CONFIG_MISSING,
                ['status' => 500],
            );
        }

        $instance_id = $request->get_param('instance_id');

        if (empty($instance_id)) {
            return new WP_Error(
                'missing_instance_id',
                'Instance ID is required',
                ['status' => 400],
            );
        }

        fleekdash_log_debug('FleekDash V2: Attempting to deactivate license instance: ' . $instance_id);

        // Use the new RPC function to deactivate the license instance
        $response = \wp_remote_post(FLEEKDASH_SUPABASE_URL . '/rest/v1/rpc/deactivate_license_instance', [
            'headers' => [
                'apikey' => FLEEKDASH_SUPABASE_KEY,
                'Authorization' => AUTH_BEARER_PREFIX . FLEEKDASH_SUPABASE_KEY,
                'Content-Type' => CONTENT_TYPE_JSON,
                'Accept' => CONTENT_TYPE_JSON,
                'Prefer' => PREFER_REPRESENTATION
            ],
            'body' => json_encode([
                'p_instance_id' => $instance_id
            ]),
            'timeout' => 30,
            'sslverify' => false // Disable SSL verification for self-signed certificates
        ]);

        if (is_wp_error($response)) {
            fleekdash_log_error('FleekDash V2: Failed to deactivate license via RPC: ' . $response->get_error_message());

            return new WP_REST_Response([
                'deactivated' => false,
                'message' => 'Failed to deactivate license on server. Please try again.',
                'error' => true,
                'storage_method' => 'remote_rpc_error'
            ], 200);
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $body = json_decode(wp_remote_retrieve_body($response), true);

        fleekdash_log_debug('FleekDash V2: License deactivation RPC response: ' . print_r([
            'status' => $status_code,
            'body' => $body
        ], true));

        if ($status_code !== 200 || !isset($body['success']) || !$body['success']) {
            fleekdash_log_error('FleekDash V2: RPC deactivation failed; local license state was not changed');

            return new WP_REST_Response([
                'deactivated' => false,
                'message' => $body['message'] ?? 'Failed to deactivate license on server.',
                'error' => true,
                'storage_method' => 'remote_rpc_error'
            ], 200);
        }

        // Success! Update local status as well
        \update_option('fleekdash_v2_license_status', 'inactive');
        \delete_option('fleekdash_v2_instance_id');
        \delete_option('fleekdash_v2_license_key');
        \delete_option('fleekdash_v2_license_data');
        \delete_option('fleekdash_v2_tier_data');
        \delete_option('fleekdash_v2_license_last_verified');
        // Persist user intent so verifyLicense / cron / admin_init do not silently re-activate
        // from Supabase remote-activation (`find_site_activation` would otherwise re-save the just-deactivated instance).
        \update_option('fleekdash_v2_user_deactivated_at', time());

        $this->invalidatePermissionCachesForLicenseEvent('deactivated');

        return new WP_REST_Response([
            'deactivated' => true,
            'instance_id' => $body['instance_id'],
            'storage_method' => 'remote_rpc'
        ], 200);
    }

    /**
     * Verify if the license is valid
     *
     * @param string|null $license_key License key to verify, or null to use stored key
     * @param bool $force Force verification even if TTL cache is still valid
     * @return array License status information
     */
    public function verifyLicense($license_key = null, $force = false) // NOSONAR php:S1142 php:S3776 - License verification has multiple paths
    {
        // If no license key provided, use the stored one
        if (is_null($license_key)) {
            $license_key = \get_option('fleekdash_v2_license_key');
        }

        // If we don't have a license key, check for remote activation
        if (empty($license_key)) {
            // Honor user-initiated deactivation: skip remote re-activation while the guard is set.
            if ((int) \get_option('fleekdash_v2_user_deactivated_at', 0) > 0) {
                fleekdash_log_debug('FleekDash V2: User-deactivated guard set; skipping remote activation check.');
                return [
                    'status' => 'inactive',
                    'message' => 'License deactivated by user',
                    'reason' => 'user_deactivated',
                ];
            }

            fleekdash_log_debug('FleekDash V2: No license key found, checking for remote activation');
            $remote_activation = $this->checkRemoteActivation();

            if (!is_wp_error($remote_activation)) {
                // Remote activation found, save it locally
                fleekdash_log_info('FleekDash V2: Remote activation found, saving locally: ' . print_r($remote_activation, true));

                \update_option('fleekdash_v2_license_key', $remote_activation['license_key']);
                \update_option('fleekdash_v2_license_status', $remote_activation['status']);
                \update_option('fleekdash_v2_instance_id', $remote_activation['instance_id']);
                \update_option('fleekdash_v2_license_last_verified', time());

                return [
                    'status' => 'active',
                    'message' => 'License activated remotely',
                    'remote_activation' => true,
                    'instance_id' => $remote_activation['instance_id'],
                    'license_key' => $remote_activation['license_key']
                ];
            } else {
                fleekdash_log_debug('FleekDash V2: No remote activation found: ' . $remote_activation->get_error_message());

                // No license key and no remote activation
                return [
                    'status' => 'inactive',
                    'message' => 'No license key found'
                ];
            }
        }

        // Check if we need to verify with Supabase or if we can use the cached status
        $last_verified = \get_option('fleekdash_v2_license_last_verified', 0);
        $ttl = 6 * 60 * 60; // 6 hours TTL

        if (!$force && (time() - $last_verified) < $ttl) {
            // Use cached status
            $status = \get_option('fleekdash_v2_license_status', 'inactive');
            $instance_id = \get_option('fleekdash_v2_instance_id', '');

            fleekdash_log_debug('FleekDash V2: Using cached license status: ' . $status);

            return [
                'status' => $status,
                'message' => $status === 'active' ? 'License is active (cached)' : 'License is inactive (cached)',
                'cached' => true,
                'last_verified' => $last_verified,
                'ttl' => $ttl,
                'ttl_remaining' => $ttl - (time() - $last_verified),
                'instance_id' => $instance_id,
                'license_key' => $license_key
            ];
        }

        if ($force) {
            $this->clearFeatureCaches();
            $this->invalidatePermissionCachesForLicenseEvent('updated');
        }

        // Verify with Supabase
        fleekdash_log_debug('FleekDash V2: Verifying license with Supabase: ' . $license_key);

        if (empty($this->supabaseUrl) || empty($this->supabaseKey)) {
            fleekdash_log_error('FleekDash V2: Supabase configuration missing');
            return [
                'status' => 'error',
                'message' => MSG_SUPABASE_CONFIG_MISSING
            ];
        }

        // Call the verify_license_instance RPC function
        $url = $this->supabaseUrl . '/rest/' . $this->apiVersion . '/rpc/verify_license_instance';
        $site_url = \get_site_url();

        $response = \wp_remote_post($url, [
            'headers' => [
                'apikey' => $this->supabaseKey,
                'Authorization' => AUTH_BEARER_PREFIX . $this->supabaseKey,
                'Content-Type' => CONTENT_TYPE_JSON,
                'Accept' => CONTENT_TYPE_JSON
            ],
            'body' => json_encode([
                'p_license_key' => $license_key,
                'p_site_url' => $site_url
            ]),
            'timeout' => 30,
            'sslverify' => false // Disable SSL verification for self-signed certificates
        ]);

        if (is_wp_error($response)) {
            fleekdash_log_error('FleekDash V2: License verification error: ' . $response->get_error_message());

            // Keep the current status on error
            $current_status = \get_option('fleekdash_v2_license_status', 'inactive');
            $instance_id = \get_option('fleekdash_v2_instance_id', '');

            return [
                'status' => $current_status,
                'message' => 'Failed to verify license: ' . $response->get_error_message(),
                'error' => true,
                'instance_id' => $instance_id,
                'license_key' => $license_key
            ];
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        $status_code = wp_remote_retrieve_response_code($response);

        fleekdash_log_debug('FleekDash V2: License verification response: ' . print_r([
            'status' => $status_code,
            'body' => $body
        ], true));

        if ($status_code !== 200) {
            fleekdash_log_error('FleekDash V2: Invalid response from license server: ' . $status_code);

            // Keep the current status on error
            $current_status = \get_option('fleekdash_v2_license_status', 'inactive');
            $instance_id = \get_option('fleekdash_v2_instance_id', '');

            return [
                'status' => $current_status,
                'message' => 'Invalid response from license server',
                'error' => true,
                'instance_id' => $instance_id,
                'license_key' => $license_key
            ];
        }

        // Update the last verified timestamp
        \update_option('fleekdash_v2_license_last_verified', time());

        $isLicenseValid = \is_array($body)
            && (
                !empty($body['is_valid'])
                || !empty($body['valid'])
                || (($body['status'] ?? null) === 'active')
                || (!empty($body['success']) && (($body['message'] ?? '') !== ''))
            );

        // Check if the license is valid
        if ($isLicenseValid) {
            // License is valid
            \update_option('fleekdash_v2_license_status', 'active');

            // Save the instance ID if provided
            if (isset($body['instance_id'])) {
                \update_option('fleekdash_v2_instance_id', $body['instance_id']);
            }

            // Store license details for expiration date and LTD status
            $license_details = $body['license'] ?? null;
            if ($license_details) {
                \update_option('fleekdash_v2_license_data', $license_details);
            }

            // Store tier details if available
            $tier_details = $body['tier'] ?? null;
            if ($tier_details) {
                \update_option('fleekdash_v2_tier_data', $tier_details);
            }

            return [
                'status' => 'active',
                'message' => 'License is active',
                'instance_id' => $body['instance_id'] ?? null,
                'license_key' => $license_key,
                'license_data' => $license_details
            ];
        } else {
            // License is invalid - but check the message to see if it's actually active
            $message = \is_array($body) ? ($body['message'] ?? MSG_LICENSE_INACTIVE) : MSG_LICENSE_INACTIVE;

            // If the message says the license instance is active, treat it as active
            if (strpos($message, 'instance is active') !== false) {
                \update_option('fleekdash_v2_license_status', 'active');

                // Store license details if provided
                $license_details = $body['license'] ?? null;
                if ($license_details) {
                    \update_option('fleekdash_v2_license_data', $license_details);
                }

                // Store tier details if available
                $tier_details = $body['tier'] ?? null;
                if ($tier_details) {
                    \update_option('fleekdash_v2_tier_data', $tier_details);
                }

                return [
                    'status' => 'active',
                    'message' => $message,
                    'instance_id' => $body['instance_id'] ?? \get_option('fleekdash_v2_instance_id', ''),
                    'license_key' => $license_key,
                    'license_data' => $license_details
                ];
            }

            /** @var array<string, mixed> $bodyArray */
            $bodyArray = \is_array($body) ? $body : [];

            // verify_license_instance may return a shape that does not match $isLicenseValid above even when the
            // license is still active (different RPC schema vs activate). Never downgrade local state unless the
            // API explicitly reports revocation — otherwise the next verify after activation wipes instance_id.
            $storedKey = (string) \get_option('fleekdash_v2_license_key', '');
            if (
                !$this->verifyRpcBodyIndicatesExplicitRevocation($bodyArray)
                && \get_option('fleekdash_v2_license_status', 'inactive') === 'active'
                && $storedKey !== ''
                && $storedKey === (string) $license_key
            ) {
                fleekdash_log_debug(
                    'FleekDash V2: verify_license_instance inconclusive; preserving active license (no explicit revocation).',
                );
                $license_data = \get_option('fleekdash_v2_license_data');
                $license_data = \is_array($license_data) ? $license_data : null;

                return [
                    'status' => 'active',
                    'message' => 'License remains active (verify response inconclusive)',
                    'instance_id' => \get_option('fleekdash_v2_instance_id'),
                    'license_key' => $license_key,
                    'license_data' => $license_data,
                    'verify_preserved' => true,
                ];
            }

            // License is truly inactive
            \update_option('fleekdash_v2_license_status', 'inactive');

            // Clear the instance ID
            \delete_option('fleekdash_v2_instance_id');

            return [
                'status' => 'inactive',
                'message' => $message,
                'reason' => $body['reason'] ?? 'unknown'
            ];
        }
    }

    /**
     * Whether Supabase verify_license_instance payload explicitly revokes the license.
     * Ambiguous/missing fields must return false so we do not overwrite a valid activation.
     *
     * @param array<string, mixed> $body Decoded JSON body from verify_license_instance RPC.
     */
    private function verifyRpcBodyIndicatesExplicitRevocation(array $body): bool
    {
        $explicitRevocation = false;

        if ($body !== []) {
            if (\is_string($body['status'] ?? null)) {
                $explicitRevocation = \in_array(
                    \strtolower((string) $body['status']),
                    ['inactive', 'expired', 'revoked', 'cancelled', 'disabled'],
                    true,
                );
            }
            if (!$explicitRevocation) {
                $reason = isset($body['reason']) ? \strtolower((string) $body['reason']) : '';
                if ($reason !== '') {
                    $explicitRevocation = \in_array(
                        $reason,
                        ['revoked', 'expired', 'cancelled', 'disabled', 'inactive'],
                        true,
                    );
                }
            }
        }

        return $explicitRevocation;
    }

    private function fetchLicense(string $license_key)
    {
        return $this->apiClient->fetchLicense($license_key);
    }

    /**
     * Check if this site has a remote activation in Supabase
     * This allows activating sites remotely without user input
     *
     * @return array|WP_Error Activation data if found, WP_Error if not found or error
     */
    private function checkRemoteActivation()
    {
        return $this->remoteActivation->checkRemoteActivation();
    }

    /**
     * AJAX endpoint for testing remote activation
     */
    public function ajaxCheckRemoteActivation()
    {
        // Force check for remote activation
        $result = $this->verifyLicense(null);

        // Log detailed information
        fleekdash_log_debug('FleekDash V2: Manual remote activation check via AJAX');
        fleekdash_log_debug('FleekDash V2: Site URL: ' . \get_site_url());
        fleekdash_log_debug('FleekDash V2: Result: ' . print_r($result, true));

        // Return result as JSON
        \wp_send_json($result);
        exit;
    }

    /**
     * Check for remote activation if no license is active locally
     */
    public function maybeCheckRemoteActivation()
    {
        // Avoid multiple checks in the same request
        static $already_checked = false;
        if ($already_checked) {
            return;
        }
        $already_checked = true;

        // Check if remote activation should be checked
        if ($this->remoteActivation->shouldCheckRemoteActivation()) {
            // Call verify_license with null to trigger remote activation check
            $this->verifyLicense();
        }
    }

    /**
     * Fetch tier data from Supabase.
     *
     * @param int|string $tier_id Tier ID
     * @return array|null Tier data
     */
    private function fetchTierData($tier_id)
    {
        return $this->apiClient->fetchTierData($tier_id);
    }

    /**
     * Calculate expiration date based on license data
     *
     * @param array|null $license_data License data from Supabase
     * @return string|null Expiration date or 'Never' for LTD
     */
    private function calculateExpirationDate($license_data = null)
    {
        return LicenseHelpers::calculateExpirationDate($license_data);
    }

    /** Tier cache TTL in seconds (1 hour). */
    private const TIER_CACHE_TTL = 3600;

    /**
     * Resolve tier data, refreshing from API if stale or mismatched.
     *
     * @param array|null $licenseData License data with optional deal_tier_id
     * @return array|null Tier data
     */
    private function resolveTierData(?array $licenseData): ?array
    {
        $tierData = \get_option('fleekdash_v2_tier_data');
        $rawStored = $licenseData ?? \get_option('fleekdash_v2_license_data');
        $licenseData = LicenseHelpers::getCanonicalLicenseRecord(
            \is_array($rawStored) ? $rawStored : null,
        );
        $tierData = \is_array($tierData) ? $tierData : null;

        $shouldRefresh = !$tierData
            || (isset($tierData['_cached_at']) && (time() - $tierData['_cached_at']) > self::TIER_CACHE_TTL)
            || ($licenseData && isset($licenseData['deal_tier_id']) &&
                (!isset($tierData['id']) || $tierData['id'] !== $licenseData['deal_tier_id']));

        if ($shouldRefresh && $licenseData && isset($licenseData['deal_tier_id'])) {
            fleekdash_log_debug('FleekDash V2: Refreshing tier data - cache stale or mismatch');
            return $this->fetchTierData($licenseData['deal_tier_id']);
        }

        return $tierData;
    }

    /**
     * Expiration display string for REST payloads (wraps {@see calculateExpirationDate}).
     *
     * @param array|null $license_data
     * @return string|null
     */
    public function getLicenseExpirationDisplay($license_data = null)
    {
        return $this->calculateExpirationDate($license_data);
    }

    /**
     * Tier payload for REST (wraps {@see resolveTierData}).
     *
     * @param array|null $licenseData
     * @return array|null
     */
    public function getTierDataForLicense(?array $licenseData): ?array
    {
        return $this->resolveTierData($licenseData);
    }

    /**
     * After manual "clear license cache": refresh feature + permission caches.
     *
     * @return int Cleared feature cache entry count
     */
    public function refreshCachesAfterManualLicenseClear(): int
    {
        $cleared_count = $this->clearFeatureCaches();
        $this->invalidatePermissionCachesForLicenseEvent('updated');

        return $cleared_count;
    }

    /**
     * Clear feature cache entries tied to license/tier checks.
     *
     * @return int Number of cleared entries (best effort).
     */
    private function clearFeatureCaches(): int
    {
        if (!class_exists('\FleekDash\API\FeatureController')) {
            return 0;
        }

        $feature_controller = \FleekDash\API\FeatureController::getInstance();
        if (!method_exists($feature_controller, 'clearFeaturesCache')) {
            return 0;
        }

        $cleared_count = (int) $feature_controller->clearFeaturesCache();
        fleekdash_log_info("FleekDash V2.2: Cleared {$cleared_count} feature cache entries");

        return $cleared_count;
    }

    /**
     * Invalidate PermissionManager transients and auth caches when license state changes.
     *
     * Feature transients are cleared via {@see clearFeaturesCacheOnLicenseChange} and
     * {@see clearFeatureCaches()}; permission results are cached separately under
     * `fleekdash_permission_*` (see PermissionManager::CACHE_TTL). PermissionBootstrap
     * registers listeners for these actions — they must run after license options change.
     *
     * @param 'activated'|'deactivated'|'updated' $event License lifecycle event.
     */
    private function invalidatePermissionCachesForLicenseEvent(string $event): void
    {
        switch ($event) {
            case 'activated':
                \do_action('fleekdash_license_activated', ['source' => 'license_controller']);
                return;
            case 'deactivated':
                \do_action('fleekdash_license_deactivated', ['source' => 'license_controller']);
                return;
            default:
                \do_action('fleekdash_license_updated');
        }
    }

    /**
     * Clear features cache when license options change
     * Called automatically via WordPress hooks
     *
     * @param mixed $old_value Previous option value
     * @param mixed $new_value New option value
     */
    public function clearFeaturesCacheOnLicenseChange($old_value, $new_value)
    {
        // Only clear cache if the value actually changed
        if ($old_value === $new_value) {
            return;
        }

        // Get FeatureController instance and clear cache
        $feature_controller = \FleekDash\API\FeatureController::getInstance();
        $cleared_count = $feature_controller->clearFeaturesCache();

        // Log the cache clearing for debugging
        fleekdash_log_info("FleekDash V2.2: License changed, cleared {$cleared_count} feature cache entries");

        // Also clear license cache to ensure consistency
        \delete_option('fleekdash_v2_tier_data');
        \delete_option('fleekdash_v2_license_last_verified');

        $this->invalidatePermissionCachesForLicenseEvent('updated');
    }
}
