<?php

namespace WPSN\Modules\Licensing;

use WPSN\Core\Logger;
use WPSN\Core\Settings;

defined('ABSPATH') || exit;

final class LicenseService
{
    public const STATUS_NONE = 'none';
    public const STATUS_UNKNOWN = 'unknown';
    public const STATUS_ACTIVE = 'active';
    public const STATUS_EXPIRED = 'expired';
    public const STATUS_INVALID = 'invalid';
    public const STATUS_GRACE = 'grace';

    private Settings $settings;
    private Logger $logger;

    private LicenseSecretsRepository $secrets;
    private LicenseCacheRepository $cache;

    private LicenseClient $client;
    private LicenseSignatureVerifier $sig;

    public function __construct(Settings $settings, Logger $logger)
    {
        $this->settings = $settings;
        $this->logger = $logger;

        $this->secrets = new LicenseSecretsRepository();
        $this->cache = new LicenseCacheRepository();

        $this->client = new LicenseClient($settings, $logger);
        $this->sig = new LicenseSignatureVerifier($settings);
    }

    public function getLicenseKey(): string
    {
        return $this->secrets->getLicenseKey();
    }

    public function hasLicenseKey(): bool
    {
        return $this->secrets->hasLicenseKey();
    }

    public function setLicenseKey(string $key): void
    {
        $key = trim($key);
        $previous = $this->secrets->getLicenseKey();
        $this->secrets->setLicenseKey($key);

        if ($key !== trim($previous)) {
            $this->secrets->clearServerBinding();
        }

        $cache = $this->cache->get();
        if (is_array($cache)) {
            $cache['status'] = self::STATUS_UNKNOWN;
            $cache['last_error'] = '';
            $cache['checked_at'] = 0;
            $this->cache->set($cache);
        }
    }

    public function clearLicenseKey(): void
    {
        $this->secrets->clearLicenseKey();
        $this->secrets->clearServerBinding();
        $this->cache->clear();
    }

    public function clearCache(): void
    {
        $this->cache->clear();
    }

    public function serverUrl(): string
    {
        return trim((string) $this->settings->get('licensing.server_url', ''));
    }

    public function enforceEntitlements(): bool
    {
        return (bool) $this->settings->get('licensing.enforce_entitlements', false);
    }

    public function requireSigned(): bool
    {
        $cfg = (bool) $this->settings->get('licensing.require_signed', false);
        return $cfg || $this->enforceEntitlements();
    }

    public function cacheTtlSeconds(): int
    {
        $hours = (int) $this->settings->get('licensing.cache_ttl_hours', 24);
        $hours = max(1, min(168, $hours));
        return $hours * HOUR_IN_SECONDS;
    }

    public function graceDays(): int
    {
        $days = (int) $this->settings->get('licensing.grace_days', 14);
        return max(0, min(60, $days));
    }

    /**
     * @return array<string,mixed>|null
     */
    public function getCache(): ?array
    {
        $cache = $this->cache->get();
        return is_array($cache) ? $cache : null;
    }

    /**
     * Get effective license status (with grace logic).
     */
    public function status(): string
    {
        if (!$this->hasLicenseKey()) {
            return self::STATUS_NONE;
        }

        $cache = $this->getCache();
        if (!$cache) {
            return self::STATUS_UNKNOWN;
        }

        $status = isset($cache['status']) ? sanitize_key((string) $cache['status']) : self::STATUS_UNKNOWN;
        if ($status === '') {
            $status = self::STATUS_UNKNOWN;
        }

        if ($status === self::STATUS_ACTIVE) {
            $exp = isset($cache['license']['expires_at']) ? (string) $cache['license']['expires_at'] : '';
            if ($exp !== '') {
                $expTs = strtotime($exp);
                if ($expTs && $expTs < time()) {
                    return self::STATUS_EXPIRED;
                }
            }
        }

        if (in_array($status, [self::STATUS_UNKNOWN, self::STATUS_INVALID], true)) {
            $graceDays = $this->graceDays();
            if ($graceDays > 0) {
                $lastSuccess = isset($cache['last_success_at']) ? (int) $cache['last_success_at'] : 0;
                if ($lastSuccess > 0 && (time() - $lastSuccess) <= ($graceDays * DAY_IN_SECONDS)) {
                    return self::STATUS_GRACE;
                }
            }
        }

        return $status;
    }

    public function isActive(): bool
    {
        return in_array($this->status(), [self::STATUS_ACTIVE, self::STATUS_GRACE], true);
    }

    public function isTrusted(): bool
    {
        $cache = $this->getCache();
        if (!$cache) {
            return false;
        }
        return !empty($cache['trusted']);
    }

