<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Core;

use Ekesto\CacheAutopilot\Support\SupportLog;
use Ekesto\CacheAutopilot\PluginMetadata;
use Ekesto\CacheAutopilot\AccessControl;

/**
 * Plugin lifecycle orchestration.
 *
 * Handles activation/deactivation and version-based upgrade reconciliation.
 */
final class Lifecycle
{
    private const VERSION_OPTION = 'ekesto_eci_plugin_version';
    private const TIMED_SYNC_REQUIRED_OPTION = 'ekesto_eci_timed_sync_required';
    private const UPGRADE_LOCK_OPTION = 'ekesto_eci_upgrade_lock';
    private const UPGRADE_LOCK_TTL = 30;

    /**
     * Register lifecycle hooks.
     *
     * @param string $pluginFile Main plugin file path.
     * @return void
     */
    public static function register(string $pluginFile): void
    {
        register_activation_hook($pluginFile, [self::class, 'activate']);
        register_deactivation_hook($pluginFile, [self::class, 'deactivate']);
        register_uninstall_hook($pluginFile, [self::class, 'uninstall']);
        add_action('plugins_loaded', [self::class, 'maybeUpgrade'], 1);
    }

    /**
     * Handle plugin activation.
     *
     * @return void
     */
    public static function activate(): void
    {
        self::setVersionAndRequireTimedSync();
    }

    /**
     * Handle plugin deactivation.
     *
     * @return void
     */
    public static function deactivate(): void
    {
        wp_clear_scheduled_hook('ekesto_eci_process_queue');
        delete_transient('ekesto_eci_timed_sync_done');
    }

    /**
     * Handle plugin uninstall.
     *
     * @return void
     */
    public static function uninstall(): void
    {
        SupportLog::purgeAllLogs();
        self::deletePluginOptions();
        self::deletePluginTransients();
        wp_clear_scheduled_hook('ekesto_eci_process_queue');
        self::clearTimedInvalidationSchedules();
    }

    /**
     * Run idempotent version upgrade reconciliation.
     *
     * @return void
     */
    public static function maybeUpgrade(): void
    {
        if (self::isMaintenanceOrSensitiveUpdateRequest()) {
            return;
        }

        if (!self::acquireUpgradeLock()) {
            return;
        }

        try {
            $storedVersion = get_option(self::VERSION_OPTION, '');
            if ((string) $storedVersion === PluginMetadata::VERSION) {
                return;
            }

            self::deleteLegacyTransientState();
            self::setVersionAndRequireTimedSync();
        } finally {
            self::releaseUpgradeLock();
        }
    }

    /**
     * Return whether timed schedule synchronization is required.
     *
     * @return bool
     */
    public static function isTimedSyncRequired(): bool
    {
        return (bool) get_option(self::TIMED_SYNC_REQUIRED_OPTION, false);
    }

    /**
     * Mark timed schedule synchronization complete.
     *
     * @return void
     */
    public static function markTimedSyncComplete(): void
    {
        delete_option(self::TIMED_SYNC_REQUIRED_OPTION);
    }

    /**
     * Persist the current plugin version and require timed sync.
     *
     * @return void
     */
    private static function setVersionAndRequireTimedSync(): void
    {
        update_option(self::VERSION_OPTION, PluginMetadata::VERSION, false);
        update_option(self::TIMED_SYNC_REQUIRED_OPTION, 1, false);
    }

    /**
     * Acquire a short-lived lock to avoid concurrent upgrade runs.
     *
     * @return bool
     */
    private static function acquireUpgradeLock(): bool
    {
        $now = time();
        $current = (int) get_option(self::UPGRADE_LOCK_OPTION, 0);

        if ($current > 0 && ($current + self::UPGRADE_LOCK_TTL) > $now) {
            return false;
        }

        update_option(self::UPGRADE_LOCK_OPTION, $now, false);
        return true;
    }

    /**
     * Release upgrade lock.
     *
     * @return void
     */
    private static function releaseUpgradeLock(): void
    {
        delete_option(self::UPGRADE_LOCK_OPTION);
    }

    /**
     * Remove all plugin-owned options, including shared Cache Autopilot settings.
     *
     * @return void
     */
    private static function deletePluginOptions(): void
    {
        delete_option('ekesto_cache_invalidator');
        delete_option('ekesto_eci_async_queue');
        delete_option(self::VERSION_OPTION);
        delete_option(self::TIMED_SYNC_REQUIRED_OPTION);
        delete_option(self::UPGRADE_LOCK_OPTION);
        delete_option(AccessControl::OPTION_KEY);
        delete_option(SupportLog::OPTION_KEY);
    }

