<?php
    /**
     * Update Checker for FleekDash V2
     *
     * This class handles automatic update notifications from Supabase Edge Function.
     * It checks the updates-fleekdash-check endpoint and notifies WordPress
     * when a new version is available.
     *
     * @package FleekDash\Core
     * @since 2.1.0
     */

namespace FleekDash\Core;

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

if (!class_exists(UpdatePreferences::class)) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/UpdatePreferences.php';
}
if (!class_exists(UpdatePayloadFactory::class)) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/UpdatePayloadFactory.php';
}

class UpdateChecker
{
    /**
     * Plugin slug (must match plugin_slug in plugin_versions table)
     */
    private const PLUGIN_SLUG = 'fleekdash';

    /**
     * Plugin file path (fleekdash/fleekdash.php)
     */
    private string $pluginFile;

    /**
     * Current plugin version
     */
    private string $currentVersion;

    /**
     * Supabase URL
     */
    private string $supabaseUrl;

    /**
     * Supabase Anon Key
     */
    private string $supabaseKey;

    private UpdatePayloadFactory $payloadFactory;

    /**
     * Cache key for update data
     */
    private const CACHE_KEY = '_fleekdash_v2_update_data';

    /**
     * Cache duration (1 hour)
     */
    private const CACHE_DURATION = HOUR_IN_SECONDS;

    /**
     * Constructor
     */
    public function __construct(string $pluginFile, string $currentVersion)
    {
        $this->pluginFile = $pluginFile;
        $this->currentVersion = $currentVersion;
        $this->payloadFactory = new UpdatePayloadFactory($pluginFile);

        // Get Supabase credentials from environment or constants
        $this->supabaseUrl = $this->getSupabaseUrl();
        $this->supabaseKey = $this->getSupabaseKey();
    }

    /**
     * Get Supabase URL from constants or environment
     *
     * Priority:
     * 1. FLEEKDASH_SUPABASE_URL constant (defined in fleekdash.php - ALWAYS available in production)
     * 2. SUPABASE_URL environment variable (optional fallback for development/testing)
     *
     * @return string
     */
    private function getSupabaseUrl(): string
    {
        // In production, FLEEKDASH_SUPABASE_URL is always defined in fleekdash.php (line 76)
        // getenv() is only used as fallback for development/testing environments
        if (defined('FLEEKDASH_SUPABASE_URL')) {
            return constant('FLEEKDASH_SUPABASE_URL');
        }

        // Optional fallback for development/testing (not used in production)
        $env_url = getenv('SUPABASE_URL');
        return $env_url !== false ? $env_url : '';
    }

    /**
     * Get Supabase key from constants or environment
     *
     * Priority:
     * 1. FLEEKDASH_SUPABASE_KEY constant (defined in fleekdash.php - ALWAYS available in production)
     * 2. FLEEKDASH_SUPABASE_ANON_KEY constant (legacy backward compatibility)
     * 3. SUPABASE_ANON_KEY environment variable (optional fallback for development/testing)
     *
     * @return string
     */
    private function getSupabaseKey(): string
    {
        // In production, FLEEKDASH_SUPABASE_KEY is always defined in fleekdash.php (line 77)
        // This avoids linter warnings for constants that may not exist
        if (defined('FLEEKDASH_SUPABASE_KEY')) {
            return constant('FLEEKDASH_SUPABASE_KEY');
        }

        // Legacy backward compatibility (deprecated - use FLEEKDASH_SUPABASE_KEY instead)
        if (defined('FLEEKDASH_SUPABASE_ANON_KEY')) {
            return constant('FLEEKDASH_SUPABASE_ANON_KEY');
        }

        // Optional fallback for development/testing (not used in production)
        $env_key = getenv('SUPABASE_ANON_KEY');
        return $env_key !== false ? $env_key : '';
    }

