<?php
/**
 * Plugin Update Checker
 *
 * Checks for plugin updates from the configured license server.
 *
 * @package Jeremias\WcLicensedProduct\Update
 */

declare(strict_types=1);

namespace Jeremias\WcLicensedProduct\Update;

use Jeremias\WcLicensedProduct\Admin\SettingsController;
use Jeremias\WcLicensedProduct\License\PluginLicenseChecker;
use Symfony\Component\HttpClient\HttpClient;

/**
 * Handles checking for plugin updates from the license server
 *
 * This class hooks into WordPress's native plugin update system to check for
 * updates from the configured license server. It validates the license and
 * provides download authentication.
 */
final class PluginUpdateChecker
{
    /**
     * Cache key for update info
     */
    private const CACHE_KEY = 'wclp_update_info';

    /**
     * Default cache TTL (12 hours)
     */
    private const DEFAULT_CACHE_TTL = 43200;

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

    /**
     * Plugin slug
     */
    private string $pluginSlug;

    /**
     * Plugin basename (slug/slug.php)
     */
    private string $pluginBasename;

    /**
     * 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()
    {
        $this->pluginSlug = 'wc-licensed-product';
        $this->pluginBasename = WC_LICENSED_PRODUCT_PLUGIN_BASENAME;
    }

    /**
     * Register WordPress hooks for update checking
     */
    public function register(): void
    {
        // Skip if update notifications are disabled
        if ($this->isUpdateNotificationDisabled()) {
            return;
        }

        // Check for updates
        add_filter('pre_set_site_transient_update_plugins', [$this, 'checkForUpdates']);

        // Provide plugin information for the update modal
        add_filter('plugins_api', [$this, 'getPluginInfo'], 10, 3);

        // Add authentication headers to download requests
        add_filter('http_request_args', [$this, 'addAuthHeaders'], 10, 2);

        // Handle auto-install setting
        add_filter('auto_update_plugin', [$this, 'handleAutoInstall'], 10, 2);

        // Clear cache on settings save
        add_action('update_option_wc_licensed_product_plugin_license_key', [$this, 'clearCache']);
        add_action('update_option_wc_licensed_product_plugin_license_server_url', [$this, 'clearCache']);
        add_action('update_option_wc_licensed_product_update_notification_enabled', [$this, 'clearCache']);
    }

    /**
     * Check if update notifications are disabled
     */
    private function isUpdateNotificationDisabled(): bool
    {
        // Check constant
        if (defined('WC_LICENSE_DISABLE_AUTO_UPDATE') && WC_LICENSE_DISABLE_AUTO_UPDATE) {
            return true;
        }

        // Check setting
        return !SettingsController::isUpdateNotificationEnabled();
    }

    /**
     * Handle auto-install setting for WordPress automatic updates
     *
     * @param bool|null $update The update decision
     * @param object $item The plugin update object
     * @return bool|null Whether to auto-update this plugin
     */
    public function handleAutoInstall($update, $item): ?bool
    {
        // Only handle our plugin
        if (!isset($item->plugin) || $item->plugin !== $this->pluginBasename) {
            return $update;
        }

        // Return true to enable auto-install, false to disable, or null to use default
        return SettingsController::isAutoInstallEnabled() ? true : $update;
    }

    /**
     * Check for plugin updates
     *
     * @param object $transient The update_plugins transient
     * @return object Modified transient
     */
    public function checkForUpdates($transient)
    {
        if (empty($transient->checked)) {
            return $transient;
        }

        // Get cached update info or fetch fresh
        $updateInfo = $this->getUpdateInfo();

        if (!$updateInfo || !isset($updateInfo['update_available']) || !$updateInfo['update_available']) {
            return $transient;
        }

        // Compare versions
        $currentVersion = $transient->checked[$this->pluginBasename] ?? WC_LICENSED_PRODUCT_VERSION;

        if (version_compare($updateInfo['version'], $currentVersion, '>')) {
            $transient->response[$this->pluginBasename] = $this->buildUpdateObject($updateInfo);
        }

        return $transient;
    }

    /**
     * Get plugin information for the update modal
     *
     * @param false|object|array $result The result object or array
     * @param string $action The API action
     * @param object $args Request arguments
     * @return false|object
     */
    public function getPluginInfo($result, string $action, object $args)
    {
        if ($action !== 'plugin_information') {
            return $result;
        }

        if (!isset($args->slug) || $args->slug !== $this->pluginSlug) {
            return $result;
        }

        // Get update info
        $updateInfo = $this->getUpdateInfo(true);

        if (!$updateInfo) {
            return $result;
        }

        return $this->buildPluginInfoObject($updateInfo);
    }

    /**
     * Add authentication headers to download requests
     *
     * @param array $args HTTP request arguments
     * @param string $url Request URL
     * @return array Modified arguments
     */
    public function addAuthHeaders(array $args, string $url): array
    {
        // Only modify requests to our license server
        $serverUrl = $this->getLicenseServerUrl();
        if (empty($serverUrl) || strpos($url, parse_url($serverUrl, PHP_URL_HOST)) === false) {
            return $args;
        }

        // Only modify download requests
        if (strpos($url, 'license-download') === false) {
            return $args;
        }

        // Add license key to headers for potential server-side verification
        $licenseKey = $this->getLicenseKey();
        if (!empty($licenseKey)) {
            $args['headers']['X-License-Key'] = $licenseKey;
        }

        return $args;
    }