    /**
     * @return string[]
     */
    public function entitlements(): array
    {
        $cache = $this->getCache();
        if (!$cache || empty($cache['license']) || !is_array($cache['license'])) {
            return [];
        }

        $ents = $cache['license']['entitlements'] ?? [];
        if (!is_array($ents)) {
            return [];
        }

        $out = [];
        foreach ($ents as $e) {
            if (!is_string($e)) {
                continue;
            }
            $e = sanitize_key($e);
            if ($e !== '') {
                $out[] = $e;
            }
        }
        return array_values(array_unique($out));
    }

    public function isEntitled(string $entitlement): bool
    {
        $entitlement = sanitize_key((string) $entitlement);
        if ($entitlement === '') {
            return false;
        }

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

        if ($this->requireSigned() && !$this->isTrusted()) {
            return false;
        }

        return in_array($entitlement, $this->entitlements(), true);
    }

    /**
     * Verify license if cache is stale or if forced.
     *
     * @return array{ok:bool,verified?:bool,status?:string,error?:string,retry_after?:int}
     */
    public function verifyIfNeeded(bool $force = false): array
    {
        $key = $this->getLicenseKey();
        if ($key === '') {
            return ['ok' => true, 'verified' => false, 'status' => self::STATUS_NONE];
        }

        $cache = $this->getCache();
        $checkedAt = $cache && isset($cache['checked_at']) ? (int) $cache['checked_at'] : 0;
        $ttl = $this->cacheTtlSeconds();

        if (!$force && $checkedAt > 0 && (time() - $checkedAt) < $ttl) {
            return ['ok' => true, 'verified' => false, 'status' => $this->status()];
        }

        $res = $this->client->verify($key);
        $now = time();

        if (empty($res['ok'])) {
            $error = isset($res['error']) ? (string) $res['error'] : __('License verification failed.', 'wp-seo-next');
            $retryAfter = isset($res['retry_after']) ? (int) $res['retry_after'] : 0;

            $newCache = is_array($cache) ? $cache : [];
            $newCache['checked_at'] = $now;
            $newCache['status'] = self::STATUS_INVALID;
            $newCache['last_error'] = $error;
            $newCache['retry_after'] = $retryAfter;
            $meta = isset($res['meta']) && is_array($res['meta']) ? $res['meta'] : [];
            if (!empty($meta)) {
                $newCache['server'] = isset($newCache['server']) && is_array($newCache['server']) ? $newCache['server'] : [];
                $newCache['server']['meta'] = $meta;
            }
            $this->cache->set($newCache);

            do_action('wpsn/license/verify_failed', $error, $retryAfter);

            return ['ok' => false, 'verified' => false, 'status' => $this->status(), 'error' => $error, 'retry_after' => $retryAfter];
        }

        $license = isset($res['license']) && is_array($res['license']) ? $res['license'] : [];
        $signature = isset($res['signature']) ? (string) $res['signature'] : '';
        $alg = isset($res['alg']) ? (string) $res['alg'] : '';
        $kid = isset($res['kid']) ? (string) $res['kid'] : '';
        $meta = isset($res['meta']) && is_array($res['meta']) ? $res['meta'] : [];
        $connection = isset($res['connection']) && is_array($res['connection']) ? $res['connection'] : [];

        $trusted = false;
        if ($signature !== '') {
            $trusted = $this->sig->verify($license, $signature, $alg, $kid);
        }

        $trusted = (bool) apply_filters('wpsn/license/trusted', $trusted, $license, $signature, $alg, $kid, $meta);
        $this->persistConnection($connection, $trusted);

        $status = isset($license['status']) ? sanitize_key((string) $license['status']) : self::STATUS_UNKNOWN;
        if (!in_array($status, [self::STATUS_ACTIVE, self::STATUS_EXPIRED, self::STATUS_INVALID, self::STATUS_UNKNOWN], true)) {
            $status = self::STATUS_UNKNOWN;
        }

        if ($this->requireSigned() && !$trusted) {
            $status = self::STATUS_INVALID;
        }

        $cacheConnection = $connection;
        unset($cacheConnection['request_secret']);

        $newCache = [
            'license' => $license,
            'signature' => $signature,
            'alg' => $alg,
            'trusted' => $trusted,
            'status' => $status,
            'checked_at' => $now,
            'last_error' => '',
            'retry_after' => 0,
            'last_success_at' => ($status === self::STATUS_ACTIVE) ? $now : (isset($cache['last_success_at']) ? (int) $cache['last_success_at'] : 0),
            'server' => [
                'meta' => $meta,
                'connection' => $cacheConnection,
            ],
        ];

        $this->cache->set($newCache);

        do_action('wpsn/license/verified', $status, $license, $trusted);

        return ['ok' => true, 'verified' => true, 'status' => $this->status()];
    }

    /**
     * @param array<string,string> $connection
     */
    private function persistConnection(array $connection, bool $trusted): void
    {
        if (empty($connection)) {
            return;
        }

        if (!$trusted) {
            unset($connection['request_secret']);
        }

        if (empty($connection)) {
            return;
        }

        $this->secrets->updateServerBinding($connection);
    }
}
