<?php

declare(strict_types=1);

namespace SiteFort\Modules\TwoFactor;

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

use SiteFort\Core\Database\SchemaInspector;
use SiteFort\Includes\Interfaces\Module;
use SiteFort\Includes\Module_Loader;

/**
 * Two-Factor Authentication module controller.
 *
 * Orchestrates the method-aware two-factor authentication system including
 * login interception, REST API routes, profile management, and settings.
 *
 */
class TwoFactor implements Module {

	/**
	 * Module settings.
	 *
	 * @var array
	 */
	private array $settings;

	/**
	 * The REST API handler.
	 *
	 * @var TwoFactorApi
	 */
	private TwoFactorApi $api;

	/**
	 * The login interstitial handler.
	 *
	 * @var LoginInterstitial
	 */
	private LoginInterstitial $login_interstitial;

	/** Option flag marking that the one-time default-role seed has run. */
	private const DEFAULT_ROLES_SEED_OPTION = 'sitefort_twofa_default_roles_seeded';

	/**
	 * Constructor.
	 *
	 * @param string $plugin_name The name of the plugin.
	 * @param string $version     The version of this plugin.
	 */
	public function __construct( string $plugin_name, string $version ) {
		$this->load_settings();
		$this->maybe_seed_default_enforce_roles();
	}

	/**
	 * Initialize the module and register hooks via the loader.
	 *
	 * @param Module_Loader $loader The module loader instance.
	 */
	public function initialize( Module_Loader $loader ): void {
		// Always clean up 2FA data when a user is deleted (even if the module is disabled).
		$loader->add_action( 'delete_user', $this, 'on_delete_user' );

		// Keep the REST and pending-challenge login endpoints available even if
		// policy is disabled mid-session so existing challenge tokens resolve
		// cleanly instead of falling through to an unhandled login action.
		$this->api                = new TwoFactorApi();
		$this->login_interstitial = new LoginInterstitial( $this->settings );
		$loader->add_action( 'rest_api_init', $this->api, 'register_routes' );
		$loader->add_action( 'login_form_sitefort_2fa', $this->login_interstitial, 'render_2fa_form' );

		// Only register interactive enforcement and profile management hooks when
		// the module is enabled. The REST controller still loads so settings can
		// be inspected and administrators can enable the feature.
		if ( empty( $this->settings['enabled'] ) ) {
			return;
		}

		// Register authenticate filter at priority 30 to intercept after
		// WordPress has validated the username/password (priority 20).
		$loader->add_filter( 'authenticate', $this->login_interstitial, 'intercept_authentication', 30, 3 );

		// Show 2FA status on user profile pages.
		$loader->add_action( 'show_user_profile', $this, 'render_profile_section' );
		$loader->add_action( 'edit_user_profile', $this, 'render_profile_section' );

		// Enqueue profile page JS.
		$loader->add_action( 'admin_enqueue_scripts', $this, 'enqueue_profile_script' );

		// Show admin notice for users whose role requires 2FA.
		$loader->add_action( 'admin_notices', $this, 'render_enforcement_notice' );

		// After grace period expires, force users to the profile page until 2FA is set up.
		$loader->add_action( 'admin_init', $this, 'enforce_2fa_setup' );
	}

	/**
	 * Remove 2FA data when a WordPress user is deleted.
	 *
	 * @param int $user_id The ID of the user being deleted.
	 */
		public function on_delete_user( int $user_id ): void {
			global $wpdb;
			$table = $wpdb->prefix . 'sitefort_two_factor';

			RecoveryCodes::clear_codes( $user_id );
			LoginSessionStore::delete_for_user( $user_id );

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Deletes a single row from a plugin-owned table during user cleanup.
			$wpdb->delete( $table, [ 'user_id' => $user_id ], [ '%d' ] );
		}