    /**
     * Get update info from cache or server
     *
     * @param bool $forceRefresh Force refresh from server
     * @return array|null Update info or null if unavailable
     */
    public function getUpdateInfo(bool $forceRefresh = false): ?array
    {
        // Check cache unless force refresh
        if (!$forceRefresh) {
            $cached = get_transient(self::CACHE_KEY);
            if ($cached !== false) {
                return $cached;
            }
        }

        // Fetch from server
        $updateInfo = $this->fetchUpdateInfo();

        if ($updateInfo) {
            // Cache the result
            $cacheTtl = $this->getCacheTtl();
            set_transient(self::CACHE_KEY, $updateInfo, $cacheTtl);
        }

        return $updateInfo;
    }

    /**
     * Fetch update info from the license server
     */
    private function fetchUpdateInfo(): ?array
    {
        $serverUrl = $this->getLicenseServerUrl();
        $licenseKey = $this->getLicenseKey();

        if (empty($serverUrl) || empty($licenseKey)) {
            return null;
        }

        try {
            $httpClient = HttpClient::create([
                'timeout' => 15,
                'verify_peer' => true,
            ]);

            $updateCheckUrl = rtrim($serverUrl, '/') . '/wp-json/wc-licensed-product/v1/update-check';

            $response = $httpClient->request('POST', $updateCheckUrl, [
                'json' => [
                    'license_key' => $licenseKey,
                    'domain' => $this->getCurrentDomain(),
                    'plugin_slug' => $this->pluginSlug,
                    'current_version' => WC_LICENSED_PRODUCT_VERSION,
                ],
            ]);

            if ($response->getStatusCode() !== 200) {
                return null;
            }

            $data = $response->toArray();

            // Verify response structure
            if (!isset($data['success']) || !$data['success']) {
                return null;
            }

            return $data;
        } catch (\Throwable $e) {
            // Log error but don't break the site
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('WC Licensed Product: Update check failed - ' . $e->getMessage());
            }
            return null;
        }
    }

    /**
     * Build WordPress update object for transient
     */
    private function buildUpdateObject(array $updateInfo): object
    {
        $update = new \stdClass();
        $update->id = $this->pluginSlug;
        $update->slug = $updateInfo['slug'] ?? $this->pluginSlug;
        $update->plugin = $this->pluginBasename;
        $update->new_version = $updateInfo['version'];
        $update->url = $updateInfo['homepage'] ?? '';
        $update->package = $updateInfo['download_url'] ?? $updateInfo['package'] ?? '';

        if (isset($updateInfo['tested'])) {
            $update->tested = $updateInfo['tested'];
        }

        if (isset($updateInfo['requires'])) {
            $update->requires = $updateInfo['requires'];
        }

        if (isset($updateInfo['requires_php'])) {
            $update->requires_php = $updateInfo['requires_php'];
        }

        if (isset($updateInfo['icons'])) {
            $update->icons = $updateInfo['icons'];
        }

        return $update;
    }

    /**
     * Build plugin info object for plugins_api
     */
    private function buildPluginInfoObject(array $updateInfo): object
    {
        $info = new \stdClass();
        $info->name = $updateInfo['name'] ?? 'WC Licensed Product';
        $info->slug = $updateInfo['slug'] ?? $this->pluginSlug;
        $info->version = $updateInfo['version'];
        $info->author = '<a href="https://src.bundespruefstelle.ch/magdev">Marco Graetsch</a>';
        $info->homepage = $updateInfo['homepage'] ?? '';
        $info->requires = $updateInfo['requires'] ?? '6.0';
        $info->tested = $updateInfo['tested'] ?? '';
        $info->requires_php = $updateInfo['requires_php'] ?? '8.3';
        $info->downloaded = 0;
        $info->last_updated = $updateInfo['last_updated'] ?? '';
        $info->download_link = $updateInfo['download_url'] ?? $updateInfo['package'] ?? '';

        // Sections for the modal
        $info->sections = [];

        if (isset($updateInfo['sections']['description'])) {
            $info->sections['description'] = $updateInfo['sections']['description'];
        } else {
            $info->sections['description'] = __(
                'WooCommerce plugin for selling licensed software products with domain-bound license keys.',
                'wc-licensed-product'
            );
        }

        if (isset($updateInfo['sections']['changelog']) || isset($updateInfo['changelog'])) {
            $info->sections['changelog'] = $updateInfo['sections']['changelog'] ?? $updateInfo['changelog'];
        }

        // Banners and icons
        if (isset($updateInfo['banners'])) {
            $info->banners = $updateInfo['banners'];
        }

        if (isset($updateInfo['icons'])) {
            $info->icons = $updateInfo['icons'];
        }

        return $info;
    }

    /**
     * Clear the update cache
     */
    public function clearCache(): void
    {
        delete_transient(self::CACHE_KEY);
    }

    /**
     * Get cache TTL from settings or default
     */
    private function getCacheTtl(): int
    {
        $hours = (int) get_option('wc_licensed_product_update_check_frequency', 12);
        return max(1, $hours) * HOUR_IN_SECONDS;
    }

    /**
     * Get the license server URL from settings
     */
    private function getLicenseServerUrl(): string
    {
        // Check constant override first
        if (defined('WC_LICENSE_UPDATE_CHECK_URL') && WC_LICENSE_UPDATE_CHECK_URL) {
            return WC_LICENSE_UPDATE_CHECK_URL;
        }

        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 current domain from the site URL
     */
    private function getCurrentDomain(): string
    {
        $siteUrl = get_site_url();
        $parsed = parse_url($siteUrl);
        $host = $parsed['host'] ?? 'localhost';

        if (isset($parsed['port'])) {
            $host .= ':' . $parsed['port'];
        }

        return strtolower($host);
    }

    /**
     * Force an immediate update check
     *
     * Useful for admin interfaces where user clicks "Check for updates"
     */
    public function forceUpdateCheck(): ?array
    {
        $this->clearCache();
        return $this->getUpdateInfo(true);
    }
}