    /**
     * Initialize update checker hooks
     */
    public function init()
    {
        // Get Supabase credentials (re-check in case they were set after constructor)
        $this->supabaseUrl = $this->getSupabaseUrl();
        $this->supabaseKey = $this->getSupabaseKey();

        // Only check for updates if Supabase is configured
        if (empty($this->supabaseUrl) || empty($this->supabaseKey)) {
            return;
        }

        // Hook into WordPress update check (must run before WordPress checks)
        \add_filter('pre_set_site_transient_update_plugins', [$this, 'checkForUpdates'], 10, 2);

        // Also hook into site transient (for multisite and Update Core page)
        \add_filter('site_transient_update_plugins', [$this, 'injectUpdateData'], 10, 1);

        // Clear cache when requested
        \add_action('admin_init', [$this, 'maybeClearCache']);

        // Add update information to plugin details
        \add_filter('plugins_api', [$this, 'pluginInfo'], 10, 3);

        // Force check on admin pages (more reliable than waiting for cron)
        \add_action('admin_init', [$this, 'maybeCheckOnAdmin'], 5);
    }

    /**
     * Inject update data into site transient (for Update Core page)
     *
     * @param object $transient Update plugins transient
     * @return object Modified transient
     */
    public function injectUpdateData($transient)
    {
        if ($this->canCheckTransient($transient)) {
            $updateData = $this->getUpdateData();
            $updateAction = UpdatePreferences::resolveUpdateVisibilityAction($this->currentVersion, $updateData);
            if ($updateData instanceof \stdClass && $updateAction === UpdatePreferences::UPDATE_ACTION_RESPONSE) {
                if (!isset($transient->response)) {
                    $transient->response = [];
                }
                $transient->response[$this->pluginFile] = $this->payloadFactory->ensureIcons($updateData);
            }
        }

        return $transient;
    }

    /**
     * Check for updates from Supabase
     *
     * @param object $transient Update plugins transient
     * @param bool   $forced    When true, WordPress forces a refresh — bypass our cached Edge response
     * @return object Modified transient
     */
    public function checkForUpdates($transient, $forced = false)
    {
        if ($this->canCheckTransient($transient)) {
            if ($forced) {
                \delete_transient($this->getCacheKey());
            }

            $updateData = $this->getUpdateData();
            if ($updateData instanceof \stdClass) {
                $this->applyUpdateDataToTransient($transient, $updateData);
            }
        }

        return $transient;
    }

    /**
     * Get update data from Supabase (with caching)
     *
     * @return object|null Update data object or null if no update
     */
    private function getUpdateData(): ?\stdClass
    {
        // Check cache first
        $cached_data = \get_transient($this->getCacheKey());
        if ($cached_data instanceof \stdClass) {
            return $this->payloadFactory->ensureIcons($cached_data);
        }

        // Fetch from Supabase
        $data = $this->fetchFromSupabase();

        if ($data) {
            $data = $this->payloadFactory->ensureIcons($data);
            // Cache for 1 hour
            \set_transient($this->getCacheKey(), $data, self::CACHE_DURATION);
        }

        return $data;
    }

