<?php
/**
 * Plugin License Checker
 *
 * Validates the plugin's own license against a remote server.
 *
 * @package Jeremias\WcLicensedProduct\License
 */

declare(strict_types=1);

namespace Jeremias\WcLicensedProduct\License;

use Magdev\WcLicensedProductClient\LicenseClient;
use Magdev\WcLicensedProductClient\LicenseClientInterface;
use Magdev\WcLicensedProductClient\SecureLicenseClient;
use Magdev\WcLicensedProductClient\Exception\LicenseException;
use Symfony\Component\HttpClient\HttpClient;

/**
 * Handles validation of this plugin's license
 */
final class PluginLicenseChecker
{
    /**
     * Cache key for license validation result
     */
    private const CACHE_KEY = 'wclp_plugin_license_valid';

    /**
     * Cache TTL for successful validation (1 hour)
     */
    private const CACHE_TTL = 3600;

    /**
     * Cache key for error messages
     */
    private const ERROR_CACHE_KEY = 'wclp_plugin_license_error';

    /**
     * Cache TTL for errors (5 minutes)
     */
    private const ERROR_CACHE_TTL = 300;

    /**
     * Singleton instance
     */
    private static ?self $instance = null;

    /**
     * Cached localhost check result
     */
    private ?bool $isLocalhostCached = null;

    /**
     * Cached self-licensing check result
     */
    private ?bool $isSelfLicensingCached = null;

    /**
     * Get singleton instance
     */
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * Private constructor for singleton
     */
    private function __construct()
    {
        // Private constructor
    }

    /**
     * Check if the plugin license is valid
     *
     * Returns cached result if available, otherwise validates against server.
     */
    public function isLicenseValid(): bool
    {
        // Always valid on localhost
        if ($this->isLocalhost()) {
            return true;
        }

        // Always valid when self-licensing (server URL points to this installation)
        if ($this->isSelfLicensing()) {
            return true;
        }

        // Check cache first
        $cached = get_transient(self::CACHE_KEY);
        if ($cached !== false) {
            return (bool) $cached;
        }

        // Validate against server
        return $this->validateLicense();
    }

    /**
     * Validate license against the server
     *
     * @param bool $forceRefresh Force refresh even if cached
     * @return bool True if license is valid
     */
    public function validateLicense(bool $forceRefresh = false): bool
    {
        // Always valid on localhost
        if ($this->isLocalhost()) {
            return true;
        }

        // Always valid when self-licensing (server URL points to this installation)
        if ($this->isSelfLicensing()) {
            return true;
        }

        // Check settings are configured
        $serverUrl = $this->getLicenseServerUrl();
        $licenseKey = $this->getLicenseKey();

        if (empty($serverUrl) || empty($licenseKey)) {
            set_transient(
                self::ERROR_CACHE_KEY,
                __('License settings not configured.', 'wc-licensed-product'),
                self::ERROR_CACHE_TTL
            );
            return false;
        }

        // Check cache unless force refresh
        if (!$forceRefresh) {
            $cached = get_transient(self::CACHE_KEY);
            if ($cached !== false) {
                return (bool) $cached;
            }
        }

        try {
            $client = $this->createLicenseClient();
            $domain = $this->getCurrentDomain();

            // Validate the license
            $client->validate($licenseKey, $domain);

            // Valid license - cache success
            set_transient(self::CACHE_KEY, 1, self::CACHE_TTL);
            delete_transient(self::ERROR_CACHE_KEY);

            return true;
        } catch (LicenseException $e) {
            // License-specific error (invalid, expired, revoked, etc.)
            set_transient(self::CACHE_KEY, 0, self::CACHE_TTL);
            set_transient(self::ERROR_CACHE_KEY, $e->getMessage(), self::ERROR_CACHE_TTL);

            return false;
        } catch (\Throwable $e) {
            // Network/server error - use shorter cache to allow retry
            set_transient(
                self::ERROR_CACHE_KEY,
                __('Could not connect to license server.', 'wc-licensed-product') . ' ' . $e->getMessage(),
                self::ERROR_CACHE_TTL
            );

            // Don't cache validation failure on network errors - allow retry on next page load
            return false;
        }
    }

    /**
     * Get the last error message
     */
    public function getLastError(): ?string
    {
        $error = get_transient(self::ERROR_CACHE_KEY);
        return $error !== false ? (string) $error : null;
    }

