<?php
/**
 * License Manager (CF16/F56 — Freemius-backed).
 *
 * Two storage backends, in priority order:
 *  1. Freemius SDK storage (FS_Plugin_License) — when SDK is loaded.
 *  2. Legacy wp_options storage (swapads_client_license_*) — fallback
 *     for installs predating the SDK integration. Kept for backwards
 *     compat; once LicenseManager::store() is called by the new
 *     SettingsPage (post-CF16), the legacy data is migrated forward
 *     and not touched again.
 *
 * @package SwapAds\Client\License
 * @since   1.0.0  (legacy)
 * @since   1.1.0  (Freemius fallback + migration path)
 */

declare(strict_types=1);

namespace SwapAds\Client\License;

/**
 * Class LicenseManager.
 *
 * @since 1.0.0
 */
final class LicenseManager
{
    public const OPTION_KEY       = 'swapads_client_license_key';
    public const OPTION_SECRET    = 'swapads_client_license_secret';
    public const OPTION_SITE_ID   = 'swapads_client_site_id';
    public const OPTION_DOMAIN    = 'swapads_client_site_domain';
    public const OPTION_STATUS    = 'swapads_client_license_status';
    public const OPTION_FREEMIUS_PLAN   = 'swapads_client_freemius_plan';
    public const OPTION_FREEMIUS_LICENSE = 'swapads_client_freemius_license_id';

    /**
     * Check if the client is licensed.
     *
     * Returns true when either (a) a Freemius license exists in SDK storage,
     * or (b) the legacy wp_options key is set.
     *
     * @since 1.1.0 Reads Freemius first, falls back to legacy.
     *
     * @return bool
     */
    public static function isLicensed(): bool
    {
        if (function_exists('swa_fs')) {
            $sdkLicense = swa_fs()->_get_license();
            if (is_object($sdkLicense) && property_exists($sdkLicense, 'id') && $sdkLicense->id > 0) {
                return true;
            }
        }
        // CF17.1: a site is "licensed" when either:
        //  (a) a paid license key is stored, OR
        //  (b) the server secret has been fetched from /public-key (free plan)
        if ((string) get_option(self::OPTION_KEY, '') !== '') {
            return true;
        }
        if ((string) get_option(self::OPTION_SECRET, '') !== '') {
            return true;
        }
        return false;
    }

    /**
     * Get the license key.
     *
     * @since 1.1.0 Reads Freemius first.
     *
     * @return string
     */
    public static function key(): string
    {
        if (function_exists('swa_fs')) {
            $sdkLicense = swa_fs()->_get_license();
            if (is_object($sdkLicense) && !empty($sdkLicense->secret_key)) {
                return (string) $sdkLicense->secret_key;
            }
        }
        $stored = (string) get_option(self::OPTION_KEY, '');
        if ($stored !== '') {
            return $stored;
        }
        // CF17.1 fallback: use FS_Site.id as a synthetic license key for free-plan sites.
        $fsSiteId = self::freemiusSiteId();
        if ($fsSiteId > 0) {
            return 'fs_site_' . $fsSiteId;
        }
        return '';
    }

    /**
     * Get the license secret (HMAC shared secret for server calls).
     *
     * @since 1.1.0 Reads Freemius first.
     *
     * @return string
     */
    public static function secret(): string
    {
        // Paid license path: SDK stores the secret in the license object.
        if (function_exists('swa_fs')) {
            $sdkLicense = swa_fs()->_get_license();
            if (is_object($sdkLicense) && !empty($sdkLicense->secret_key)) {
                return (string) $sdkLicense->secret_key;
            }
        }
        // CF17.1: OPTION_SECRET holds SWAPADS_SERVER_SECRET (set by manual
        // entry or by FreemiusAutoActivator::ensureServerSecret() via /public-key).
        return (string) get_option(self::OPTION_SECRET, '');
    }

    /**
     * Get the site_id assigned by server.
     *
     * @return int
     */
    public static function siteId(): int
    {
        return (int) get_option(self::OPTION_SITE_ID, 0);
    }

    /**
     * Get the registered domain.
     *
     * @return string
     */
    public static function domain(): string
    {
        return (string) get_option(self::OPTION_DOMAIN, '');
    }

    /**
     * Get the license status (active / expired / cancelled / etc).
     *
     * Reads from the SDK if available, falls back to legacy.
     *
     * @since 1.1.0
     *
     * @return string
     */
    public static function status(): string
    {
        if (function_exists('swa_fs')) {
            $sdkLicense = swa_fs()->_get_license();
            if (is_object($sdkLicense) && property_exists($sdkLicense, 'id') && $sdkLicense->id > 0) {
                // Determine status from FS_Plugin_License fields.
                $isCancelled = property_exists($sdkLicense, 'is_cancelled') && $sdkLicense->is_cancelled;
                $expiration  = (string) (property_exists($sdkLicense, 'expiration') ? $sdkLicense->expiration : '');
                if ($isCancelled) {
                    return 'cancelled';
                }
                if ($expiration !== '' && $expiration !== 'never' && strtotime($expiration) < time()) {
                    return 'expired';
                }
                return 'active';
            }
        }
        return (string) get_option(self::OPTION_STATUS, '');
    }