    /**
     * Fetch latest version from Supabase Edge Function
     *
     * @return object|null Update data object or null on failure
     */
    private function fetchFromSupabase(): ?\stdClass
    {
        $out = null;
        try {
            $data = $this->getEdgeFunctionDecodedPayload();
            if (is_array($data)) {
                $out = $this->buildAndValidateUpdateFromEdgePayload($data);
            }
        } catch (\Exception $e) {
            fleekdash_log('UpdateChecker: Exception while fetching from Supabase Edge Function: ' . $e->getMessage(), 'error');
        }

        return $out;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function getEdgeFunctionDecodedPayload(): ?array
    {
        $queryArgs = [
            'include_prerelease' => UpdatePreferences::shouldReceivePrereleaseUpdates() ? '1' : '0',
        ];
        $url = \add_query_arg($queryArgs, $this->supabaseUrl . '/functions/v1/updates-fleekdash-check');
        $response = \wp_remote_get($url, [
            'headers' => [
                'apikey' => $this->supabaseKey,
                'Authorization' => 'Bearer ' . $this->supabaseKey,
                'Content-Type' => 'application/json',
            ],
            'timeout' => 15,
        ]);

        $decoded = null;
        if (\is_wp_error($response)) {
            fleekdash_log('UpdateChecker: Failed to fetch from Supabase Edge Function: ' . $response->get_error_message(), 'error');
        } else {
            $status_code = \wp_remote_retrieve_response_code($response);
            if ($status_code !== 200) {
                fleekdash_log('UpdateChecker: Edge Function returned status code ' . $status_code, 'error');
            } else {
                $body = \wp_remote_retrieve_body($response);
                $data = json_decode($body, true);
                if (empty($data) || !is_array($data)) {
                    fleekdash_log('UpdateChecker: Invalid response format from Edge Function', 'warning');
                } else {
                    $decoded = $data;
                }
            }
        }

        return $decoded;
    }

    /**
     * @param array<string, mixed> $data
     */
    private function buildAndValidateUpdateFromEdgePayload(array $data): ?\stdClass
    {
        return $this->payloadFactory->buildValidatedUpdate($data);
    }

    private function getCacheKey(): string
    {
        $mode = UpdatePreferences::shouldReceivePrereleaseUpdates() ? 'prerelease' : 'stable';

        return self::CACHE_KEY . '_' . $mode;
    }

    public static function clearCachedUpdatePayloads(): void
    {
        \delete_transient(self::CACHE_KEY . '_stable');
        \delete_transient(self::CACHE_KEY . '_prerelease');
    }

    private function canCheckTransient(mixed $transient): bool
    {
        return is_object($transient)
            && !empty($transient->checked)
            && $this->supabaseUrl !== ''
            && $this->supabaseKey !== '';
    }

    private function applyUpdateDataToTransient(object $transient, \stdClass $updateData): void
    {
        $updateAction = UpdatePreferences::resolveUpdateVisibilityAction($this->currentVersion, $updateData);
        if ($updateAction === UpdatePreferences::UPDATE_ACTION_NO_UPDATE) {
            $transient->no_update[$this->pluginFile] = $updateData;
            return;
        }

        if ($updateAction === UpdatePreferences::UPDATE_ACTION_RESPONSE) {
            if (!isset($transient->response)) {
                $transient->response = [];
            }
            $transient->response[$this->pluginFile] = $this->payloadFactory->ensureIcons($updateData);
        }
    }

    /**
     * Provide plugin information for details popup
     *
     * @param false|object|array $result The result object or array
     * @param string $action The type of information being requested
     * @param object $args Plugin API arguments
     * @return false|object|array Modified result
     */
    public function pluginInfo($result, $action, $args)
    {
        $out = $result;
        $handles = $action === 'plugin_information'
            && !empty($args->slug)
            && $args->slug === self::PLUGIN_SLUG;

        if ($handles) {
            $update_data = $this->getUpdateData();
            $updateAction = UpdatePreferences::resolveUpdateVisibilityAction($this->currentVersion, $update_data);
            if ($update_data && $updateAction === UpdatePreferences::UPDATE_ACTION_RESPONSE) {
                $out = $this->payloadFactory->buildPluginInfo($update_data);
            }
        }

        return $out;
    }

    /**
     * Maybe check for updates on admin pages (more reliable than cron)
     * Only checks if cache is expired
     */
    public function maybeCheckOnAdmin()
    {
        // Only check on plugin pages or dashboard
        $screen = get_current_screen();
        if (!$screen || (!in_array($screen->id, ['plugins', 'dashboard', 'update-core']) && strpos($screen->id, 'fleekdash') === false)) {
            return;
        }

        // Check if cache is expired
        $cached_data = \get_transient($this->getCacheKey());
        if ($cached_data === false) {
            // Cache expired, trigger update check
            \delete_site_transient('update_plugins');
            \wp_update_plugins();
        }
    }

    /**
     * Maybe clear cache if fleekdash_force_update_check query parameter is set
     */
    public function maybeClearCache()
    {
        if (isset($_GET['fleekdash_force_update_check']) && \current_user_can('manage_options')) {
            self::clearCachedUpdatePayloads();
            \delete_site_transient('update_plugins');

            // Force WordPress to check for updates
            \wp_update_plugins();

            // Redirect to remove query parameter
            wp_safe_redirect(remove_query_arg('fleekdash_force_update_check'));
            exit;
        }
    }

    /**
     * Force check for updates (clears cache and checks immediately)
     *
     * @return object|null Update data or null
     */
    public function forceCheck()
    {
        self::clearCachedUpdatePayloads();
        \delete_site_transient('update_plugins');
        return $this->getUpdateData();
    }
}
