<?php
/**
 * Custom Auth Suite™ — uninstall
 *
 * Executed only when WordPress uninstalls/deletes the plugin.
 *
 * IMPORTANT:
 * uninstall.php runs standalone. Do not rely on plugin bootstrap,
 * plugin constants, Composer, module helpers or admin helpers here.
 */

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


/*
 * CAS uninstall owner.
 *
 * This file runs only during plugin uninstall and intentionally performs direct
 * cleanup of CUSTAUSU-owned database tables, options, transients, usermeta and log
 * directories. WordPress has no higher-level API for dropping custom plugin
 * tables or deleting namespaced option/usermeta families by prefix.
 *
 * Cleanup is guarded by the keep_data_on_uninstall setting and uses explicit CAS
 * table names only. It must not sweep broad wp_cas_% families because CAS LS, CAS
 * SD and CAS RM may coexist in the same database.
 *
 * Direct database calls here are uninstall-only destructive cleanup routines, not
 * request/runtime queries. Do not copy these disables to normal request, input or
 * rendering code.
 */
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Intentional uninstall-only CAS cleanup.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Uninstall cleanup must inspect/delete current DB state and must not cache.
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table identifiers are explicit CUSTAUSU-owned names validated before use.
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table names are explicit/validated CUSTAUSU-owned identifiers, not user input.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange -- Dropping CUSTAUSU-owned custom tables is expected during uninstall when retention is OFF.
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Uninstall-only usermeta cleanup by exact/prefix key is intentional.


global $wpdb;

if (!isset($wpdb) || !is_object($wpdb)) {
    exit;
}

/**
 * Retention flag.
 *
 * If enabled in CAS settings, keep all plugin data on uninstall.
 * Read the raw option directly because plugin bootstrap is not loaded here.
 */
$custausu_settings = get_option('custausu_global_settings', []);
$custausu_settings = is_array($custausu_settings) ? $custausu_settings : [];

$custausu_uninstall_keep_data_on_uninstall = !empty($custausu_settings['keep_data_on_uninstall']);

if ($custausu_uninstall_keep_data_on_uninstall) {
    return;
}

/**
 * Drop a known CAS table safely.
 *
 * Do NOT use a broad "wp_cas_%" table sweep here:
 * on customauthsuite.com the same DB family may also contain CAS LS / CAS SD
 * tables such as wp_cas_ls_* or wp_cas_sd_*.
 */
if (!function_exists('custausu_uninstall_drop_table')) {
    function custausu_uninstall_drop_table(string $custausu_uninstall_table): void {
        global $wpdb;

        $custausu_uninstall_table = trim($custausu_uninstall_table);

        if ($custausu_uninstall_table === '' || !preg_match('/^[A-Za-z0-9_]+$/', $custausu_uninstall_table)) {
            return;
        }

        $wpdb->query(
            $wpdb->prepare(
                'DROP TABLE IF EXISTS %i',
                $custausu_uninstall_table
            )
        );
    }
}

/**
 * Delete one option from regular and network scope.
 */
if (!function_exists('custausu_uninstall_delete_option')) {
    function custausu_uninstall_delete_option(string $key): void {
        $key = trim($key);

        if ($key === '') {
            return;
        }

        delete_option($key);
        delete_site_option($key);
    }
}

/**
 * Delete options by prefix from the current blog options table.
 */
if (!function_exists('custausu_uninstall_delete_options_by_prefix')) {
    function custausu_uninstall_delete_options_by_prefix(string $prefix): void {
        global $wpdb;

        $prefix = trim($prefix);

        if ($prefix === '') {
            return;
        }

        $like = $wpdb->esc_like($prefix) . '%';

        $names = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
                $like
            )
        );

        if (!is_array($names)) {
            return;
        }

        foreach ($names as $name) {
            custausu_uninstall_delete_option((string) $name);
        }
    }
}

/**
 * Delete site/network options by prefix when the sitemeta table exists.
 */
if (!function_exists('custausu_uninstall_delete_site_options_by_prefix')) {
    function custausu_uninstall_delete_site_options_by_prefix(string $prefix): void {
        global $wpdb;

        $prefix = trim($prefix);

        if ($prefix === '' || empty($wpdb->sitemeta)) {
            return;
        }

        $like = $wpdb->esc_like($prefix) . '%';

        $names = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s",
                $like
            )
        );

        if (!is_array($names)) {
            return;
        }

        foreach ($names as $name) {
            delete_site_option((string) $name);
        }
    }
}

