<?php

defined( 'ABSPATH' ) || exit;

class ZACLS_Install {

	const DB_VERSION_OPTION = 'zacls_db_version';

	public static function activate() {
		self::maybe_upgrade();
		self::set_default_options();
		self::create_upload_dir();
		set_transient( 'zacls_onboarding_redirect', true, 30 );

		if ( ZACLS_Multisite::is_network_active() ) {
			ZACLS_Multisite::network_activate_tables();
		}
	}

	public static function deactivate() {
		wp_clear_scheduled_hook( 'zacls_daily_cleanup' );
		wp_clear_scheduled_hook( 'zacls_process_queue' );
		delete_transient( 'zacls_onboarding_redirect' );
	}

	public static function maybe_upgrade() {
		$current_version   = get_option( self::DB_VERSION_OPTION, '0.0.0' );
		$needs_schema_sync = false;
		$needs_column_fix  = false;

		if ( version_compare( $current_version, ZACLS_DB_VERSION, '<' ) ) {
			$needs_schema_sync = true;
		} else {
			$needs_schema_sync = self::tables_missing();
			$needs_column_fix  = self::columns_missing();
		}

		if ( $needs_schema_sync || $needs_column_fix ) {
			self::create_tables();
			self::run_migrations( $current_version );

			if ( $needs_column_fix && version_compare( $current_version, '1.1.0', '>=' ) ) {
				self::migrate_to_1_1_0();
			}

			update_option( self::DB_VERSION_OPTION, ZACLS_DB_VERSION );
		}
	}

	private static function columns_missing() {
		global $wpdb;
		$required = array( 'country_code', 'device_id', 'session_id' );
		$columns  = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_login_attempts", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( ! is_array( $columns ) ) {
			return true;
		}
		$missing = array_diff( $required, $columns );
		if ( ! empty( $missing ) ) {
			return true;
		}