    /**
     * Remove plugin-owned transient state and transient-backed option rows.
     *
     * @return void
     */
    private static function deletePluginTransients(): void
    {
        delete_transient('ekesto_eci_timed_sync_done');
        delete_transient('ekesto_eci_queue_lock');
        delete_transient('ekesto_ca_log_cleanup_last');

        self::deleteOptionRowsByPatterns([
            '_transient_ekesto_eci_acf_relmap_',
            '_transient_timeout_ekesto_eci_acf_relmap_',
            '_transient_ekesto_eci_meta_hash_',
            '_transient_timeout_ekesto_eci_meta_hash_',
            '_transient_ekesto_eci_meta_dedupe_',
            '_transient_timeout_ekesto_eci_meta_dedupe_',
            '_transient_ekesto_eci_meta_seen_',
            '_transient_timeout_ekesto_eci_meta_seen_',
            '_transient_eci_meta_hash_',
            '_transient_timeout_eci_meta_hash_',
            '_transient_eci_meta_dedupe_',
            '_transient_timeout_eci_meta_dedupe_',
            '_transient_eci_meta_seen_',
            '_transient_timeout_eci_meta_seen_',
            '_transient_ekesto_ca_dedupe_',
            '_transient_timeout_ekesto_ca_dedupe_',
        ]);
    }

    /**
     * Delete obsolete short-lived transient rows during version upgrades.
     *
     * @return void
     */
    private static function deleteLegacyTransientState(): void
    {
        self::deleteOptionRowsByPatterns([
            '_transient_eci_meta_hash_',
            '_transient_timeout_eci_meta_hash_',
            '_transient_eci_meta_dedupe_',
            '_transient_timeout_eci_meta_dedupe_',
            '_transient_eci_meta_seen_',
            '_transient_timeout_eci_meta_seen_',
        ]);
    }

    /**
     * Remove timed invalidation cron events.
     *
     * @return void
     */
    private static function clearTimedInvalidationSchedules(): void
    {
        $cron = _get_cron_array();
        if (!is_array($cron)) {
            return;
        }

        foreach ($cron as $timestamp => $hooks) {
            if (!is_array($hooks) || !isset($hooks['ekesto_eci_timed_invalidation_rule'])) {
                continue;
            }

            foreach ($hooks['ekesto_eci_timed_invalidation_rule'] as $scheduled) {
                $args = $scheduled['args'] ?? [];
                wp_unschedule_event($timestamp, 'ekesto_eci_timed_invalidation_rule', $args);
            }
        }
    }

    /**
     * Delete option table rows matching the provided LIKE prefixes.
     *
     * @param string[] $prefixes Option-name prefixes before the SQL wildcard.
     * @return void
     */
    private static function deleteOptionRowsByPatterns(array $prefixes): void
    {
        global $wpdb;

        foreach ($prefixes as $prefix) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Uninstall cleanup must remove plugin-owned transient rows directly and must not use stale cache state.
            $wpdb->query(
                $wpdb->prepare(
                    'DELETE FROM %i WHERE option_name LIKE %s',
                    $wpdb->options,
                    $wpdb->esc_like($prefix) . '%'
                )
            );
        }
    }

    /**
     * Avoid upgrade writes during maintenance and sensitive installer/update requests.
     *
     * @return bool
     */
    private static function isMaintenanceOrSensitiveUpdateRequest(): bool
    {
        if (file_exists(ABSPATH . '.maintenance') || wp_is_maintenance_mode()) {
            return true;
        }

        if (defined('WP_INSTALLING') && WP_INSTALLING) {
            return true;
        }

        if (!is_admin()) {
            return false;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only classification of installer/update requests to avoid upgrade writes.
        $page = isset($_REQUEST['page']) ? sanitize_key(wp_unslash((string) $_REQUEST['page'])) : '';
        if ($page === 'update-core') {
            return true;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only classification of installer/update requests to avoid upgrade writes.
        $action = isset($_REQUEST['action']) ? sanitize_key(wp_unslash((string) $_REQUEST['action'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only classification of installer/update requests to avoid upgrade writes.
        $action2 = isset($_REQUEST['action2']) ? sanitize_key(wp_unslash((string) $_REQUEST['action2'])) : '';

        foreach (array_filter([$action, $action2]) as $candidate) {
            if (in_array($candidate, [
                'update',
                'update-plugin',
                'update-selected',
                'upgrade-plugin',
                'upload-plugin',
                'install-plugin',
                'activate-plugin',
            ], true)) {
                return true;
            }
        }

        return false;
    }
}
