<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-update-checker.php
// Description: Checks Core for plugin updates and injects into WP update transient - NUCLEAR+++
// Last Edited: 2026-05-24
// ==================================================
namespace AutoMarketerAI;

defined('ABSPATH') || exit;

class UpdateChecker
{
    private const CRON_HOOK     = 'amai_update_check';
    private const INTERVAL      = 'amai_twelve_hours';
    private const TRANSIENT_KEY = 'amai_update_cache';
    private const CACHE_TTL     = 3600; // 1 hour in seconds
    private const PLUGIN_SLUG   = 'automarketer-ai/automarketer-ai.php';
    private const CORE_ENDPOINT = 'https://core.automarketer-ai.com/api/plugin/update-check';

    public static function register(): void
    {
        add_filter('cron_schedules',                        [self::class, 'addCronInterval']);
        add_action(self::CRON_HOOK,                         [self::class, 'runCheck']);
        add_filter('pre_set_site_transient_update_plugins', [self::class, 'injectUpdate']);
        add_filter('site_transient_update_plugins',         [self::class, 'filterUpdateTransient']);
        add_filter('plugins_api',                           [self::class, 'injectPluginInfo'], 10, 3);
        add_action('admin_init',                            [self::class, 'nudgeUpdateCheck']);
        add_action('upgrader_process_complete',             [self::class, 'clearAfterUpdate'], 10, 2);

        if (!wp_next_scheduled(self::CRON_HOOK)) {
            wp_schedule_event(time(), self::INTERVAL, self::CRON_HOOK);
        }
    }

    public static function deregister(): void
    {
        $timestamp = wp_next_scheduled(self::CRON_HOOK);
        if ($timestamp !== false) {
            wp_unschedule_event($timestamp, self::CRON_HOOK);
        }
        delete_transient(self::TRANSIENT_KEY);
    }

    public static function addCronInterval(array $schedules): array
    {
        $schedules[self::INTERVAL] = [
            'interval' => self::CACHE_TTL,
            'display'  => 'Every 12 hours',
        ];
        return $schedules;
    }

    // Called by cron -- fetches Core and caches result.
    public static function runCheck(): void
    {
        $data = self::fetchUpdateData();
        if ($data !== null) {
            set_transient(self::TRANSIENT_KEY, $data, self::CACHE_TTL);
        }
    }

    // Hooked to admin_init -- forces WP to recheck when:
    // 1. Our cache has a newer version than installed, or
    // 2. Plugin is in no_update (meaning WP thinks it is current) -- we may have a stale cache.
    public static function nudgeUpdateCheck(): void
    {
        $wpTransient = get_site_transient('update_plugins');
        if (!is_object($wpTransient)) {
            return;
        }

        // If our cache has a newer version, force WP to recheck.
        $data = get_transient(self::TRANSIENT_KEY);
        if (is_array($data) && !empty($data['version'])) {
            if (version_compare((string) $data['version'], AMAI_VERSION, '>')) {
                if (!isset($wpTransient->response[self::PLUGIN_SLUG])) {
                    delete_site_transient('update_plugins');
                }
                return;
            }
        }

        // If plugin is stuck in no_update, delete cache and WP transient to force a fresh check.
        if (isset($wpTransient->no_update[self::PLUGIN_SLUG])) {
            $entry = $wpTransient->no_update[self::PLUGIN_SLUG];
            if (isset($entry->new_version) && version_compare((string) $entry->new_version, AMAI_VERSION, '<=')) {
                delete_transient(self::TRANSIENT_KEY);
                delete_site_transient('update_plugins');
            }
        }
        // If plugin is absent from both response and no_update, WP has never checked it.
        // Clear cache and WP transient so the next admin_init triggers a fresh check.
        if (
            !isset($wpTransient->response[self::PLUGIN_SLUG]) &&
            !isset($wpTransient->no_update[self::PLUGIN_SLUG])
        ) {
            delete_transient(self::TRANSIENT_KEY);
            delete_site_transient('update_plugins');
        }
    }

    // Hooked to upgrader_process_complete -- clears our transient and WP update transient after update.
    public static function clearAfterUpdate(mixed $upgrader, array $options): void
    {
        if (
            isset($options['action'], $options['type'], $options['plugins']) &&
            $options['action'] === 'update' &&
            $options['type'] === 'plugin' &&
            in_array(self::PLUGIN_SLUG, (array) $options['plugins'], true)
        ) {
            delete_transient(self::TRANSIENT_KEY);
            delete_site_transient('update_plugins');
        }
    }

    // Hooked to site_transient_update_plugins (getter) -- removes stale update entry when versions match.
    public static function filterUpdateTransient(mixed $transient): mixed
    {
        if (!is_object($transient)) {
            return $transient;
        }
        if (!isset($transient->response[self::PLUGIN_SLUG])) {
            return $transient;
        }
        $entry = $transient->response[self::PLUGIN_SLUG];
        if (isset($entry->new_version) && !version_compare((string) $entry->new_version, AMAI_VERSION, '>')) {
            unset($transient->response[self::PLUGIN_SLUG]);
            if (!isset($transient->no_update)) {
                $transient->no_update = [];
            }
            $transient->no_update[self::PLUGIN_SLUG] = (object) [
                'slug'         => 'automarketer-ai',
                'plugin'       => self::PLUGIN_SLUG,
                'new_version'  => AMAI_VERSION,
                'url'          => '',
                'package'      => '',
                'requires'     => '6.0',
                'requires_php' => '8.2',
            ];
        }
        return $transient;
    }

