<?php

/**
 * Entitlement Verifier
 *
 * Verifies license entitlements using RSA public-key signatures
 *
 * @package ProRank\SEO\Core
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


/**
 * Entitlement Verifier class
 */


class EntitlementVerifier {

    /**
     * RSA Public Key for signature verification
     * This key is embedded in the plugin - server holds the private key
     *
     * @var string
     */
    private const PUBLIC_KEY = <<<'EOK'
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4KFo67Ea2uiy749mvQeD
oQEpm1Ii3D9YwrgnccCKVFaF+mPnBGZu8aA4lUP9T1EvHZzn+3mleXYpTsBFYWcV
zdesQz6bXlbzWOQGF+kw39zkT/nyTgjFoXzwVWhlRLLC1cZBHjnQwEphQAzmdU64
+BrZ4Qhi/v+jDe01dBU3saTJJEUca16C5vi6tf2D7KVO6/+k9Pq3Bk/J4JjOI1p9
yb/yQsVXxkFZDcj5JNJNUUVqKmqyQSBcxyZX99p09CoPDwFlvLv+sIUiOCRWrSHV
3xdefI57xooLQg2ZXM0AzKk/iRV3qXY0sWPN5Sf85k3iX0Hu4P8yrYMcuH21L4zR
RwIDAQAB
-----END PUBLIC KEY-----
EOK;

    /**
     * Cache duration for entitlement (seconds)
     *
     * @var int
     */
    private const CACHE_DURATION = 3600; // 1 hour

    /**
     * Grace period after license expiry (seconds)
     *
     * @var int
     */
    private const GRACE_PERIOD = 604800; // 7 days

    /**
     * Deactivation cooldown period (seconds)
     *
     * @var int
     */
    private const DEACTIVATION_COOLDOWN = 172800; // 48 hours

    /**
     * Cached entitlement data
     *
     * @var array|null
     */
    private ?array $cached_entitlement = null;

    /**
     * Get the public key for verification
     *
     * @return string
     */
    public function get_public_key(): string {
        // Allow override via constant (for testing/custom deployments)
        if (defined('PRORANK_PUBLIC_KEY')) {
            return PRORANK_PUBLIC_KEY;
        }

        return self::PUBLIC_KEY;
    }

    /**
     * Verify entitlement token signature
     *
     * @param string $token Base64-encoded entitlement token
     * @param string $signature Base64-encoded RSA signature
     * @return bool True if signature is valid
     */
    public function verify_signature(string $token, string $signature): bool {
        $public_key = openssl_pkey_get_public($this->get_public_key());

        if ($public_key === false) {
            prorank_log('ProRank SEO: Failed to load public key for verification');
            return false;
        }

        $decoded_signature = base64_decode($signature, true);
        if ($decoded_signature === false) {
            return false;
        }

        $result = openssl_verify($token, $decoded_signature, $public_key, OPENSSL_ALGO_SHA256);

        return $result === 1;
    }

    /**
     * Verify and decode entitlement token
     *
     * @param string $token Base64-encoded JSON token
     * @param string $signature Base64-encoded RSA signature
     * @return array|null Decoded entitlement data or null if invalid
     */
    public function verify_entitlement(string $token, string $signature): ?array {
        // Verify signature first
        if (!$this->verify_signature($token, $signature)) {
            prorank_log('ProRank SEO: Entitlement signature verification failed');
            return null;
        }

        // Decode token
        $decoded = base64_decode($token, true);
        if ($decoded === false) {
            return null;
        }

        $data = json_decode($decoded, true);
        if (!is_array($data)) {
            return null;
        }

        // Validate required fields
        $required_fields = ['tier', 'expiry', 'site_limit', 'license_key'];
        foreach ($required_fields as $field) {
            if (!isset($data[$field])) {
                prorank_log("ProRank SEO: Missing required field in entitlement: {$field}");
                return null;
            }
        }

        return $data;
    }

    /**
     * Check if entitlement is expired (considering grace period)
     *
     * @param array $entitlement Entitlement data
     * @return bool True if expired beyond grace period
     */
    public function is_expired(array $entitlement): bool {
        if (!isset($entitlement['expiry'])) {
            return true;
        }

        $expiry = strtotime($entitlement['expiry']);
        if ($expiry === false) {
            return true;
        }

        $grace_end = $expiry + self::GRACE_PERIOD;

        return time() > $grace_end;
    }

    /**
     * Check if entitlement is in grace period
     *
     * @param array $entitlement Entitlement data
     * @return bool True if in grace period
     */
    public function is_in_grace_period(array $entitlement): bool {
        if (!isset($entitlement['expiry'])) {
            return false;
        }

        $expiry = strtotime($entitlement['expiry']);
        if ($expiry === false) {
            return false;
        }

        $now = time();
        $grace_end = $expiry + self::GRACE_PERIOD;

        return $now > $expiry && $now <= $grace_end;
    }

    /**
     * Get days remaining in grace period
     *
     * @param array $entitlement Entitlement data
     * @return int Days remaining (0 if not in grace period)
     */
    public function get_grace_days_remaining(array $entitlement): int {
        if (!$this->is_in_grace_period($entitlement)) {
            return 0;
        }

        $expiry = strtotime($entitlement['expiry']);
        $grace_end = $expiry + self::GRACE_PERIOD;
        $remaining_seconds = $grace_end - time();

        return (int) ceil($remaining_seconds / 86400);
    }

    /**
     * Check if site is within deactivation cooldown
     *
     * @param string $hostname Site hostname
     * @return bool True if in cooldown period
     */
    public function is_in_deactivation_cooldown(string $hostname): bool {
        $deactivations = get_option('prorank_site_deactivations', []);

        if (!isset($deactivations[$hostname])) {
            return false;
        }

        $deactivated_at = $deactivations[$hostname];
        $cooldown_end = $deactivated_at + self::DEACTIVATION_COOLDOWN;

        return time() < $cooldown_end;
    }