/**
 * Clear scheduled hooks by explicit name and by CAS namespace.
 */
if (!function_exists('custausu_uninstall_clear_cron_hook')) {
    function custausu_uninstall_clear_cron_hook(string $hook): void {
        $hook = trim($hook);

        if ($hook === '') {
            return;
        }

        wp_clear_scheduled_hook($hook);
    }
}

if (!function_exists('custausu_uninstall_clear_cron_hooks_by_prefix')) {
    function custausu_uninstall_clear_cron_hooks_by_prefix(string $prefix): void {
        $prefix = trim($prefix);

        if ($prefix === '' || !function_exists('_get_cron_array')) {
            return;
        }

        $crons = _get_cron_array();

        if (!is_array($crons)) {
            return;
        }

        $hooks = [];

        foreach ($crons as $timestamp => $cron) {
            if (!is_array($cron)) {
                continue;
            }

            foreach ($cron as $hook => $events) {
                if (is_string($hook) && strpos($hook, $prefix) === 0) {
                    $hooks[$hook] = true;
                }
            }
        }

        foreach (array_keys($hooks) as $hook) {
            custausu_uninstall_clear_cron_hook($hook);
        }
    }
}

/**
 * Delete user meta by exact key.
 */
if (!function_exists('custausu_uninstall_delete_usermeta_key')) {
    function custausu_uninstall_delete_usermeta_key(string $custausu_uninstall_meta_key): void {
        global $wpdb;

        $custausu_uninstall_meta_key = trim($custausu_uninstall_meta_key);

        if ($custausu_uninstall_meta_key === '') {
            return;
        }

        $wpdb->delete(
            $wpdb->usermeta,
            ['meta_key' => $custausu_uninstall_meta_key],
            ['%s']
        );
    }
}

/**
 * Delete user meta by prefix.
 */
if (!function_exists('custausu_uninstall_delete_usermeta_by_prefix')) {
    function custausu_uninstall_delete_usermeta_by_prefix(string $prefix): void {
        global $wpdb;

        $prefix = trim($prefix);

        if ($prefix === '') {
            return;
        }

        $like = $wpdb->esc_like($prefix) . '%';

        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s",
                $like
            )
        );
    }
}

/**
 * Recursively delete a directory and its contents.
 */

if (!function_exists('custausu_uninstall_wp_filesystem')) {
    function custausu_uninstall_wp_filesystem() {
        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        WP_Filesystem();

        global $wp_filesystem;

        if (
            is_object($wp_filesystem)
            && class_exists('WP_Filesystem_Base')
            && $wp_filesystem instanceof WP_Filesystem_Base
        ) {
            return $wp_filesystem;
        }

        return null;
    }
}

if (!function_exists('custausu_uninstall_delete_file')) {
    function custausu_uninstall_delete_file(string $path): bool {
        if ($path === '') {
            return false;
        }

        return (bool) wp_delete_file($path);
    }
}

if (!function_exists('custausu_uninstall_remove_empty_dir')) {
    function custausu_uninstall_remove_empty_dir(string $dir): bool {
        if ($dir === '') {
            return false;
        }

        $fs = custausu_uninstall_wp_filesystem();

        if (
            is_object($fs)
            && class_exists('WP_Filesystem_Base')
            && $fs instanceof WP_Filesystem_Base
        ) {
            return (bool) $fs->rmdir($dir, false);
        }

        return false;
    }
}

if (!function_exists('custausu_uninstall_delete_dir')) {
    function custausu_uninstall_delete_dir(string $dir): void {
        $dir = trim($dir);

        if ($dir === '' || !is_dir($dir)) {
            return;
        }

        $items = scandir($dir);

        if (!is_array($items)) {
            return;
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $dir . DIRECTORY_SEPARATOR . $item;

            if (is_dir($path)) {
                custausu_uninstall_delete_dir($path);
            } elseif (is_file($path) || is_link($path)) {
                custausu_uninstall_delete_file($path);
            }
        }

        custausu_uninstall_remove_empty_dir($dir);
    }
}

/**
 * 1) Clear CAS scheduled jobs.
 */
custausu_uninstall_clear_cron_hook('cas_license_client_auto_validate');
custausu_uninstall_clear_cron_hook(
    'custausu_support_diagnostics_client_revoke_retry'
);

