<?php

declare(strict_types=1);

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

if ( ! current_user_can( 'activate_plugins' ) ) {
	return;
}

$securwp_installer_file = __DIR__ . '/core/Installer.php';
if ( file_exists( $securwp_installer_file ) ) {
	require_once $securwp_installer_file;
}

/**
 * Delete all options in the current blog matching the provided prefixes.
 *
 * @param array<int,string> $prefixes Option prefixes.
 */
function securwp_delete_blog_options_by_prefixes( array $prefixes ): void {
	global $wpdb;

	foreach ( $prefixes as $prefix ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Removes this plugin's option rows during an explicit uninstall cleanup.
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( $prefix ) . '%'
			)
		);
	}
}

/**
 * Drop all SecurWP tables for the current blog.
 *
 * @param array<int,string> $tables Table suffixes without $wpdb->prefix.
 */
function securwp_drop_blog_tables( array $tables ): void {
	global $wpdb;

	foreach ( $tables as $table ) {
		$full_name = $wpdb->prefix . $table;
		if ( $full_name === '' || strlen( $full_name ) > 64 || ! preg_match( '/^[A-Za-z0-9_]+$/', $full_name ) ) {
			continue;
		}

		$table_identifier = '`' . str_replace( '`', '``', $full_name ) . '`';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Drops validated plugin-owned tables during uninstall after explicit capability and uninstall checks.
		$wpdb->query( "DROP TABLE IF EXISTS {$table_identifier}" );
	}
}

/**
 * Remove all scheduled actions and cron events for a hook, including variant args.
 */
function securwp_clear_scheduled_hook_everywhere( string $hook ): void {
	wp_clear_scheduled_hook( $hook );

	if ( function_exists( '_get_cron_array' ) ) {
		$crons = _get_cron_array();
		if ( is_array( $crons ) ) {
			foreach ( $crons as $timestamp => $cron_hooks ) {
				if ( ! isset( $cron_hooks[ $hook ] ) || ! is_array( $cron_hooks[ $hook ] ) ) {
					continue;
				}

				foreach ( $cron_hooks[ $hook ] as $event ) {
					$args = [];
					if ( is_array( $event ) && isset( $event['args'] ) && is_array( $event['args'] ) ) {
						$args = $event['args'];
					}

					wp_unschedule_event( (int) $timestamp, $hook, $args );
				}
			}
		}
	}

	if ( function_exists( 'as_unschedule_all_actions' ) ) {
		as_unschedule_all_actions( $hook );
	}
}

/**
 * Recursively remove a directory and all nested files.
 */
function securwp_delete_directory_recursive( string $path ): void {
	if ( ! is_dir( $path ) ) {
		return;
	}

	$entries = scandir( $path );
	if ( ! is_array( $entries ) ) {
		return;
	}

	foreach ( $entries as $entry ) {
		if ( $entry === '.' || $entry === '..' ) {
			continue;
		}

		$target = $path . '/' . $entry;
		if ( is_dir( $target ) ) {
			securwp_delete_directory_recursive( $target );
			continue;
		}

		if ( file_exists( $target ) ) {
			wp_delete_file( $target );
		}
	}

	securwp_remove_empty_directory( $path );
}

/**
 * Recursively delete a directory's contents while preserving specific filenames.
 *
 * @param string            $path             Directory to clean.
 * @param array<int,string> $preserve_entries Basename entries to keep.
 */
function securwp_delete_directory_contents_except( string $path, array $preserve_entries ): void {
	if ( ! is_dir( $path ) ) {
		return;
	}

	$entries = scandir( $path );
	if ( ! is_array( $entries ) ) {
		return;
	}

	foreach ( $entries as $entry ) {
		if ( $entry === '.' || $entry === '..' || in_array( $entry, $preserve_entries, true ) ) {
			continue;
		}

		$target = $path . '/' . $entry;
		if ( is_dir( $target ) ) {
			securwp_delete_directory_recursive( $target );
			continue;
		}

		if ( file_exists( $target ) ) {
			wp_delete_file( $target );
		}
	}
}

/**
 * Remove a SecurWP-managed config block from a root config file.
 */
function securwp_cleanup_root_config_file( string $path, string $begin_marker, string $end_marker, string $managed_directive_pattern ): void {
	if ( ! is_file( $path ) || ! wp_is_writable( $path ) ) {
		return;
	}

	$contents = @file_get_contents( $path );
	if ( $contents === false ) {
		return;
	}

	$pattern = '/' . preg_quote( $begin_marker, '/' ) . '.*?' . preg_quote( $end_marker, '/' ) . '\s*/s';
	$cleaned = preg_replace( $pattern, '', $contents );
	$cleaned = is_string( $cleaned ) ? $cleaned : $contents;

	$directive_cleaned = preg_replace( $managed_directive_pattern, '', $cleaned );
	if ( is_string( $directive_cleaned ) ) {
		$cleaned = $directive_cleaned;
	}

	if ( $cleaned === $contents ) {
		return;
	}

	$cleaned = trim( $cleaned );
	if ( $cleaned === '' ) {
		wp_delete_file( $path );
		return;
	}

	@file_put_contents( $path, $cleaned . PHP_EOL, LOCK_EX );
}