	/**
	 * After the grace period expires, redirect enforced users to their profile page
	 * until they set up 2FA. Allows profile.php, admin-ajax.php, and REST API requests
	 * so the inline setup flow works.
	 */
	public function enforce_2fa_setup(): void {
		$user = wp_get_current_user();
		if ( ! $user || ! $user->ID ) {
			return;
		}

		// Already has 2FA enabled; nothing to enforce.
		if ( LoginInterstitial::is_2fa_enabled_for_user( $user->ID ) ) {
			return;
		}

		$enforce_roles = $this->settings['enforce_for_roles'] ?? [];
		if ( empty( $enforce_roles ) ) {
			return;
		}

		// Check if user has any enforced role.
		$user_roles = (array) $user->roles;
		if ( empty( array_intersect( $user_roles, $enforce_roles ) ) ) {
			return;
		}

		$grace_days = (int) ( $this->settings['grace_period_days'] ?? 3 );
		$since      = LoginInterstitial::get_enforcement_start( $user->ID );
		$elapsed    = ( time() - $since ) / DAY_IN_SECONDS;

		// Still within grace period; the admin notice handles the reminder.
		if ( $elapsed < $grace_days ) {
			return;
		}

		// Grace period expired. Allow profile page, AJAX/REST (needed for setup),
		// and the SiteFort plugin page so admins can manage enforcement or set up 2FA.
		global $pagenow;
		$allowed_pages = [ 'profile.php', 'admin-ajax.php' ];
		if ( in_array( $pagenow, $allowed_pages, true ) ) {
			return;
		}
		$plugin_page = filter_input(
			INPUT_GET,
			'page',
			FILTER_CALLBACK,
			[
				'options' => 'sanitize_key',
				'flags'   => FILTER_REQUIRE_SCALAR,
			]
		);
		$plugin_page = is_string( $plugin_page ) ? sanitize_key( $plugin_page ) : '';
		if ( $pagenow === 'admin.php' && str_starts_with( $plugin_page, 'sitefort' ) ) {
			return;
		}

		wp_safe_redirect( admin_url( 'profile.php#sitefort-2fa-profile' ) );
		exit;
	}

	/**
	 * Enqueue the profile page 2FA script on profile.php and user-edit.php.
	 *
	 * @param string $hook_suffix The current admin page hook.
	 */
	public function enqueue_profile_script( string $hook_suffix ): void {
		if ( $hook_suffix !== 'profile.php' && $hook_suffix !== 'user-edit.php' ) {
			return;
		}

		$style_path = SITEFORT_PLUGIN_DIR . 'assets/profile-2fa.css';
		wp_enqueue_style(
			'sitefort-profile-2fa',
			SITEFORT_PLUGIN_URL . 'assets/profile-2fa.css',
			[],
			file_exists( $style_path ) ? (string) filemtime( $style_path ) : SITEFORT_VERSION
		);

		$path = SITEFORT_PLUGIN_DIR . 'assets/profile-2fa.js';
		if ( ! file_exists( $path ) ) {
			return;
		}

		wp_enqueue_script(
			'sitefort-profile-2fa',
			SITEFORT_PLUGIN_URL . 'assets/profile-2fa.js',
			[],
			(string) filemtime( $path ),
			true
		);
	}

	/**
	 * Show an admin notice when the current user's role requires 2FA but they haven't set it up.
	 *
	 * During the grace period: dismissible warning with days remaining.
	 * After the grace period: non-dismissible error notice.
	 */
	public function render_enforcement_notice(): void {
		$user = wp_get_current_user();
		if ( ! $user || ! $user->ID ) {
			return;
		}

		// Check if user already has 2FA enabled.
		if ( LoginInterstitial::is_2fa_enabled_for_user( $user->ID ) ) {
			return;
		}

		$enforce_roles = $this->settings['enforce_for_roles'] ?? [];
		if ( empty( $enforce_roles ) ) {
			return;
		}

		// Check if user has any enforced role.
		$user_roles = (array) $user->roles;
		if ( empty( array_intersect( $user_roles, $enforce_roles ) ) ) {
			return;
		}

			$grace_days      = (int) ( $this->settings['grace_period_days'] ?? 3 );
			$since           = LoginInterstitial::get_enforcement_start( $user->ID );
			$elapsed         = ( time() - $since ) / DAY_IN_SECONDS;
			$remaining       = max( 0, (int) ceil( $grace_days - $elapsed ) );
			$remaining_count = absint( $remaining );
			$profile_url = get_edit_profile_url( $user->ID ) . '#sitefort-2fa-profile';

		if ( $remaining > 0 ) :
			// Grace period active: dismissible warning with countdown.
			?>
			<div class="notice notice-warning is-dismissible">
				<p>
					<strong><?php esc_html_e( 'SiteFort:', 'sitefort' ); ?></strong>
					<?php
						echo esc_html(
							sprintf(
								/* translators: %d: number of days remaining */
								_n(
									'Your account role requires two-factor authentication. You have %d day left to set it up.',
									'Your account role requires two-factor authentication. You have %d days left to set it up.',
									$remaining_count,
									'sitefort'
								),
								$remaining_count
							)
						);
					?>
					<a href="<?php echo esc_url( $profile_url ); ?>"><?php esc_html_e( 'Set up 2FA now', 'sitefort' ); ?></a>
				</p>
			</div>
		<?php else : ?>
			<div class="notice notice-error">
				<p>
					<strong><?php esc_html_e( 'SiteFort:', 'sitefort' ); ?></strong>
					<?php esc_html_e( 'Two-factor authentication is required for your account. Please set it up now to continue using the site.', 'sitefort' ); ?>
					<a href="<?php echo esc_url( $profile_url ); ?>"><?php esc_html_e( 'Set up 2FA', 'sitefort' ); ?></a>
				</p>
			</div>
			<?php
		endif;
	}