/*
 * Do not clear generic cas_* hooks: CAS LS / CAS SD / CAS RM may coexist
 * on the same WordPress install. Keep uninstall cleanup scoped to the CAS
 * client plugin only.
 */
custausu_uninstall_clear_cron_hooks_by_prefix('cas_license_client_');
custausu_uninstall_clear_cron_hooks_by_prefix('cas_doctor_');
custausu_uninstall_clear_cron_hooks_by_prefix('cas_compatibility_');

/**
 * 2) Drop known CAS client custom tables.
 *
 * Explicit list only. Do not broaden to wp_cas_% because CAS LS / CAS SD
 * server plugins use wp_cas_ls_* / wp_cas_sd_* tables.
 */
$custausu_uninstall_tables = [
    $wpdb->prefix . 'cas_email_verifications',
    $wpdb->prefix . 'cas_webauthn_credentials',
    $wpdb->prefix . 'cas_user_devices',
];

foreach ($custausu_uninstall_tables as $custausu_uninstall_table) {
    custausu_uninstall_drop_table($custausu_uninstall_table);
}

/**
 * 3) Delete explicit known CAS options.
 *
 * Kept for readability/audit even though the prefix sweep below also covers
 * these keys.
 */
$custausu_uninstall_options = [
    // DB / package metadata.
    'custausu_db_version',
    'cas_db_version', // Legacy cleanup after prefix migration.
    'custausu_db_migrations',
    'cas_db_migrations', // Legacy cleanup after prefix migration.
    'custausu_db_installed_package_plan',
    'custausu_prefix_option_key_migration_ci2b',

    'cas_db_installed_package_plan', // Legacy cleanup after prefix migration.

    // Main settings.
    'custausu_global_settings',
    'custausu_setup_completed',
    'custausu_setup_completed_at',

    // Wizard / admin state.
    'custausu_wizard_state',

    // Licensing state.
    'cas_license_state',
    'cas_license_client_state',
    'custausu_license_last_valid_state',
    'custausu_license_instance_id',

    // Remote Support Diagnostics state and temporary runtime data.
    'custausu_support_diagnostics_state',
    'cas_support_diagnostics_state', // Legacy cleanup after prefix migration.
    'custausu_support_diagnostics_local_revocations',
    'custausu_support_diagnostics_client_revoke_queue',
    'custausu_support_diagnostics_runtime_logs',
    'cas_support_diagnostics_runtime_log',

    // Email verification backfill state.
    'custausu_email_verifications_existing_users_backfill',

    // Remote messages client cache/catalog version.
    'custausu_remote_messages_catalog_version',

    // Local Free Upgrade-card dismissals.
    'custausu_free_upgrade_card_dismissals',

    // Legacy / WebAuthn fallback.
    'cas_webauthn_cred_map',
];

foreach ($custausu_uninstall_options as $custausu_uninstall_opt) {
    custausu_uninstall_delete_option($custausu_uninstall_opt);
}

/**
 * 4) Delete CAS options/transients by namespace.
 *
 * This is intentionally prefix-based so future CAS client options, Doctor
 * handoff transients and Remote Messages cache entries are not left behind.
 */
/*
 * Prefix cleanup is deliberately scoped. Do not delete generic cas_* keys:
 * CAS LS / CAS SD / CAS RM may use custausu_ls_*, custausu_sd_* and cas_rm_*.
 */
foreach ([
    'cas_db_',
    'cas_wizard_',
    'cas_license_',
    'cas_email_verifications_',
    'cas_remote_messages_',
    'cas_support_diagnostics_',
    'cas_doctor_',
    'cas_compatibility_',
    'cas_health_',
] as $custausu_option_prefix) {
    custausu_uninstall_delete_options_by_prefix($custausu_option_prefix);
    custausu_uninstall_delete_site_options_by_prefix($custausu_option_prefix);

    custausu_uninstall_delete_options_by_prefix('_transient_' . $custausu_option_prefix);
    custausu_uninstall_delete_options_by_prefix('_transient_timeout_' . $custausu_option_prefix);
    custausu_uninstall_delete_options_by_prefix('_site_transient_' . $custausu_option_prefix);
    custausu_uninstall_delete_options_by_prefix('_site_transient_timeout_' . $custausu_option_prefix);

    custausu_uninstall_delete_site_options_by_prefix('_site_transient_' . $custausu_option_prefix);
    custausu_uninstall_delete_site_options_by_prefix('_site_transient_timeout_' . $custausu_option_prefix);
}
unset($custausu_option_prefix);