/**
 * Remove SecurWP-managed auto_prepend_file directives.
 */
function securwp_remove_extended_waf_directives(): void {
	securwp_cleanup_root_config_file(
		ABSPATH . '.user.ini',
		'; BEGIN SecurWP WAF',
		'; END SecurWP WAF',
		'/^\s*auto_prepend_file\s*=.*securwp-data\/bootstrap\.php.*(?:\R)?/mi'
	);
	securwp_cleanup_root_config_file(
		ABSPATH . '.htaccess',
		'# BEGIN SecurWP WAF',
		'# END SecurWP WAF',
		'/^\s*php_value\s+auto_prepend_file\s+["\']?.*securwp-data\/bootstrap\.php["\']?.*(?:\R)?/mi'
	);
}

/**
 * Read a scalar server value using the standard unslash + sanitize flow.
 */
function securwp_get_server_value( string $key ): string {
	if ( ! isset( $_SERVER[ $key ] ) || is_array( $_SERVER[ $key ] ) ) {
		return '';
	}

	return sanitize_text_field( wp_unslash( (string) $_SERVER[ $key ] ) );
}

/**
 * Leave a no-op bootstrap in place until cached auto_prepend_file values expire.
 */
function securwp_write_safe_waf_bootstrap_stub( string $data_dir ): void {
	if ( ! is_dir( $data_dir ) && ! wp_mkdir_p( $data_dir ) ) {
		return;
	}

	$bootstrap = $data_dir . '/bootstrap.php';
	$index     = $data_dir . '/index.php';
	$htaccess  = $data_dir . '/.htaccess';

	$stub  = "<?php\n";
	$stub .= "declare(strict_types=1);\n";
	$stub .= "// Left in place after SecurWP uninstall to avoid fatal errors while PHP-FPM refreshes cached auto_prepend_file values.\n";
	$stub .= "return;\n";

	@file_put_contents( $bootstrap, $stub, LOCK_EX );

	if ( ! is_file( $index ) ) {
		@file_put_contents( $index, "<?php\n// Silence is golden.\n", LOCK_EX );
	}

	if ( ! is_file( $htaccess ) ) {
		$server_software = strtolower( securwp_get_server_value( 'SERVER_SOFTWARE' ) );
		$is_nginx        = false !== strpos( $server_software, 'nginx' );
		if ( ! $is_nginx ) {
			@file_put_contents(
				$htaccess,
				"<IfModule mod_authz_core.c>\nRequire all denied\n</IfModule>\n<IfModule mod_litespeed.c>\nOrder Deny,Allow\nDeny from all\n</IfModule>\n<IfModule !mod_authz_core.c>\n  <IfModule !mod_litespeed.c>\n    Order Deny,Allow\n    Deny from all\n  </IfModule>\n</IfModule>\n",
				LOCK_EX
			);
		}
	}
}

/**
 * Remove server-level WAF directives while keeping the bootstrap stub in place.
 */
function securwp_cleanup_extended_waf_artifacts( string $data_dir ): void {
	securwp_remove_extended_waf_directives();
	securwp_write_safe_waf_bootstrap_stub( $data_dir );
	securwp_delete_directory_contents_except( $data_dir, [ 'bootstrap.php', 'index.php', '.htaccess' ] );
}

/**
 * Remove an empty directory using the WordPress filesystem layer.
 */
function securwp_remove_empty_directory( string $path ): void {
	if ( ! function_exists( 'WP_Filesystem' ) ) {
		require_once ABSPATH . 'wp-admin/includes/file.php';
	}

	WP_Filesystem();
	global $wp_filesystem;

	if ( is_object( $wp_filesystem ) && method_exists( $wp_filesystem, 'rmdir' ) ) {
		$wp_filesystem->rmdir( $path, false );
	}
}

/**
 * Clean up all blog-scoped SecurWP storage for the current blog.
 *
 * @param array<int,string> $option_prefixes Option prefixes in wp_options.
 * @param array<int,string> $tables          Table suffixes without $wpdb->prefix.
 */
function securwp_cleanup_current_blog( array $option_prefixes, array $tables ): void {
	delete_option( 'swp_adaptive_batch_size' );
	securwp_delete_blog_options_by_prefixes( $option_prefixes );
	securwp_drop_blog_tables( $tables );
}