    /**
     * Get cooldown hours remaining
     *
     * @param string $hostname Site hostname
     * @return int Hours remaining (0 if not in cooldown)
     */
    public function get_cooldown_hours_remaining(string $hostname): int {
        $deactivations = get_option('prorank_site_deactivations', []);

        if (!isset($deactivations[$hostname])) {
            return 0;
        }

        $deactivated_at = $deactivations[$hostname];
        $cooldown_end = $deactivated_at + self::DEACTIVATION_COOLDOWN;
        $remaining_seconds = $cooldown_end - time();

        if ($remaining_seconds <= 0) {
            return 0;
        }

        return (int) ceil($remaining_seconds / 3600);
    }

    /**
     * Record site deactivation
     *
     * @param string $hostname Site hostname
     */
    public function record_deactivation(string $hostname): void {
        $deactivations = get_option('prorank_site_deactivations', []);
        $deactivations[$hostname] = time();

        // Clean up old entries (older than cooldown period)
        $cutoff = time() - self::DEACTIVATION_COOLDOWN;
        $deactivations = array_filter($deactivations, fn($time) => $time > $cutoff);

        update_option('prorank_site_deactivations', $deactivations, false);
    }

    /**
     * Clear deactivation record
     *
     * @param string $hostname Site hostname
     */
    public function clear_deactivation(string $hostname): void {
        $deactivations = get_option('prorank_site_deactivations', []);
        unset($deactivations[$hostname]);
        update_option('prorank_site_deactivations', $deactivations, false);
    }

    /**
     * Cache entitlement data
     *
     * @param array $entitlement Entitlement data
     */
    public function cache_entitlement(array $entitlement): void {
        $cache_data = [
            'entitlement' => $entitlement,
            'cached_at' => time(),
            'expires_at' => time() + self::CACHE_DURATION,
        ];

        set_transient('prorank_entitlement_cache', $cache_data, self::CACHE_DURATION);
        $this->cached_entitlement = $entitlement;
    }

    /**
     * Get cached entitlement
     *
     * @return array|null Cached entitlement or null if expired/not cached
     */
    public function get_cached_entitlement(): ?array {
        if ($this->cached_entitlement !== null) {
            return $this->cached_entitlement;
        }

        $cache_data = get_transient('prorank_entitlement_cache');

        if (!is_array($cache_data) || !isset($cache_data['entitlement'])) {
            return null;
        }

        // Check if cache is still valid
        if (time() > ($cache_data['expires_at'] ?? 0)) {
            delete_transient('prorank_entitlement_cache');
            return null;
        }

        $this->cached_entitlement = $cache_data['entitlement'];
        return $this->cached_entitlement;
    }

    /**
     * Clear entitlement cache
     */
    public function clear_cache(): void {
        delete_transient('prorank_entitlement_cache');
        $this->cached_entitlement = null;
    }

    /**
     * Check if cloud feature is allowed
     *
     * @param string $feature Feature identifier
     * @param array|null $entitlement Entitlement data (uses cached if null)
     * @return bool
     */
    public function is_cloud_feature_allowed(string $feature, ?array $entitlement = null): bool {
        $entitlement = $entitlement ?? $this->get_cached_entitlement();

        if ($entitlement === null) {
            return false;
        }

        // Check if expired (grace period doesn't apply to cloud features)
        $expiry = strtotime($entitlement['expiry'] ?? '');
        if ($expiry === false || time() > $expiry) {
            return false;
        }

        // Check if feature is in allowed list.
        // Cloud audit was previously provisioned as "seo_audit" but some
        // runtime checks still asked for "cloud_audit". Accept both.
        $allowed_features = is_array($entitlement['cloud_features'] ?? null) ? $entitlement['cloud_features'] : [];
        $feature_aliases = [
            'cloud_audit' => ['cloud_audit', 'seo_audit'],
            'seo_audit' => ['seo_audit', 'cloud_audit'],
        ];

        foreach (($feature_aliases[$feature] ?? [$feature]) as $candidate) {
            if (in_array($candidate, $allowed_features, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get grace period duration in days
     *
     * @return int
     */
    public function get_grace_period_days(): int {
        return (int) (self::GRACE_PERIOD / 86400);
    }

    /**
     * Get deactivation cooldown duration in hours
     *
     * @return int
     */
    public function get_cooldown_hours(): int {
        return (int) (self::DEACTIVATION_COOLDOWN / 3600);
    }

    /**
     * Get entitlement status summary
     *
     * @param array|null $entitlement Entitlement data
     * @return array
     */
    public function get_status_summary(?array $entitlement = null): array {
        $entitlement = $entitlement ?? $this->get_cached_entitlement();

        if ($entitlement === null) {
            return [
                'status' => 'no_entitlement',
                'is_valid' => false,
                'tier' => 'free',
            ];
        }

        $is_expired = $this->is_expired($entitlement);
        $is_in_grace = $this->is_in_grace_period($entitlement);

        return [
            'status' => $is_expired ? 'expired' : ($is_in_grace ? 'grace_period' : 'active'),
            'is_valid' => !$is_expired,
            'tier' => $entitlement['tier'] ?? 'free',
            'expiry' => $entitlement['expiry'] ?? null,
            'is_in_grace_period' => $is_in_grace,
            'grace_days_remaining' => $this->get_grace_days_remaining($entitlement),
            'site_limit' => $entitlement['site_limit'] ?? 1,
            'activated_sites' => $entitlement['activated_sites'] ?? [],
            'cloud_features' => $entitlement['cloud_features'] ?? [],
        ];
    }
}