    // Hooked to pre_set_site_transient_update_plugins -- injects update if newer version available.
    public static function injectUpdate(mixed $transient): mixed
    {
        if (!is_object($transient)) {
            return $transient;
        }

        $fromCache = true;
        $data      = get_transient(self::TRANSIENT_KEY);

        if (!is_array($data)) {
            // No cache at all -- fetch live.
            $fromCache = false;
            $data      = self::fetchUpdateData();
            if ($data !== null) {
                set_transient(self::TRANSIENT_KEY, $data, self::CACHE_TTL);
            }
        }

        if (!is_array($data) || empty($data['version'])) {
            return $transient;
        }

        // If cache says version is not newer than what is installed, do a live fetch to confirm.
        // This ensures a client with a stale cache discovers a new release immediately.
        if ($fromCache && !version_compare((string) $data['version'], AMAI_VERSION, '>')) {
            $fresh = self::fetchUpdateData();
            if ($fresh !== null) {
                $data = $fresh;
                set_transient(self::TRANSIENT_KEY, $data, self::CACHE_TTL);
            }
        }

        if (!is_array($data) || empty($data['version'])) {
            return $transient;
        }

        $remoteVersion = (string) $data['version'];

        if (version_compare($remoteVersion, AMAI_VERSION, '>')) {
            $licenseKey  = (string) get_option('automarketer_ai_license_key', '');
            $downloadUrl = rtrim((string) ($data['download_url'] ?? ''), '?&');
            if ($licenseKey !== '') {
                $downloadUrl .= '?license_key=' . rawurlencode($licenseKey);
            }

            $update = (object) [
                'slug'         => 'automarketer-ai',
                'plugin'       => self::PLUGIN_SLUG,
                'new_version'  => $remoteVersion,
                'url'          => (string) ($data['changelog_url'] ?? ''),
                'package'      => $downloadUrl,
                'requires'     => (string) ($data['min_wp'] ?? '6.0'),
                'requires_php' => (string) ($data['min_php'] ?? '8.2'),
            ];

            if (!isset($transient->response)) {
                $transient->response = [];
            }
            $transient->response[self::PLUGIN_SLUG] = $update;
            unset($transient->no_update[self::PLUGIN_SLUG]);
        } else {
            // Version is current -- remove from response and add to no_update to suppress stale notification.
            unset($transient->response[self::PLUGIN_SLUG]);
            if (!isset($transient->no_update)) {
                $transient->no_update = [];
            }
            $transient->no_update[self::PLUGIN_SLUG] = (object) [
                'slug'         => 'automarketer-ai',
                'plugin'       => self::PLUGIN_SLUG,
                'new_version'  => AMAI_VERSION,
                'url'          => '',
                'package'      => '',
                'requires'     => (string) ($data['min_wp'] ?? '6.0'),
                'requires_php' => (string) ($data['min_php'] ?? '8.2'),
            ];
        }

        return $transient;
    }

    // Hooked to plugins_api -- populates the update detail popup.
    public static function injectPluginInfo(mixed $result, string $action, mixed $args): mixed
    {
        if ($action !== 'plugin_information') {
            return $result;
        }

        if (!isset($args->slug) || $args->slug !== 'automarketer-ai') {
            return $result;
        }

        $data = get_transient(self::TRANSIENT_KEY);
        if (!is_array($data) || empty($data['version'])) {
            return $result;
        }

        return (object) [
            'name'         => 'AutoMarketer AI',
            'slug'         => 'automarketer-ai',
            'version'      => (string) $data['version'],
            'requires'     => (string) ($data['min_wp'] ?? '6.0'),
            'requires_php' => (string) ($data['min_php'] ?? '8.2'),
            'author'       => 'Ron Seigel',
            'homepage'     => 'https://automarketer-ai.com',
            'sections'     => [
                'changelog' => '<p>See <a href="' . esc_url((string) ($data['changelog_url'] ?? '')) . '" target="_blank">full changelog</a>.</p>',
            ],
        ];
    }

    // Fetch update data from Core. Returns decoded array or null on failure.
    private static function fetchUpdateData(): ?array
    {
        $response = wp_remote_get(self::CORE_ENDPOINT, [
            'timeout'   => 10,
            'sslverify' => true,
        ]);

        if (is_wp_error($response)) {
            return null;
        }

        $code = wp_remote_retrieve_response_code($response);
        if ($code !== 200) {
            return null;
        }

        $body    = wp_remote_retrieve_body($response);
        $decoded = json_decode($body, true);

        if (!is_array($decoded) || empty($decoded['version'])) {
            return null;
        }

        return $decoded;
    }
}