    /**
     * Get the Freemius plan id for the current license, if any.
     *
     * Y2 will use this for plan-based feature gating. In Y1 we read it
     * for visibility / display only — actual gating happens on the
     * server side using pricing_mode column.
     *
     * @since 1.1.0
     *
     * @return int 0 if no plan (free), else plan id.
     */
    public static function freemiusPlanId(): int
    {
        // Read legacy first (we own this), then SDK as fallback.
        $stored = (int) get_option(self::OPTION_FREEMIUS_PLAN, 0);
        if ($stored > 0) {
            return $stored;
        }
        if (function_exists('swa_fs')) {
            $sdkLicense = swa_fs()->_get_license();
            if (is_object($sdkLicense) && !empty($sdkLicense->plan_id)) {
                return (int) $sdkLicense->plan_id;
            }
        }
        return 0;
    }

    /**
     * Store a license activation response from the server (legacy direct activation).
     *
     * For new Freemius-activated licenses, prefer activateViaSdk() instead.
     *
     * @param string $licenseKey
     * @param string $licenseSecret
     * @param int    $siteId
     * @param string $domain
     * @param string $status
     */
    public static function store(string $licenseKey, string $licenseSecret, int $siteId, string $domain, string $status): void
    {
        update_option(self::OPTION_KEY, $licenseKey);
        update_option(self::OPTION_SECRET, $licenseSecret);
        update_option(self::OPTION_SITE_ID, $siteId);
        update_option(self::OPTION_DOMAIN, $domain);
        update_option(self::OPTION_STATUS, $status);
    }

    /**
     * Forget the stored license (deactivate).
     */
    public static function forget(): void
    {
        delete_option(self::OPTION_KEY);
        delete_option(self::OPTION_SECRET);
        delete_option(self::OPTION_SITE_ID);
        delete_option(self::OPTION_DOMAIN);
        delete_option(self::OPTION_STATUS);
        delete_option(self::OPTION_FREEMIUS_PLAN);
        delete_option(self::OPTION_FREEMIUS_LICENSE);
    }

    /**
     * Activate the plugin license via the embedded Freemius SDK.
     *
     * When the SDK is present, this delegates to fs_activate() and the SDK
     * handles the communication with the Freemius API. In Y1 the default
     * plan is free ($0/month); operators only get a license after purchase.
     *
     * In development mode (WP_FS__DEV_MODE defined), the SDK is shortcut to
     * a fake activation so the rest of the plugin can be exercised locally.
     *
     * @since 1.1.0
     *
     * @return bool true on success, false if the SDK is not loaded.
     */
    public static function activateViaSdk(): bool
    {
        if (!function_exists('swa_fs')) {
            return false;
        }
        $sdk = swa_fs();
        if (method_exists($sdk, 'activate')) {
            $sdk->activate();
        }
        return true;
    }

    /**
     * Confirm that the Freemius SDK has been loaded successfully.
     *
     * @since 1.1.0
     *
     * @return bool
     */
    public static function isSdkLoaded(): bool
    {
        return function_exists('swa_fs');
    }


    /**
     * CF17.1: Get the FS_Site.secret_key for free-plan installs.
     *
     * Returns the Freemius site install secret, used as the HMAC secret
     * when no paid license is available. Empty string when SDK not loaded
     * or site not registered.
     *
     * @since 1.3.0
     *
     * @return string
     */
    public static function freemiusSiteSecret(): string
    {
        if (!function_exists('swa_fs')) {
            return '';
        }
        $sdk = swa_fs();
        if (!is_object($sdk) || !method_exists($sdk, 'get_site')) {
            return '';
        }
        $site = $sdk->get_site();
        if (!is_object($site) || empty($site->secret_key)) {
            return '';
        }
        return (string) $site->secret_key;
    }

    /**
     * CF17.1: Get the FS_Site.id for free-plan installs.
     *
     * @since 1.3.0
     *
     * @return int
     */
    public static function freemiusSiteId(): int
    {
        if (!function_exists('swa_fs')) {
            return 0;
        }
        $sdk = swa_fs();
        if (!is_object($sdk) || !method_exists($sdk, 'get_site')) {
            return 0;
        }
        $site = $sdk->get_site();
        if (!is_object($site) || empty($site->id)) {
            return 0;
        }
        return (int) $site->id;
    }

    /**
     * CF17.1: Check whether the SDK has a registered site (free or paid).
     *
     * Distinct from sdkHasLicense() (CF17): a registered site without a
     * paid license can still authenticate with the server via FS_Site.
     *
     * @since 1.3.0
     *
     * @return bool
     */
    public static function sdkHasSite(): bool
    {
        if (!function_exists('swa_fs')) {
            return false;
        }
        $sdk = swa_fs();
        if (!is_object($sdk)) {
            return false;
        }
        // Freemius returns false when site is in opt-in (not yet allowed).
        if (method_exists($sdk, 'is_registered') && !$sdk->is_registered()) {
            return false;
        }
        return self::freemiusSiteSecret() !== '';
    }

    /**
     * Generate a unique visitor id and SHA-256 hash it for privacy.
     *
     * @return string 64-char hex hash.
     */
    public static function visitorIdHash(): string
    {
        if (empty($_COOKIE['swapads_visitor_id'] ?? '')) {
            $vid = bin2hex(random_bytes(16));
            setcookie('swapads_visitor_id', $vid, [
                'expires'  => time() + 86400 * 365,
                'path'     => '/',
                'secure'   => is_ssl(),
                'httponly' => true,
                'samesite' => 'Lax',
            ]);
            $_COOKIE['swapads_visitor_id'] = $vid;
        }
        return hash('sha256', (string) $_COOKIE['swapads_visitor_id']);
    }
}