	/**
	 * Render the 2FA section on the user profile page.
	 *
	 * For own profiles: shows inline setup, management, and disable controls.
	 * For admins viewing other users: shows read-only status.
	 *
	 * @param \WP_User $user The user whose profile is being displayed.
	 */
	public function render_profile_section( \WP_User $user ): void {
		$is_enabled = LoginInterstitial::is_2fa_enabled_for_user( $user->ID );
		$method     = LoginInterstitial::get_user_method( $user->ID );
		$is_own     = get_current_user_id() === $user->ID;

		$method_label = $method === 'email' ? __( 'Email Code', 'sitefort' ) : __( 'Authenticator App', 'sitefort' );

		$allowed = self::sanitize_allowed_methods( $this->settings['allowed_methods'] ?? self::get_default_settings()['allowed_methods'] );
		if ( empty( $allowed ) ) {
			$allowed = self::get_default_settings()['allowed_methods'];
		}
		$allow_totp  = in_array( 'totp', $allowed, true );
		$allow_email = in_array( 'email', $allowed, true );

		$remaining = 0;
		if ( $is_enabled ) {
			$remaining = \SiteFort\Modules\TwoFactor\RecoveryCodes::get_remaining_count( $user->ID );
		}
		?>
		<h2><?php esc_html_e( 'Two-Factor Authentication', 'sitefort' ); ?></h2>
		<div id="sitefort-2fa-profile"
			data-nonce="<?php echo esc_attr( wp_create_nonce( 'wp_rest' ) ); ?>"
			data-api-root="<?php echo esc_attr( rest_url() ); ?>"
			data-user-id="<?php echo esc_attr( (string) $user->ID ); ?>"
			data-is-own="<?php echo $is_own ? '1' : '0'; ?>">
		<table class="form-table" role="presentation">
			<tr id="sp2fa-status-row">
				<th><label><?php esc_html_e( 'Status', 'sitefort' ); ?></label></th>
				<td>
					<?php if ( $is_enabled ) : ?>
						<span class="sp2fa-badge sp2fa-badge-on">
							<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M4 8.5l3 3 5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
							<?php esc_html_e( 'Enabled', 'sitefort' ); ?>
						</span>
						<span class="sp2fa-method-label"><?php echo esc_html( $method_label ); ?></span>
						<?php if ( $remaining > 0 ) : ?>
							<span class="sp2fa-recovery-count"><?php
								echo esc_html(
									sprintf(
										/* translators: %d: remaining recovery codes */
										_n( '%d recovery code remaining', '%d recovery codes remaining', absint( $remaining ), 'sitefort' ),
										absint( $remaining )
									)
								);
							?></span>
						<?php endif; ?>
					<?php else : ?>
						<span class="sp2fa-badge sp2fa-badge-off">
							<?php esc_html_e( 'Not enabled', 'sitefort' ); ?>
						</span>
						<span class="sp2fa-hint"><?php esc_html_e( 'Add an extra layer of security to your account.', 'sitefort' ); ?></span>
					<?php endif; ?>
				</td>
			</tr>

			<?php if ( $is_own ) : ?>

				<?php if ( $is_enabled ) : ?>
					<!-- Enabled state: manage recovery codes, disable -->
					<tr id="sp2fa-actions-row">
						<th><?php esc_html_e( 'Manage', 'sitefort' ); ?></th>
						<td>
							<div class="sp2fa-btn-group">
								<button type="button" class="button" id="sp2fa-btn-regen">
									<svg width="14" height="14" viewBox="0 0 16 16" fill="none" style="vertical-align:-2px;margin-right:4px;"><path d="M2 8a6 6 0 0110.89-3.48M14 8a6 6 0 01-10.89 3.48" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M14 2v4h-4M2 14v-4h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
									<?php esc_html_e( 'Regenerate Recovery Codes', 'sitefort' ); ?>
								</button>
								<button type="button" class="button sp2fa-btn-danger" id="sp2fa-btn-show-disable"><?php esc_html_e( 'Disable 2FA', 'sitefort' ); ?></button>
							</div>
							<div id="sp2fa-new-codes" style="display:none;"></div>
						</td>
					</tr>

					<tr id="sp2fa-regen-row" style="display:none;">
						<th><label for="sp2fa-regen-pw"><?php esc_html_e( 'Confirm', 'sitefort' ); ?></label></th>
						<td>
							<div class="sp2fa-card sp2fa-card-warn" style="max-width:420px;">
								<p style="margin:0 0 10px;font-weight:500;"><?php esc_html_e( 'Generate a new set of recovery codes?', 'sitefort' ); ?></p>
								<p class="description" style="margin-bottom:10px;"><?php esc_html_e( 'Enter your password to replace the current recovery codes. Your existing recovery codes will stop working immediately.', 'sitefort' ); ?></p>
								<input type="password" id="sp2fa-regen-pw" class="regular-text" autocomplete="current-password" placeholder="<?php esc_attr_e( 'Enter your password', 'sitefort' ); ?>" />
								<div id="sp2fa-regen-msg" class="sp2fa-msg" style="display:none;"></div>
								<div class="sp2fa-actions-bar">
									<button type="button" class="button" id="sp2fa-btn-confirm-regen"><?php esc_html_e( 'Generate New Recovery Codes', 'sitefort' ); ?></button>
									<button type="button" class="button" id="sp2fa-btn-cancel-regen"><?php esc_html_e( 'Cancel', 'sitefort' ); ?></button>
								</div>
							</div>
						</td>
					</tr>

					<tr id="sp2fa-disable-row" style="display:none;">
						<th><label for="sp2fa-disable-pw"><?php esc_html_e( 'Confirm', 'sitefort' ); ?></label></th>
						<td>
							<div class="sp2fa-card sp2fa-card-error" style="max-width:420px;">
								<p style="margin:0 0 10px;font-weight:500;"><?php esc_html_e( 'Are you sure you want to disable two-factor authentication?', 'sitefort' ); ?></p>
								<p class="description" style="margin-bottom:10px;"><?php esc_html_e( 'This will make your account less secure. Enter your password to confirm.', 'sitefort' ); ?></p>
								<input type="password" id="sp2fa-disable-pw" class="regular-text" autocomplete="current-password" placeholder="<?php esc_attr_e( 'Enter your password', 'sitefort' ); ?>" />
								<div id="sp2fa-disable-msg" class="sp2fa-msg" style="display:none;"></div>
								<div class="sp2fa-actions-bar">
									<button type="button" class="button sp2fa-btn-danger" id="sp2fa-btn-disable"><?php esc_html_e( 'Disable 2FA', 'sitefort' ); ?></button>
									<button type="button" class="button" id="sp2fa-btn-cancel-disable"><?php esc_html_e( 'Cancel', 'sitefort' ); ?></button>
								</div>
							</div>
						</td>
					</tr>
				<?php else : ?>
					<!-- Not enabled: setup flow -->
					<tr id="sp2fa-actions-row">
						<th></th>
						<td>
							<button type="button" class="button button-primary" id="sp2fa-btn-setup">
								<svg width="14" height="14" viewBox="0 0 16 16" fill="none" style="vertical-align:-2px;margin-right:4px;"><rect x="3" y="1" width="10" height="14" rx="2" stroke="currentColor" stroke-width="1.3"/><circle cx="8" cy="8" r="2.5" stroke="currentColor" stroke-width="1.3"/><path d="M8 5.5V4M8 12v-1.5M10.5 8H12M4 8h1.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
								<?php esc_html_e( 'Set Up Two-Factor Authentication', 'sitefort' ); ?>
							</button>
						</td>
					</tr>

					<tr id="sp2fa-setup-row" style="display:none;">
						<th><?php esc_html_e( 'Choose Method', 'sitefort' ); ?></th>
						<td>
							<p class="sp2fa-step-desc" style="margin-bottom:12px;"><?php esc_html_e( 'Select your preferred verification method:', 'sitefort' ); ?></p>
							<div class="sp2fa-method-cards">
								<?php if ( $allow_totp ) : ?>
								<button type="button" class="sp2fa-method-card" id="sp2fa-choose-totp">
									<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
										<rect x="4" y="2" width="20" height="24" rx="3" stroke="#2563eb" stroke-width="1.5" fill="#eff6ff"/>
										<circle cx="14" cy="14" r="5" stroke="#2563eb" stroke-width="1.5"/>
										<path d="M14 11v3l2 1.5" stroke="#2563eb" stroke-width="1.5" stroke-linecap="round"/>
									</svg>
									<strong><?php esc_html_e( 'Authenticator App', 'sitefort' ); ?></strong>
									<span><?php esc_html_e( 'Google Authenticator, Authy, 1Password, etc.', 'sitefort' ); ?></span>
									<em class="sp2fa-recommended"><?php esc_html_e( 'Recommended', 'sitefort' ); ?></em>
								</button>
								<?php endif; ?>
								<?php if ( $allow_email ) : ?>
								<button type="button" class="sp2fa-method-card" id="sp2fa-choose-email">
									<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
										<rect x="3" y="6" width="22" height="16" rx="2.5" stroke="#6b7280" stroke-width="1.5" fill="#f9fafb"/>
										<path d="M3 8l11 7 11-7" stroke="#6b7280" stroke-width="1.5"/>
									</svg>
									<strong><?php esc_html_e( 'Email Code', 'sitefort' ); ?></strong>
									<span><?php esc_html_e( 'Receive a code via email each time you log in.', 'sitefort' ); ?></span>
								</button>
								<?php endif; ?>
							</div>
							<div class="sp2fa-setup-password">
								<label for="sp2fa-setup-pw"><?php esc_html_e( 'Confirm your password', 'sitefort' ); ?></label>
								<input type="password" id="sp2fa-setup-pw" class="regular-text" autocomplete="current-password" placeholder="<?php esc_attr_e( 'Enter your password to continue', 'sitefort' ); ?>" />
								<p class="description"><?php esc_html_e( 'Password confirmation is required before a new verification method can be enrolled.', 'sitefort' ); ?></p>
								<div id="sp2fa-setup-msg" class="sp2fa-msg" style="display:none;"></div>
							</div>
							<p style="margin-top:10px;">
								<button type="button" class="button" id="sp2fa-btn-cancel"><?php esc_html_e( 'Cancel', 'sitefort' ); ?></button>
							</p>
						</td>
					</tr>

					<tr id="sp2fa-confirm-row" style="display:none;">
						<th><?php esc_html_e( 'Setup', 'sitefort' ); ?></th>
						<td id="sp2fa-confirm-content"></td>
					</tr>

					<tr id="sp2fa-codes-row" style="display:none;">
						<th></th>
						<td id="sp2fa-codes-content"></td>
					</tr>
				<?php endif; ?>

			<?php endif; ?>
			</table>
			</div>
			<?php
		}