$securwp_blog_option_prefixes = [
	'securwp_',
	'_transient_securwp_',
	'_transient_timeout_securwp_',
];

$securwp_network_option_prefixes = [
	'securwp_',
	'_site_transient_securwp_',
	'_site_transient_timeout_securwp_',
];

$securwp_tables = [
	'securwp_scan_queue',
	'securwp_commands',
	'securwp_audit_log',
	'securwp_file_states',
	'securwp_cloud_jobs',
	'securwp_file_index',
	'securwp_hash_cache',
	'securwp_discovery_state',
	'securwp_discovery_stack',
	'securwp_issues',
	'securwp_issue_instances',
	'securwp_ignored_issues',
	'securwp_quarantine_items',
	'securwp_scan_runs',
	'securwp_scan_status',
	'securwp_settings',
	'securwp_request_nonces',
	'securwp_two_factor',
	'securwp_two_factor_recovery_codes',
	'securwp_two_factor_sessions',
	'securwp_login_attempts',
	'securwp_rate_limits',
	'securwp_blocklist',
	'securwp_firewall_events',
];

$securwp_cron_hooks = class_exists( '\SecurWP\Core\Installer' )
	? \SecurWP\Core\Installer::scheduled_hooks()
	: [
		'securwp_daily_scan_cron',
		'securwp_weekly_report_cron',
		'securwp_telemetry_heartbeat',
		'securwp_daily_event',
		'securwp_state_push',
		'securwp_keepalive',
		'securwp_state_dispatch',
		'securwp_state_dispatch_low',
		'securwp_state_notification_dispatch',
		'securwp_state_audit_dispatch',
		'securwp_scheduled_scan_job',
		'securwp_scan_cron_check',
		'securwp_scan_worker_fallback',
		'securwp_scanner_watchdog',
		'securwp_blocklist_sync',
		'securwp_geoip_update',
		'securwp_login_attempt_cleanup',
		'securwp_rate_limit_cleanup',
		'securwp_firewall_lockout_cleanup',
		'securwp_audit_log_cleanup',
		'securwp_db_migration_runner',
		'securwp_quarantine_purge',
		'securwp_firewall_log_cleanup',
		'securwp_firewall_waf_log_ingest',
		'securwp_email_digest',
		'securwp_fw_block_digest_send',
		'securwp_webhook_retry',
		'securwp_metrics_push',
		'securwp_cf_reconcile',
		'securwp_vulnerability_refresh',
		'securwp_command_execute',
		'securwp_server_rule_reconcile',
		'securwp_server_rule_maintenance',
		'securwp_server_rule_verify',
		'securwp_user_nicename_remediation',
	];

securwp_cleanup_current_blog( $securwp_blog_option_prefixes, $securwp_tables );

if ( is_multisite() ) {
	global $wpdb;

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Enumerates multisite blogs once during uninstall cleanup.
	$securwp_blog_ids = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs}" );
	if ( is_array( $securwp_blog_ids ) ) {
		foreach ( $securwp_blog_ids as $securwp_blog_id ) {
			$blog_id = (int) $securwp_blog_id;
			if ( $blog_id <= 0 || $blog_id === (int) get_current_blog_id() ) {
				continue;
			}

			switch_to_blog( $blog_id );
			securwp_cleanup_current_blog( $securwp_blog_option_prefixes, $securwp_tables );
			restore_current_blog();
		}
	}

	foreach ( $securwp_network_option_prefixes as $securwp_prefix ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Removes this plugin's network option rows during multisite uninstall cleanup.
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s",
				$wpdb->esc_like( $securwp_prefix ) . '%'
			)
		);
	}
}

global $wpdb;

// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Removes this plugin's usermeta rows during uninstall cleanup.
$wpdb->query(
	$wpdb->prepare(
		"DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s",
		$wpdb->esc_like( 'securwp_' ) . '%'
	)
);

foreach ( $securwp_cron_hooks as $securwp_hook ) {
	if ( class_exists( '\SecurWP\Core\Installer' ) ) {
		\SecurWP\Core\Installer::clear_scheduled_hook_everywhere( $securwp_hook );
	} else {
		securwp_clear_scheduled_hook_everywhere( $securwp_hook );
	}
}

$securwp_data_dir = WP_CONTENT_DIR . '/securwp-data';
securwp_cleanup_extended_waf_artifacts( $securwp_data_dir );

$securwp_upload_dir = wp_upload_dir();
if ( is_array( $securwp_upload_dir ) && ! empty( $securwp_upload_dir['basedir'] ) ) {
	$securwp_legacy_log_dir = $securwp_upload_dir['basedir'] . '/securwp-logs';
	securwp_delete_directory_recursive( $securwp_legacy_log_dir );
}