/**
 * 5) Delete CAS user meta.
 *
 * The cas_ prefix sweep covers current/future namespaced CAS meta.
 * The explicit non-prefixed keys below are historical CAS client keys.
 */
/*
 * Do not delete generic cas_* usermeta: other CAS ecosystem plugins may coexist.
 * Keep client cleanup scoped to known client-side prefixes.
 */
foreach ([
    'cas_license_',
    'cas_webauthn_',
    'cas_passkey_',
    'cas_device_',
    'cas_email_',
    'cas_doctor_',
    'cas_compatibility_',
] as $custausu_usermeta_prefix) {
    custausu_uninstall_delete_usermeta_by_prefix($custausu_usermeta_prefix);
}
unset($custausu_usermeta_prefix);

$custausu_uninstall_user_meta_keys = [
    // Passkey/WebAuthn legacy/current client flags.
    'cas_has_passkey',

    // Email verification legacy keys.
    'email_verified',
    'email_verification_token',
    'email_verification_expires',
    'email_verified_at',

    // Language legacy key.
    'user_lang',
];

foreach ($custausu_uninstall_user_meta_keys as $custausu_uninstall_meta_key) {
    custausu_uninstall_delete_usermeta_key($custausu_uninstall_meta_key);
}

/**
 * 6) Delete CAS local log directory.
 */
$custausu_uninstall_uploads = function_exists('wp_upload_dir') ? wp_upload_dir(null, false) : [];

if (!empty($custausu_uninstall_uploads['basedir']) && is_string($custausu_uninstall_uploads['basedir'])) {
    custausu_uninstall_delete_dir(rtrim($custausu_uninstall_uploads['basedir'], '/') . '/custom-auth-suite/logs');
}



// Prefix migration C18 compatibility cleanup.
$custausu_prefix_migration_option_keys = [
    'cas_admin_classic_notice_dismissals',
    'custausu_admin_classic_notice_dismissals',
    'cas_doctor_assisted_support_preferences',
    'custausu_doctor_assisted_support_preferences',
    'cas_doctor_change_history',
    'custausu_doctor_change_history',
    'cas_doctor_last_snapshot',
    'custausu_doctor_last_snapshot',
    'cas_doctor_plugin_warning_acknowledgements',
    'custausu_doctor_plugin_warning_acknowledgements',
    'cas_email_verifications_existing_users_backfill',
    'custausu_email_verifications_existing_users_backfill',
    'cas_free_upgrade_card_dismissals',
    'custausu_free_upgrade_card_dismissals',
    'cas_license_instance_id',
    'custausu_license_instance_id',
    'cas_license_last_valid_state',
    'custausu_license_last_valid_state',
    'cas_remote_messages_catalog_version',
    'custausu_remote_messages_catalog_version',
    'cas_remote_messages_client_preferences',
    'custausu_remote_messages_client_preferences',
    'cas_remote_messages_dismissals',
    'custausu_remote_messages_dismissals',
    'cas_review_notice_started_at',
    'custausu_review_notice_started_at',
    'cas_setup_completed',
    'custausu_setup_completed',
    'cas_setup_completed_at',
    'custausu_setup_completed_at',
    'cas_support_diagnostics_client_revoke_queue',
    'custausu_support_diagnostics_client_revoke_queue',
    'cas_support_diagnostics_client_revoke_retry',
    'custausu_support_diagnostics_client_revoke_retry',
    'cas_support_diagnostics_local_revocations',
    'custausu_support_diagnostics_local_revocations',
    'cas_support_diagnostics_runtime_logs',
    'custausu_support_diagnostics_runtime_logs',
    'cas_wizard_completed',
    'custausu_wizard_completed',
    'cas_wizard_completed_at',
    'custausu_wizard_completed_at',
    'cas_wizard_state',
    'custausu_wizard_state',
    'custausu_prefix_option_key_migration_c18_v1',
];

foreach ($custausu_prefix_migration_option_keys as $custausu_prefix_migration_option_key) {
    delete_option($custausu_prefix_migration_option_key);
}

unset($custausu_prefix_migration_option_keys, $custausu_prefix_migration_option_key);