	/**
	 * Get the default settings for the 2FA module.
	 *
	 * @return array Default settings.
	 */
	public static function get_default_settings(): array {
		return [
			'enabled'              => false,
			'enforce_for_roles'    => [ 'administrator' ],
			'remember_device_days' => 30,
			'grace_period_days'    => 3,
			'allowed_methods'      => [ 'totp', 'email' ],
		];
	}

	/**
	 * Normalize allowed-method settings to supported values only.
	 *
	 * @param mixed $methods Raw method configuration payload.
	 * @return array<int,string>
	 */
	public static function sanitize_allowed_methods( $methods ): array {
		if ( ! is_array( $methods ) ) {
			return [];
		}

		$valid = array_map(
			'sanitize_key',
			array_values( array_intersect( array_map( 'sanitize_key', $methods ), [ 'totp', 'email' ] ) )
		);

		return array_values( array_unique( array_filter( $valid ) ) );
	}

	/**
	 * Apply runtime side effects after 2FA policy changes.
	 *
	 * Keeps grace-period tracking and pending login sessions aligned whether the
	 * change originates from the plugin UI or a Console command.
	 *
	 * @param array $previous Previous persisted settings.
	 * @param array $current  Newly persisted settings.
	 */
	public static function apply_settings_side_effects( array $previous, array $current ): void {
		$defaults = self::get_default_settings();
		$previous = wp_parse_args( $previous, $defaults );
		$current  = wp_parse_args( $current, $defaults );

		$was_enabled = ! empty( $previous['enabled'] );
		$now_enabled = ! empty( $current['enabled'] );

		if ( $now_enabled && ! $was_enabled ) {
			LoginInterstitial::clear_all_enforcement_starts();
		}

		$previous_methods = self::sanitize_allowed_methods( $previous['allowed_methods'] ?? $defaults['allowed_methods'] );
		$current_methods  = self::sanitize_allowed_methods( $current['allowed_methods'] ?? $defaults['allowed_methods'] );
		if ( empty( $previous_methods ) ) {
			$previous_methods = $defaults['allowed_methods'];
		}
		if ( empty( $current_methods ) ) {
			$current_methods = $defaults['allowed_methods'];
		}
		sort( $previous_methods );
		sort( $current_methods );

		$remember_days_changed = (int) ( $previous['remember_device_days'] ?? $defaults['remember_device_days'] ) !== (int) ( $current['remember_device_days'] ?? $defaults['remember_device_days'] );
		$methods_changed       = $previous_methods !== $current_methods;

		if ( ! $now_enabled || $remember_days_changed || $methods_changed ) {
			self::clear_all_remembered_devices();
		}

		if ( ! $now_enabled ) {
			LoginSessionStore::delete_all();
			return;
		}

		LoginSessionStore::delete_disallowed_methods( $current_methods );
	}