    /**
     * Clear the validation cache
     */
    public function clearCache(): void
    {
        delete_transient(self::CACHE_KEY);
        delete_transient(self::ERROR_CACHE_KEY);
        $this->isLocalhostCached = null;
        $this->isSelfLicensingCached = null;
    }

    /**
     * Check if running on localhost
     *
     * Matches localhost, 127.0.0.1, ::1, and any port number.
     */
    public function isLocalhost(): bool
    {
        if ($this->isLocalhostCached !== null) {
            return $this->isLocalhostCached;
        }

        $domain = $this->getCurrentDomain();

        // Remove port number if present
        $domainWithoutPort = preg_replace('/:[\d]+$/', '', $domain);

        // Check for localhost variants
        $localhostNames = ['localhost', '127.0.0.1', '::1'];

        if (in_array($domainWithoutPort, $localhostNames, true)) {
            $this->isLocalhostCached = true;
            return true;
        }

        // Check for .localhost and .local subdomains
        if (
            str_ends_with($domainWithoutPort, '.localhost') ||
            str_ends_with($domainWithoutPort, '.local')
        ) {
            $this->isLocalhostCached = true;
            return true;
        }

        $this->isLocalhostCached = false;
        return false;
    }

    /**
     * Check if self-licensing (license server URL points to this installation)
     *
     * Prevents circular dependency where plugin tries to validate against itself.
     * Plugins can only be validated against the original store from which they were obtained.
     */
    public function isSelfLicensing(): bool
    {
        if ($this->isSelfLicensingCached !== null) {
            return $this->isSelfLicensingCached;
        }

        $serverUrl = $this->getLicenseServerUrl();

        // No server URL configured - not self-licensing
        if (empty($serverUrl)) {
            $this->isSelfLicensingCached = false;
            return false;
        }

        // Parse both URLs to compare domains
        $serverParsed = parse_url($serverUrl);
        $siteUrl = get_site_url();
        $siteParsed = parse_url($siteUrl);

        // Get normalized domains (lowercase, no www prefix)
        $serverDomain = $this->normalizeDomain($serverParsed['host'] ?? '');
        $siteDomain = $this->normalizeDomain($siteParsed['host'] ?? '');

        // If domains match, this is self-licensing
        if ($serverDomain === $siteDomain) {
            $this->isSelfLicensingCached = true;
            return true;
        }

        $this->isSelfLicensingCached = false;
        return false;
    }

    /**
     * Normalize a domain for comparison (lowercase, strip www)
     */
    private function normalizeDomain(string $domain): string
    {
        $domain = strtolower(trim($domain));

        // Strip www. prefix
        if (str_starts_with($domain, 'www.')) {
            $domain = substr($domain, 4);
        }

        return $domain;
    }

    /**
     * Get the current domain from the site URL
     */
    private function getCurrentDomain(): string
    {
        $siteUrl = get_site_url();
        $parsed = parse_url($siteUrl);
        $host = $parsed['host'] ?? 'localhost';

        // Include port if non-standard
        if (isset($parsed['port'])) {
            $host .= ':' . $parsed['port'];
        }

        return strtolower($host);
    }

    /**
     * Get the license server URL from settings
     */
    private function getLicenseServerUrl(): string
    {
        return (string) get_option('wc_licensed_product_plugin_license_server_url', '');
    }

    /**
     * Get the license key from settings
     */
    private function getLicenseKey(): string
    {
        return (string) get_option('wc_licensed_product_plugin_license_key', '');
    }

    /**
     * Get the server secret from settings (optional)
     */
    private function getServerSecret(): ?string
    {
        $secret = get_option('wc_licensed_product_plugin_license_server_secret', '');
        return !empty($secret) ? (string) $secret : null;
    }

    /**
     * Create the license client instance
     */
    private function createLicenseClient(): LicenseClientInterface
    {
        $httpClient = HttpClient::create([
            'timeout' => 10,
            'verify_peer' => true,
        ]);

        $serverUrl = $this->getLicenseServerUrl();
        $serverSecret = $this->getServerSecret();

        // Use secure client if server secret is configured
        if ($serverSecret !== null) {
            return new SecureLicenseClient(
                httpClient: $httpClient,
                baseUrl: $serverUrl,
                serverSecret: $serverSecret,
            );
        }

        return new LicenseClient(
            httpClient: $httpClient,
            baseUrl: $serverUrl,
        );
    }
}