		$webauthn_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_webauthn_credentials", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $webauthn_columns ) ) {
			$webauthn_missing = array_diff( array( 'sign_count', 'aaguid', 'transports' ), $webauthn_columns );
			if ( ! empty( $webauthn_missing ) ) {
				return true;
			}
		}

		$code_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_2fa_codes", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $code_columns ) && ! in_array( 'attempt_count', $code_columns, true ) ) {
			return true;
		}

		return false;
	}

	public static function tables_missing() {
		global $wpdb;
		$required = array(
			'zacls_login_attempts',
			'zacls_blocked_ips',
			'zacls_2fa_codes',
			'zacls_2fa_status',
			'zacls_sessions',
			'zacls_device_fingerprints',
			'zacls_lockdown',
			'zacls_audit_log',
			'zacls_webauthn_credentials',
			'zacls_webauthn_challenges',
		);

		foreach ( $required as $table ) {
			$table_name = $wpdb->prefix . $table;
			$exists     = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			if ( $exists !== $table_name ) {
				return true;
			}
		}

		return false;
	}

	private static function run_migrations( $from_version ) {
		if ( version_compare( $from_version, '1.1.0', '<' ) ) {
			self::migrate_to_1_1_0();
		}
		if ( version_compare( $from_version, '2.0.0', '<' ) ) {
			self::migrate_to_2_0_0();
		}
		if ( version_compare( $from_version, '2.2.0', '<' ) ) {
			self::migrate_to_6_0_1();
		}
	}

	private static function migrate_to_1_1_0() {
		global $wpdb;

		$login_attempts_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_login_attempts", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $login_attempts_columns ) ) {
			$missing = array_diff( array( 'country_code', 'device_id', 'session_id' ), $login_attempts_columns );
			if ( ! empty( $missing ) ) {
				$alter_parts = array();
				$last_col    = 'user_agent';
				foreach ( $missing as $col ) {
					if ( 'country_code' === $col ) {
						$alter_parts[] = "ADD COLUMN country_code VARCHAR(5) DEFAULT '' AFTER user_agent";
						$last_col      = 'country_code';
					} elseif ( 'device_id' === $col ) {
						$alter_parts[] = "ADD COLUMN device_id VARCHAR(64) DEFAULT '' AFTER {$last_col}";
						$last_col      = 'device_id';
					} elseif ( 'session_id' === $col ) {
						$alter_parts[] = "ADD COLUMN session_id VARCHAR(64) DEFAULT '' AFTER {$last_col}";
					}
				}
				if ( ! empty( $alter_parts ) ) {
					$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_login_attempts " . implode( ', ', $alter_parts ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
				}
			}
		}

		$blocked_ips_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_blocked_ips", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $blocked_ips_columns ) && ! in_array( 'country_code', $blocked_ips_columns, true ) ) {
			$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_blocked_ips ADD COLUMN country_code VARCHAR(5) DEFAULT '' AFTER reason" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
		}

		$fingerprint_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_device_fingerprints", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $fingerprint_columns ) && in_array( 'fingerprint', $fingerprint_columns, true ) ) {
			$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_device_fingerprints DROP COLUMN fingerprint" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
		}

		self::maybe_add_index( 'zacls_login_attempts', 'idx_status_time', '(status, attempt_time)' );
		self::maybe_add_index( 'zacls_blocked_ips', 'idx_blocked_at', '(blocked_at)' );
		self::maybe_add_index( 'zacls_2fa_status', 'idx_enabled', '(enabled)' );
		self::maybe_add_index( 'zacls_2fa_status', 'idx_updated_at', '(updated_at)' );
		self::maybe_add_index( 'zacls_2fa_codes', 'idx_user_code_expires', '(user_id, code, used, expires_at)' );
		self::maybe_add_index( 'zacls_sessions', 'idx_expires_at', '(expires_at)' );
		self::maybe_add_index( 'zacls_device_fingerprints', 'idx_last_seen', '(last_seen)' );
		self::maybe_add_index( 'zacls_audit_log', 'idx_action_created', '(action, created_at)' );
	}

	private static function migrate_to_2_0_0() {
		global $wpdb;

		$charset_collate = $wpdb->get_charset_collate();

		$new_tables = array(
			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_webauthn_credentials (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL,
				credential_id VARCHAR(255) NOT NULL,
				public_key TEXT NOT NULL,
				credential_type VARCHAR(50) DEFAULT 'public-key',
				name VARCHAR(255) DEFAULT '',
				sign_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
				aaguid VARCHAR(64) DEFAULT '',
				transports VARCHAR(255) DEFAULT '',
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				last_used DATETIME DEFAULT NULL,
				INDEX idx_user_id (user_id),
				INDEX idx_credential_id (credential_id)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_password_history (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL,
				password_hash VARCHAR(255) NOT NULL,
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_user_id (user_id),
				INDEX idx_created_at (created_at)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_risk_scores (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED DEFAULT 0,
				ip VARCHAR(45) NOT NULL,
				score INT NOT NULL DEFAULT 0,
				level VARCHAR(20) NOT NULL DEFAULT 'low',
				factors TEXT DEFAULT '',
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_ip (ip),
				INDEX idx_user_id (user_id),
				INDEX idx_created_at (created_at),
				INDEX idx_level (level)
			) $charset_collate;",
		);

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		foreach ( $new_tables as $sql ) {
			dbDelta( $sql );
		}

		self::maybe_add_index( 'zacls_login_attempts', 'idx_ip_time', '(ip, attempt_time)' );
		self::maybe_add_index( 'zacls_sessions', 'idx_user_active', '(user_id, is_active)' );
	}

	private static function migrate_to_6_0_1() {
		global $wpdb;

		$webauthn_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_webauthn_credentials", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $webauthn_columns ) ) {
			$missing = array_diff( array( 'sign_count', 'aaguid', 'transports' ), $webauthn_columns );
			if ( in_array( 'sign_count', $missing, true ) ) {
				$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_webauthn_credentials ADD COLUMN sign_count BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER public_key" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
			}
			if ( in_array( 'aaguid', $missing, true ) ) {
				$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_webauthn_credentials ADD COLUMN aaguid VARCHAR(64) DEFAULT '' AFTER sign_count" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
			}
			if ( in_array( 'transports', $missing, true ) ) {
				$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_webauthn_credentials ADD COLUMN transports VARCHAR(255) DEFAULT '' AFTER aaguid" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
			}
		}

		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			"DELETE c1 FROM {$wpdb->prefix}zacls_webauthn_credentials c1
			INNER JOIN {$wpdb->prefix}zacls_webauthn_credentials c2
				ON c1.credential_id = c2.credential_id AND c1.id > c2.id"
		);

		self::maybe_add_index( 'zacls_webauthn_credentials', 'uk_credential_id', '(credential_id)', true );

		$charset_collate = $wpdb->get_charset_collate();

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange
			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_webauthn_challenges (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
				challenge VARCHAR(128) NOT NULL,
				rp_id VARCHAR(255) NOT NULL,
				type VARCHAR(20) NOT NULL DEFAULT 'authenticate',
				used TINYINT(1) NOT NULL DEFAULT 0,
				expires_at DATETIME NOT NULL,
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				UNIQUE KEY uk_challenge (challenge),
				INDEX idx_user_id (user_id),
				INDEX idx_type (type),
				INDEX idx_expires_at (expires_at)
			) {$charset_collate};"
		);

		$code_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$wpdb->prefix}zacls_2fa_codes", 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( is_array( $code_columns ) ) {
			if ( ! in_array( 'attempt_count', $code_columns, true ) ) {
				$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_2fa_codes ADD COLUMN attempt_count TINYINT NOT NULL DEFAULT 0 AFTER used" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
			}
			if ( in_array( 'code', $code_columns, true ) ) {
				$wpdb->query( "ALTER TABLE {$wpdb->prefix}zacls_2fa_codes MODIFY COLUMN code VARCHAR(64) NOT NULL" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery
			}
		}
	}

	private static function maybe_add_index( $table, $name, $cols, $unique = false ) {
		global $wpdb;
		$safe_table = esc_sql( $table );
		$safe_name  = esc_sql( $name );
		$exists = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare( "SHOW INDEX FROM {$wpdb->prefix}{$safe_table} WHERE Key_name = %s", $name ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery
		);
		if ( ! $exists ) {
			$type = $unique ? 'ADD UNIQUE KEY' : 'ADD INDEX';
			$wpdb->query( "ALTER TABLE {$wpdb->prefix}{$safe_table} {$type} {$safe_name} {$cols}" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
		}
	}

	private static function create_tables() {
		global $wpdb;
		$charset_collate = $wpdb->get_charset_collate();

		$tables = array(
			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_login_attempts (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				ip VARCHAR(45) NOT NULL,
				username VARCHAR(100) DEFAULT '',
				user_id BIGINT UNSIGNED DEFAULT 0,
				status VARCHAR(20) NOT NULL DEFAULT 'failed',
				user_agent TEXT DEFAULT '',
				country_code VARCHAR(5) DEFAULT '',
				device_id VARCHAR(64) DEFAULT '',
				session_id VARCHAR(64) DEFAULT '',
				attempt_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_ip (ip),
				INDEX idx_status (status),
				INDEX idx_attempt_time (attempt_time),
				INDEX idx_user_id (user_id),
				INDEX idx_ip_status_time (ip, status, attempt_time),
				INDEX idx_status_time (status, attempt_time),
				INDEX idx_ip_time (ip, attempt_time)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_blocked_ips (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				ip VARCHAR(45) NOT NULL,
				type VARCHAR(20) NOT NULL DEFAULT 'auto',
				reason VARCHAR(255) DEFAULT '',
				country_code VARCHAR(5) DEFAULT '',
				blocked_by VARCHAR(100) DEFAULT 'system',
				blocked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				expires_at DATETIME DEFAULT NULL,
				UNIQUE KEY uk_ip (ip),
				INDEX idx_type (type),
				INDEX idx_expires_at (expires_at),
				INDEX idx_blocked_at (blocked_at)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_2fa_codes (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL,
				code VARCHAR(64) NOT NULL,
				type VARCHAR(20) NOT NULL DEFAULT 'email',
				expires_at DATETIME NOT NULL,
				used TINYINT(1) NOT NULL DEFAULT 0,
				attempt_count TINYINT NOT NULL DEFAULT 0,
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_user_id (user_id),
				INDEX idx_code (code),
				INDEX idx_expires_at (expires_at),
				INDEX idx_user_code_expires (user_id, code, used, expires_at)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_2fa_status (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL,
				method VARCHAR(20) NOT NULL DEFAULT 'email',
				enabled TINYINT(1) NOT NULL DEFAULT 0,
				secret_key VARCHAR(255) DEFAULT '',
				backup_codes TEXT DEFAULT '',
				updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
				UNIQUE KEY uk_user_id (user_id),
				INDEX idx_enabled (enabled),
				INDEX idx_updated_at (updated_at)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_sessions (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				session_id VARCHAR(64) NOT NULL,
				user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
				ip VARCHAR(45) NOT NULL,
				device_id VARCHAR(64) DEFAULT '',
				user_agent TEXT DEFAULT '',
				login_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				last_activity DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				expires_at DATETIME NOT NULL,
				is_active TINYINT(1) NOT NULL DEFAULT 1,
				UNIQUE KEY uk_session_id (session_id),
				INDEX idx_user_id (user_id),
				INDEX idx_device_id (device_id),
				INDEX idx_is_active (is_active),
				INDEX idx_expires_at (expires_at),
				INDEX idx_user_active (user_id, is_active)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_device_fingerprints (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				device_id VARCHAR(64) NOT NULL,
				user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
				ip VARCHAR(45) DEFAULT '',
				user_agent TEXT DEFAULT '',
				trusted TINYINT(1) NOT NULL DEFAULT 0,
				last_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				first_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				UNIQUE KEY uk_device_id (device_id),
				INDEX idx_user_id (user_id),
				INDEX idx_trusted (trusted),
				INDEX idx_last_seen (last_seen)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_lockdown (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				is_active TINYINT(1) NOT NULL DEFAULT 0,
				activated_by BIGINT UNSIGNED DEFAULT 0,
				activated_at DATETIME DEFAULT NULL,
				deactivated_at DATETIME DEFAULT NULL,
				reason VARCHAR(255) DEFAULT '',
				whitelist_ips TEXT DEFAULT '',
				INDEX idx_is_active (is_active)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_audit_log (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
				action VARCHAR(100) NOT NULL,
				details VARCHAR(500) DEFAULT '',
				ip VARCHAR(45) DEFAULT '',
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_action (action),
				INDEX idx_user_id (user_id),
				INDEX idx_created_at (created_at),
				INDEX idx_action_created (action, created_at)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_webauthn_credentials (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL,
				credential_id VARCHAR(255) NOT NULL,
				public_key TEXT NOT NULL,
				credential_type VARCHAR(50) DEFAULT 'public-key',
				name VARCHAR(255) DEFAULT '',
				sign_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
				aaguid VARCHAR(64) DEFAULT '',
				transports VARCHAR(255) DEFAULT '',
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				last_used DATETIME DEFAULT NULL,
				INDEX idx_user_id (user_id),
				INDEX idx_credential_id (credential_id)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_password_history (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL,
				password_hash VARCHAR(255) NOT NULL,
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_user_id (user_id),
				INDEX idx_created_at (created_at)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_risk_scores (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED DEFAULT 0,
				ip VARCHAR(45) NOT NULL,
				score INT NOT NULL DEFAULT 0,
				level VARCHAR(20) NOT NULL DEFAULT 'low',
				factors TEXT DEFAULT '',
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_ip (ip),
				INDEX idx_user_id (user_id),
				INDEX idx_created_at (created_at),
				INDEX idx_level (level)
			) $charset_collate;",

			"CREATE TABLE IF NOT EXISTS {$wpdb->prefix}zacls_webauthn_challenges (
				id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
				challenge VARCHAR(128) NOT NULL,
				rp_id VARCHAR(255) NOT NULL,
				type VARCHAR(20) NOT NULL DEFAULT 'authenticate',
				used TINYINT(1) NOT NULL DEFAULT 0,
				expires_at DATETIME NOT NULL,
				created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
				UNIQUE KEY uk_challenge (challenge),
				INDEX idx_user_id (user_id),
				INDEX idx_type (type),
				INDEX idx_expires_at (expires_at)
			) $charset_collate;",
		);

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		foreach ( $tables as $sql ) {
			dbDelta( $sql );
		}
	}

	private static function set_default_options() {
		$defaults = array(
			'zacls_2fa_enabled'           => 'no',
			'zacls_2fa_forced_roles'      => array(),
			'zacls_2fa_methods'           => array( 'email' ),
			'zacls_login_limits'          => array(
				'max_attempts' => 5,
				'time_window'  => 15,
				'lockout_time' => 15,
			),
			'zacls_auto_block_threshold'  => 10,
			'zacls_auto_block_duration'   => 1440,
			'zacls_custom_login_slug'     => 'be-login',
			'zacls_block_wp_login'        => 'no',
			'zacls_redirect_unauthorized' => 'homepage',
			'zacls_email_notifications'   => array(
				'failed_login' => 'yes',
				'new_ip_login' => 'yes',
				'ip_blocked'   => 'yes',
			),
			'zacls_admin_email'           => get_option( 'admin_email' ),
			'zacls_onboarding_completed'  => 'no',
			'zacls_flush_rewrite'         => 'no',
			'zacls_password_policy'       => array(
				'min_length'        => 8,
				'require_upper'     => 'yes',
				'require_lower'     => 'yes',
				'require_number'    => 'yes',
				'require_special'   => 'yes',
				'history_count'     => 5,
				'expiration_days'   => 0,
				'block_common'      => 'yes',
			),
			'zacls_emergency_lockdown'    => 'no',
			'zacls_lockdown_whitelist'    => array(),
			'zacls_session_timeout'       => 1440,
			'zacls_session_inactive_limit'=> 30,
			'zacls_device_tracking'       => 'no',
			'zacls_cloudflare_enabled'    => 'no',
			'zacls_trusted_proxy_ips'     => array(),
			'zacls_geoip_enabled'         => 'no',
			'zacls_tor_detection'         => 'no',
			'zacls_report_frequency'      => 'none',
			'zacls_last_report_sent'      => '',
			'zacls_delete_data_on_uninstall' => 'yes',
			'zacls_file_check_last'       => 0,
			'zacls_vuln_scan_last'        => 0,
			'zacls_wizard_applied'        => 'no',
			'zacls_login_logo_image'      => '',
			'zacls_login_logo_url'        => '',
			'zacls_login_logo_title'      => '',
			'zacls_login_bg_color'        => '',
			'zacls_login_form_bg'         => '',
			'zacls_login_text_color'      => '',
			'zacls_login_primary_color'   => '',
			'zacls_login_custom_css'      => '',
			'zacls_login_header_html'     => '',
			'zacls_login_footer_html'     => '',
			'zacls_wpscan_api_token'      => '',
			'zacls_webauthn_enabled'      => 'no',
			'zacls_webauthn_bypasses_2fa' => 'no',
			'zacls_cleanup_frequency'     => 'daily',
			'zacls_cleanup_retention_days'=> 90,
		);

		$admin_only = array(
			'zacls_onboarding_completed',
			'zacls_db_version',
			'zacls_last_report_sent',
			'zacls_report_frequency',
			'zacls_delete_data_on_uninstall',
			'zacls_flush_rewrite',
			'zacls_emergency_lockdown',
			'zacls_lockdown_whitelist',
			'zacls_file_check_last',
			'zacls_vuln_scan_last',
			'zacls_wizard_applied',
			'zacls_background_queue',
			'zacls_file_scan_results',
			'zacls_file_scan_time',
			'zacls_vuln_scan_results',
			'zacls_cleanup_frequency',
			'zacls_cleanup_retention_days',
		);

		foreach ( $defaults as $key => $value ) {
			if ( false === get_option( $key ) ) {
				$autoload = in_array( $key, $admin_only, true ) ? 'no' : 'yes';
				update_option( $key, $value, $autoload );
			}
		}

		if ( ! wp_next_scheduled( 'zacls_daily_cleanup' ) ) {
			wp_schedule_event( time(), 'daily', 'zacls_daily_cleanup' );
		}
	}

	private static function create_upload_dir() {
		$upload_dir = wp_upload_dir();
		$dir        = $upload_dir['basedir'] . '/zacls-logs';
		if ( ! file_exists( $dir ) ) {
			wp_mkdir_p( $dir );
		}

		$index_file = $dir . '/index.php';
		if ( ! file_exists( $index_file ) ) {
			global $wp_filesystem;
			if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
				require_once ABSPATH . 'wp-admin/includes/file.php';
				WP_Filesystem();
			}
			$wp_filesystem->put_contents( $index_file, '<?php // Silence is golden.' );
		}
	}
}