	public static function clear_all_remembered_devices(): void {
		global $wpdb;

		$table_name = $wpdb->prefix . 'sitefort_two_factor';
		if ( ! SchemaInspector::table_exists( $table_name ) ) {
			return;
		}

		$table = SchemaInspector::table_identifier( $table_name );
		$now   = time();

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$table}
				 SET remember_token = NULL,
				     remember_expires = NULL,
				     updated_at = %d
				 WHERE remember_token IS NOT NULL
				    OR remember_expires IS NOT NULL",
				$now
			)
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
	}

	public static function is_schema_current(): bool {
		global $wpdb;

		$table = $wpdb->prefix . 'sitefort_two_factor';
		$method = SchemaInspector::get_column( $table, 'method' );
		if ( ! is_array( $method ) ) {
			return false;
		}

		$method_type = strtolower( (string) ( $method['Type'] ?? '' ) );
		$method_null = strtoupper( (string) ( $method['Null'] ?? '' ) );
		$method_default = SchemaInspector::normalize_default_value( $method['Default'] ?? '' );
		if ( $method_type !== 'varchar(10)' || $method_null !== 'NO' || $method_default !== 'totp' ) {
			return false;
		}

		$totp_secret = SchemaInspector::get_column( $table, 'totp_secret' );
		if ( ! is_array( $totp_secret ) ) {
			return false;
		}

		$totp_type = strtolower( (string) ( $totp_secret['Type'] ?? '' ) );
		$totp_null = strtoupper( (string) ( $totp_secret['Null'] ?? '' ) );

		return $totp_type === 'text' && $totp_null === 'YES';
	}

	/**
	 * Get the current module settings.
	 *
	 * @return array Current settings merged with defaults.
	 */
	public function get_settings(): array {
		return $this->settings;
	}

	/**
	 * Load settings from the database with defaults.
	 */
	private function load_settings(): void {
		$defaults       = self::get_default_settings();
		$this->settings = \SiteFort\Includes\SettingsStore::get( \SiteFort\Includes\SettingsStore::COL_TWOFA, $defaults );
	}

	/**
	 * One-time: apply the administrator default to an empty enforce_for_roles
	 * left by installs saved before that default existed (a shallow merge keeps []).
	 * Runs once; later manual changes are preserved.
	 */
	private function maybe_seed_default_enforce_roles(): void {
		if ( ! \SiteFort\Includes\RequestContext::is_privileged() ) {
			return;
		}
		if ( get_option( self::DEFAULT_ROLES_SEED_OPTION ) ) {
			return;
		}
		update_option( self::DEFAULT_ROLES_SEED_OPTION, '1', false );

		if ( empty( $this->settings['enforce_for_roles'] ) ) {
			$this->settings['enforce_for_roles'] = [ 'administrator' ];
			\SiteFort\Includes\SettingsStore::save( \SiteFort\Includes\SettingsStore::COL_TWOFA, $this->settings );
		}
	}
}
