<?php

namespace LoginAsUser;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\Contracts\DataLocationsInterface;
use WP_PowerSuite\Core\NetworkScope;

class Module extends AbstractModule implements DataLocationsInterface {

	private const MODULE_ID = 'login-as-user';
	private const MODULE_VERSION = '1.1.0';
	private const SESSION_KEY = '_wpps_original_user';
	private const LOGIN_TIME_KEY = '_wpps_login_as_time';
	private const SESSION_COOKIE = 'wpps_login_as';
	private const DEFAULT_MAX_LOGIN_DURATION = 3600;
	private const CRON_HOOK = 'wpps_login_as_cleanup_sessions';
	public const AUDIT_OPTION_KEY = 'wp_powersuite_login_as_audit';
	private const CLEANUP_OFFSET_TRANSIENT = 'wpps_login_as_cleanup_offset';
	private const CLEANUP_BATCH_SIZE = 500;

	/** @var array<string, mixed>|null */
	private ?array $settings_cache = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Login as User', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'View the site as another user for support or testing, with time limits and a quick way to return to your own account.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'frontend', 'ajax', 'cron' ];
	}

	public function getDependencies(): array {
		return [];
	}

	public function getDataLocations(): array {
		return [
			'options' => [ self::AUDIT_OPTION_KEY ],
		];
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function getDefaultSettings(): array {
		return [
			'session_duration_minutes'      => 60,
			'audit_max_entries'             => 100,
			'blocked_target_capabilities'   => "manage_options\nmanage_network\nedit_users",
		];
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function sanitizeSettings( array $settings ): array {
		$defaults = self::getDefaultSettings();

		$minutes = isset( $settings['session_duration_minutes'] )
			? absint( $settings['session_duration_minutes'] )
			: (int) $defaults['session_duration_minutes'];
		$minutes = max( 5, min( 1440, $minutes ) );

		$audit_max = isset( $settings['audit_max_entries'] )
			? absint( $settings['audit_max_entries'] )
			: (int) $defaults['audit_max_entries'];
		$audit_max = max( 10, min( 500, $audit_max ) );

		$blocked = isset( $settings['blocked_target_capabilities'] )
			? (string) $settings['blocked_target_capabilities']
			: (string) $defaults['blocked_target_capabilities'];

		$lines = preg_split( '/\r\n|\r|\n/', $blocked ) ?: [];
		$caps  = [];
		foreach ( $lines as $line ) {
			$cap = sanitize_key( trim( (string) $line ) );
			if ( $cap !== '' ) {
				$caps[] = $cap;
			}
		}
		if ( $caps === [] ) {
			$caps = [ 'manage_options', 'manage_network', 'edit_users' ];
		}

		return [
			'session_duration_minutes'    => $minutes,
			'audit_max_entries'           => $audit_max,
			'blocked_target_capabilities' => implode( "\n", array_unique( $caps ) ),
		];
	}

	/**
	 * Cleanup impersonation data when the module is disabled.
	 */
	public static function cleanupOnDisable(): void {
		wp_clear_scheduled_hook( self::CRON_HOOK );
		delete_transient( self::CLEANUP_OFFSET_TRANSIENT );

		self::purgeImpersonationUserMeta();

		$rows = get_option( self::AUDIT_OPTION_KEY, [] );
		if ( ! is_array( $rows ) ) {
			$rows = [];
		}

		$rows[] = [
			't' => current_time( 'mysql' ),
			'e' => 'module_disabled',
			'a' => get_current_user_id(),
			'b' => 0,
			'i' => self::getClientIpStatic(),
		];

		update_option( self::AUDIT_OPTION_KEY, array_slice( $rows, -500 ), false );

		do_action( 'wp_powersuite_login_as_event', 'module_disabled', get_current_user_id(), 0, [] );
	}

	protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			wp_clear_scheduled_hook( self::CRON_HOOK );
			return;
		}

		add_action( self::CRON_HOOK, [ $this, 'cleanupExpiredSessions' ] );
		if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
			wp_schedule_event( time(), 'hourly', self::CRON_HOOK );
		}

		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
			return;
		}

		add_action( 'wp_ajax_wp_powersuite_login_as_user', [ $this, 'ajaxLoginAsUser' ] );
		add_action( 'wp_ajax_wp_powersuite_switch_back_user', [ $this, 'ajaxSwitchBackUser' ] );

		add_action( 'wp_login', [ $this, 'clearImpersonationMetaOnPasswordLogin' ], 10, 2 );
		add_action( 'wp_logout', [ $this, 'onLogoutClearImpersonation' ] );

		if ( $this->hasImpersonationCookie() ) {
			add_action( 'init', [ $this, 'checkLoginAsSession' ], 1 );
			add_action( 'init', [ $this, 'maybeRegisterSwitchBackUi' ], 2 );
		}

		add_action( 'load-users.php', [ $this, 'registerUsersListScripts' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAdminAssets' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueueFrontendAssets' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueSwitchBackAssets' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueueSwitchBackAssets' ] );

		add_filter( 'wpps_2fa_allow_bypass', [ $this, 'allow2FABypassForLoginAsUser' ], 10, 2 );
	}

	public function maybeRegisterSwitchBackUi(): void {
		if ( ! $this->isImpersonating() ) {
			return;
		}

		if ( ! defined( 'DONOTCACHEPAGE' ) ) {
			define( 'DONOTCACHEPAGE', true );
		}
		if ( ! defined( 'DONOTCACHEOBJECT' ) ) {
			define( 'DONOTCACHEOBJECT', true );
		}
		if ( ! headers_sent() ) {
			nocache_headers();
		}

		add_action( 'admin_head', [ $this, 'printSwitchBackNonceScript' ], 1 );
		add_action( 'wp_head', [ $this, 'printSwitchBackNonceScript' ], 1 );
		add_action( 'admin_bar_menu', [ $this, 'addSwitchBackAdminBarNode' ], 999 );
		add_action( 'wp_footer', [ $this, 'addSwitchBackButton' ] );
		add_action( 'admin_footer', [ $this, 'addSwitchBackButton' ] );
	}

	public function registerUsersListScripts(): void {
		add_filter( 'user_row_actions', [ $this, 'addUserRowAction' ], 10, 2 );
	}

	public function enqueueAdminAssets( string $hook_suffix ): void {
		if ( $hook_suffix !== 'users.php' ) {
			return;
		}

		if ( ! $this->currentOperatorCanLoginAs() ) {
			return;
		}

		$this->enqueueAsset( 'login-as-user-admin', 'login-as-user-admin.js', [], true );

		$settings = $this->getSettings();
		wp_localize_script(
			'wpps-login-as-user-admin',
			'wppsLoginAsAdmin',
			[
				'ajaxUrl'            => admin_url( 'admin-ajax.php' ),
				'ajaxNonce'          => wp_create_nonce( \WP_PowerSuite\Core\Ajax::getInstance()->getNonceAction() ),
				'loggingInLabel'     => __( 'Logging in...', 'wp-powersuite' ),
				'errorMessage'       => __( 'An error occurred.', 'wp-powersuite' ),
				'errorRetryMessage'  => __( 'An error occurred. Please try again.', 'wp-powersuite' ),
			]
		);
	}

	public function enqueueFrontendAssets(): void {
		// Switch-back assets are loaded via enqueueSwitchBackAssets().
	}

	public function enqueueSwitchBackAssets(): void {
		if ( ! $this->isImpersonating() ) {
			return;
		}

		$this->enqueueAsset( 'login-as-user', 'login-as-user.css' );
		$this->enqueueAsset( 'login-as-user-switch-back', 'login-as-user-switch-back.js', [], true );

		wp_localize_script(
			'wpps-login-as-user-switch-back',
			'wppsLoginAsSwitchBack',
			[
				'ajaxUrl'           => admin_url( 'admin-ajax.php' ),
				'ajaxNonce'         => wp_create_nonce( \WP_PowerSuite\Core\Ajax::getInstance()->getNonceAction() ),
				'errorMessage'      => __( 'An error occurred.', 'wp-powersuite' ),
				'errorRetryMessage' => __( 'An error occurred. Please try again.', 'wp-powersuite' ),
			]
		);
	}

	/**
	 * @param array<int, string> $deps
	 */
	private function enqueueAsset( string $handle, string $file, array $deps = [], bool $in_footer = false ): void {
		$base = $this->getAssetsBaseUrl();
		if ( $base === '' ) {
			return;
		}

		$path = __DIR__ . '/assets/' . $file;
		$ver  = file_exists( $path ) ? (string) filemtime( $path ) : self::MODULE_VERSION;

		if ( str_ends_with( $file, '.css' ) ) {
			wp_enqueue_style( 'wpps-' . $handle, $base . $file, $deps, $ver );
			return;
		}

		wp_enqueue_script( 'wpps-' . $handle, $base . $file, $deps, $ver, $in_footer );
	}

	private function getAssetsBaseUrl(): string {
		if ( defined( 'WP_POWERSUITE_PLUGIN_URL' ) ) {
			return WP_POWERSUITE_PLUGIN_URL . 'modules/' . self::MODULE_ID . '/assets/';
		}

		return plugin_dir_url( __FILE__ ) . 'assets/';
	}

	public function printSwitchBackNonceScript(): void {
		if ( ! $this->isImpersonating() ) {
			return;
		}

		$nonce = wp_create_nonce( \WP_PowerSuite\Core\Ajax::getInstance()->getNonceAction() );
		echo '<script>window.wppsLoginAsNonce=' . wp_json_encode( $nonce ) . ';</script>' . "\n";
	}

	public function addSwitchBackAdminBarNode( \WP_Admin_Bar $wp_admin_bar ): void {
		if ( ! $this->isImpersonating() || ! is_admin_bar_showing() ) {
			return;
		}

		$original_user_id = $this->getOriginalUserId();
		if ( ! $original_user_id || get_current_user_id() === $original_user_id ) {
			return;
		}

		$original_user = get_userdata( $original_user_id );
		if ( ! $original_user ) {
			return;
		}

		$wp_admin_bar->add_node(
			[
				'id'    => 'wpps-login-as-switch-back',
				'title' => sprintf(
					/* translators: %s: display name of the administrator account */
					__( 'Switch back to %s', 'wp-powersuite' ),
					esc_html( $original_user->display_name )
				),
				'href'  => '#',
				'meta'  => [
					'class' => 'wpps-switch-back-trigger',
				],
			]
		);
	}

	private function isImpersonating(): bool {
		if ( ! is_user_logged_in() ) {
			return false;
		}

		$session = $this->parseSessionCookie();
		if ( ! $session ) {
			return false;
		}

		return get_current_user_id() === $session['impersonated_id']
			&& $session['original_id'] !== $session['impersonated_id'];
	}

	private function hasImpersonationCookie(): bool {
		return isset( $_COOKIE[ self::SESSION_COOKIE ] )
			&& is_string( $_COOKIE[ self::SESSION_COOKIE ] )
			&& $_COOKIE[ self::SESSION_COOKIE ] !== '';
	}

	/**
	 * @return array{impersonated_id: int, original_id: int, login_time: int}|null
	 */
	private function parseSessionCookie(): ?array {
		if ( ! $this->hasImpersonationCookie() ) {
			return null;
		}

		$decoded = base64_decode( sanitize_text_field( wp_unslash( $_COOKIE[ self::SESSION_COOKIE ] ) ), true );
		if ( ! is_string( $decoded ) || $decoded === '' ) {
			return null;
		}

		$parts = explode( '|', $decoded );
		if ( count( $parts ) !== 4 ) {
			return null;
		}

		list( $impersonated_id, $original_id, $login_time, $signature ) = $parts;
		$impersonated_id = (int) $impersonated_id;
		$original_id     = (int) $original_id;
		$login_time      = (int) $login_time;

		if ( $impersonated_id < 1 || $original_id < 1 || $login_time < 1 ) {
			return null;
		}

		$payload  = $impersonated_id . '|' . $original_id . '|' . $login_time;
		$expected = hash_hmac( 'sha256', $payload, wp_salt( 'wpps_login_as_session' ) );
		if ( ! hash_equals( $expected, $signature ) ) {
			return null;
		}

		return [
			'impersonated_id' => $impersonated_id,
			'original_id'     => $original_id,
			'login_time'      => $login_time,
		];
	}

	private function setImpersonationCookie( int $impersonated_id, int $original_id ): void {
		$login_time = time();
		$payload    = $impersonated_id . '|' . $original_id . '|' . $login_time;
		$signature  = hash_hmac( 'sha256', $payload, wp_salt( 'wpps_login_as_session' ) );
		$value      = base64_encode( $payload . '|' . $signature );

		$secure = is_ssl();
		setcookie(
			self::SESSION_COOKIE,
			$value,
			[
				'expires'  => $login_time + $this->getMaxSessionDuration(),
				'path'     => COOKIEPATH ? COOKIEPATH : '/',
				'domain'   => COOKIE_DOMAIN,
				'secure'   => $secure,
				'httponly' => true,
				'samesite' => 'Lax',
			]
		);
		$_COOKIE[ self::SESSION_COOKIE ] = $value;
	}

	private function clearImpersonationCookie(): void {
		$secure = is_ssl();
		setcookie(
			self::SESSION_COOKIE,
			'',
			[
				'expires'  => time() - YEAR_IN_SECONDS,
				'path'     => COOKIEPATH ? COOKIEPATH : '/',
				'domain'   => COOKIE_DOMAIN,
				'secure'   => $secure,
				'httponly' => true,
				'samesite' => 'Lax',
			]
		);
		unset( $_COOKIE[ self::SESSION_COOKIE ] );
	}

	public function allow2FABypassForLoginAsUser( bool $bypass, \WP_User $user ): bool {
		if ( $bypass || ! wp_doing_ajax() ) {
			return $bypass;
		}

		$action = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : '';
		if ( $action !== 'wp_powersuite_login_as_user' ) {
			return $bypass;
		}

		if ( ! $this->currentOperatorCanLoginAs() ) {
			return $bypass;
		}

		unset( $user );

		return true;
	}

	public function checkLoginAsSession(): void {
		if ( ! is_user_logged_in() ) {
			$this->clearImpersonationCookie();
			return;
		}

		$session = $this->parseSessionCookie();
		if ( ! $session ) {
			$this->clearImpersonationMetaForUser( get_current_user_id() );
			return;
		}

		$current_user_id = get_current_user_id();

		if ( $current_user_id !== $session['impersonated_id'] ) {
			$this->clearImpersonationCookie();
			$this->clearImpersonationMetaForUser( $current_user_id );
			return;
		}

		if ( $current_user_id === $session['original_id'] ) {
			$this->clearLoginAsSession( $current_user_id );
			return;
		}

		if ( ( time() - $session['login_time'] ) > $this->getMaxSessionDuration() ) {
			$this->recordAuditEvent( 'expired', $session['original_id'], $session['impersonated_id'] );
			$this->switchBackToOriginalUser( 'expired' );
			return;
		}

		$original_user = get_userdata( $session['original_id'] );
		if ( ! $original_user || ! $this->canUserLoginAs( $session['original_id'] ) ) {
			$this->recordAuditEvent( 'invalid_original', $session['original_id'], $session['impersonated_id'] );
			$this->switchBackToOriginalUser( 'invalid_original' );
		}
	}

	/**
	 * @return array{success: bool, message: string, redirect_url?: string}
	 */
	public function switchToUser( int $target_user_id ): array {
		if ( ! is_user_logged_in() ) {
			return [
				'success' => false,
				'message' => __( 'You must be logged in to use this feature.', 'wp-powersuite' ),
			];
		}

		$current_user_id = get_current_user_id();

		if ( ! $this->canUserLoginAs( $current_user_id ) ) {
			return [
				'success' => false,
				'message' => __( 'You do not have permission to use this feature. Only administrators can use this feature.', 'wp-powersuite' ),
			];
		}

		$target_user = get_userdata( $target_user_id );
		if ( ! $target_user ) {
			return [
				'success' => false,
				'message' => __( 'Target user not found.', 'wp-powersuite' ),
			];
		}

		if ( $target_user_id === $current_user_id ) {
			return [
				'success' => false,
				'message' => __( 'You cannot log in as yourself.', 'wp-powersuite' ),
			];
		}

		if ( $this->isBlockedTargetUser( $target_user_id ) ) {
			return [
				'success' => false,
				'message' => __( 'You cannot log in as a privileged administrator account.', 'wp-powersuite' ),
			];
		}

		$original_user_id = $this->getOriginalUserId();
		if ( ! $original_user_id ) {
			$original_user_id = $current_user_id;
		}

		$this->clearImpersonationMetaForUser( $target_user_id );

		update_user_meta( $target_user_id, self::LOGIN_TIME_KEY, time() );
		update_user_meta( $target_user_id, self::SESSION_KEY, $original_user_id );

		wp_clear_auth_cookie();
		wp_set_current_user( $target_user_id );
		wp_set_auth_cookie( $target_user_id, false );

		$this->setImpersonationCookie( $target_user_id, $original_user_id );

		$this->recordAuditEvent( 'start', $current_user_id, $target_user_id );

		return [
			'success'      => true,
			'message'      => sprintf(
				__( 'Successfully logged in as %s.', 'wp-powersuite' ),
				$target_user->display_name
			),
			'redirect_url' => $this->getUserDefaultRedirect( $target_user_id ),
		];
	}

	/**
	 * @return array{success: bool, message: string, redirect_url?: string}
	 */
	public function switchBackToOriginalUser( string $reason = 'switch_back' ): array {
		if ( ! is_user_logged_in() ) {
			return [
				'success' => false,
				'message' => __( 'You must be logged in to use this feature.', 'wp-powersuite' ),
			];
		}

		$impersonated_user_id = get_current_user_id();
		$session              = $this->parseSessionCookie();
		$original_user_id     = $session ? $session['original_id'] : $this->getOriginalUserIdFromMeta( $impersonated_user_id );

		if ( ! $original_user_id || $original_user_id === $impersonated_user_id ) {
			$this->clearLoginAsSession( $impersonated_user_id );
			return [
				'success' => false,
				'message' => __( 'No original user session found.', 'wp-powersuite' ),
			];
		}

		if ( ! $session || $session['impersonated_id'] !== $impersonated_user_id ) {
			$this->clearLoginAsSession( $impersonated_user_id );
			return [
				'success' => false,
				'message' => __( 'Invalid impersonation session.', 'wp-powersuite' ),
			];
		}

		if ( ! $this->canUserLoginAs( $original_user_id ) ) {
			$this->clearLoginAsSession( $impersonated_user_id );
			return [
				'success' => false,
				'message' => __( 'You do not have permission to perform this action.', 'wp-powersuite' ),
			];
		}

		$this->clearLoginAsSession( $impersonated_user_id );

		wp_clear_auth_cookie();
		wp_set_current_user( $original_user_id );
		wp_set_auth_cookie( $original_user_id, false );

		if ( $reason === 'switch_back' ) {
			$this->recordAuditEvent( 'switch_back', $original_user_id, $impersonated_user_id );
		}

		return [
			'success'      => true,
			'message'      => __( 'Successfully switched back to your account.', 'wp-powersuite' ),
			'redirect_url' => admin_url( 'users.php' ),
		];
	}

	private function getOriginalUserId() {
		$session = $this->parseSessionCookie();
		if ( $session && get_current_user_id() === $session['impersonated_id'] ) {
			return $session['original_id'];
		}

		return $this->getOriginalUserIdFromMeta( get_current_user_id() );
	}

	private function getOriginalUserIdFromMeta( int $user_id ) {
		if ( $user_id < 1 ) {
			return false;
		}

		$original_user_id = get_user_meta( $user_id, self::SESSION_KEY, true );
		return $original_user_id ? (int) $original_user_id : false;
	}

	private function clearLoginAsSession( int $impersonated_user_id = 0 ): void {
		if ( $impersonated_user_id < 1 ) {
			$impersonated_user_id = get_current_user_id();
		}
		$this->clearImpersonationCookie();
		if ( $impersonated_user_id > 0 ) {
			$this->clearImpersonationMetaForUser( $impersonated_user_id );
		}
	}

	private function clearImpersonationMetaForUser( int $user_id ): void {
		if ( $user_id < 1 ) {
			return;
		}
		delete_user_meta( $user_id, self::SESSION_KEY );
		delete_user_meta( $user_id, self::LOGIN_TIME_KEY );
	}

	public function onLogoutClearImpersonation(): void {
		$session = $this->parseSessionCookie();
		if ( $session ) {
			$this->recordAuditEvent( 'logout', $session['original_id'], $session['impersonated_id'] );
			$this->clearImpersonationMetaForUser( $session['impersonated_id'] );
		}
		$this->clearImpersonationCookie();
	}

	public function clearImpersonationMetaOnPasswordLogin( string $user_login, \WP_User $user ): void {
		unset( $user_login );
		$session = $this->parseSessionCookie();
		if ( $session && $session['impersonated_id'] === (int) $user->ID ) {
			$this->clearImpersonationCookie();
		}
		$this->clearImpersonationMetaForUser( (int) $user->ID );
	}

	public function ajaxLoginAsUser(): void {
		$nonce_action = \WP_PowerSuite\Core\Ajax::getInstance()->getNonceAction();
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), $nonce_action ) ) {
			wp_send_json_error(
				[
					'message' => __( 'Security check failed. Please refresh the page and try again.', 'wp-powersuite' ),
					'code'    => 'nonce_failed',
				]
			);
		}

		if ( ! $this->currentOperatorCanLoginAs() ) {
			wp_send_json_error(
				[
					'message' => __( 'You do not have permission to perform this action.', 'wp-powersuite' ),
					'code'    => 'insufficient_permissions',
				]
			);
		}

		if ( ! isset( $_POST['user_id'] ) || $_POST['user_id'] === '' ) {
			wp_send_json_error(
				[
					'message' => __( 'User ID is required.', 'wp-powersuite' ),
					'code'    => 'missing_user_id',
				]
			);
		}

		$user_id      = absint( $_POST['user_id'] );
		$nonce_action = 'wpps_login_as_user_' . $user_id;

		if ( ! isset( $_POST['user_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['user_nonce'] ) ), $nonce_action ) ) {
			wp_send_json_error(
				[
					'message' => __( 'Security check failed. Please refresh the page and try again.', 'wp-powersuite' ),
					'code'    => 'nonce_failed',
				]
			);
		}

		$result = $this->switchToUser( $user_id );

		if ( $result['success'] ) {
			wp_send_json_success(
				[
					'message'      => $result['message'],
					'redirect_url' => $result['redirect_url'] ?? admin_url(),
				]
			);
		}

		wp_send_json_error(
			[
				'message' => $result['message'],
				'code'    => 'switch_failed',
			]
		);
	}

	public function ajaxSwitchBackUser(): void {
		$nonce_action = \WP_PowerSuite\Core\Ajax::getInstance()->getNonceAction();
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), $nonce_action ) ) {
			wp_send_json_error(
				[
					'message' => __( 'Security check failed. Please refresh the page and try again.', 'wp-powersuite' ),
					'code'    => 'nonce_failed',
				]
			);
		}

		if ( ! is_user_logged_in() ) {
			wp_send_json_error(
				[
					'message' => __( 'You must be logged in to use this feature.', 'wp-powersuite' ),
					'code'    => 'not_logged_in',
				]
			);
		}

		$session = $this->parseSessionCookie();
		if ( ! $session || get_current_user_id() !== $session['impersonated_id'] ) {
			wp_send_json_error(
				[
					'message' => __( 'No active "login as user" session found.', 'wp-powersuite' ),
					'code'    => 'no_session',
				]
			);
		}

		$result = $this->switchBackToOriginalUser( 'switch_back' );

		if ( $result['success'] ) {
			wp_send_json_success(
				[
					'message'      => $result['message'],
					'redirect_url' => $result['redirect_url'] ?? admin_url( 'users.php' ),
				]
			);
		}

		wp_send_json_error(
			[
				'message' => $result['message'],
				'code'    => 'switch_failed',
			]
		);
	}

	private function recordAuditEvent( string $event, int $actor_id, int $target_id ): void {
		if ( ! apply_filters( 'wp_powersuite_login_as_record_audit', true ) ) {
			return;
		}

		$rows = get_option( self::AUDIT_OPTION_KEY, [] );
		if ( ! is_array( $rows ) ) {
			$rows = [];
		}

		$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';

		$rows[] = [
			't' => current_time( 'mysql' ),
			'e' => sanitize_key( $event ),
			'a' => $actor_id,
			'b' => $target_id,
			'i' => $this->getClientIp(),
			'u' => substr( $ua, 0, 255 ),
		];

		$max = $this->getAuditMaxEntries();
		if ( count( $rows ) > $max ) {
			$rows = array_slice( $rows, -$max );
		}

		update_option( self::AUDIT_OPTION_KEY, $rows, false );

		do_action( 'wp_powersuite_login_as_event', $event, $actor_id, $target_id, [
			'ip'         => $this->getClientIp(),
			'user_agent' => $ua,
		] );
	}

	private function getClientIp(): string {
		$raw_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : '';
		$ip     = is_string( $raw_ip ) ? filter_var( $raw_ip, FILTER_VALIDATE_IP ) : false;
		return is_string( $ip ) ? $ip : '';
	}

	private static function getClientIpStatic(): string {
		$raw_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : '';
		$ip     = is_string( $raw_ip ) ? filter_var( $raw_ip, FILTER_VALIDATE_IP ) : false;
		return is_string( $ip ) ? $ip : '';
	}

	private function getOperatorCapability(): string {
		$cap = NetworkScope::adminCapability();
		return (string) apply_filters( 'wp_powersuite_login_as_operator_capability', $cap );
	}

	private function currentOperatorCanLoginAs(): bool {
		return current_user_can( $this->getOperatorCapability() );
	}

	private function canUserLoginAs( int $user_id ): bool {
		return user_can( $user_id, $this->getOperatorCapability() );
	}

	private function isBlockedTargetUser( int $user_id ): bool {
		$user = get_userdata( $user_id );
		if ( ! $user ) {
			return true;
		}

		if ( is_multisite() && is_super_admin( $user_id ) ) {
			return true;
		}

		if ( in_array( 'administrator', $user->roles, true ) ) {
			return true;
		}

		foreach ( $this->getBlockedTargetCapabilities() as $cap ) {
			if ( user_can( $user_id, $cap ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @return list<string>
	 */
	private function getBlockedTargetCapabilities(): array {
		$settings = $this->getSettings();
		$raw      = isset( $settings['blocked_target_capabilities'] )
			? (string) $settings['blocked_target_capabilities']
			: "manage_options\nmanage_network\nedit_users";

		$lines = preg_split( '/\r\n|\r|\n/', $raw ) ?: [];
		$caps  = [];
		foreach ( $lines as $line ) {
			$cap = sanitize_key( trim( (string) $line ) );
			if ( $cap !== '' ) {
				$caps[] = $cap;
			}
		}

		if ( $caps === [] ) {
			$caps = [ 'manage_options', 'manage_network', 'edit_users' ];
		}

		return array_values( array_unique( apply_filters( 'wp_powersuite_login_as_blocked_target_caps', $caps ) ) );
	}

	private function getUserDefaultRedirect( int $user_id ): string {
		$user = get_userdata( $user_id );
		if ( ! $user instanceof \WP_User ) {
			return home_url( '/' );
		}

		$sentinel = $this->getLoginAsRedirectSentinel();

		if ( is_multisite() && ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
			$redirect_to = user_admin_url( $user_id );
		} elseif ( $this->userShouldLandInAdmin( $user_id ) ) {
			$redirect_to = get_dashboard_url( $user_id );
		} else {
			$redirect_to = home_url( '/' );
		}

		$custom = get_user_meta( $user_id, 'login_redirect', true );
		if ( is_string( $custom ) && $custom !== '' ) {
			$redirect_to = $custom;
		}

		$redirect_to = apply_filters( 'login_redirect', $redirect_to, $sentinel, $user );

		if ( class_exists( 'WooCommerce' ) ) {
			$redirect_to = apply_filters( 'woocommerce_login_redirect', $redirect_to, $user );
		}

		return (string) wp_validate_redirect( $redirect_to, home_url( '/' ) );
	}

	private function userShouldLandInAdmin( int $user_id ): bool {
		$caps = apply_filters(
			'wp_powersuite_login_as_admin_redirect_caps',
			[ 'edit_posts', 'manage_woocommerce', 'list_users', 'manage_options' ]
		);

		foreach ( (array) $caps as $cap ) {
			if ( is_string( $cap ) && $cap !== '' && user_can( $user_id, $cap ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @return array<string, mixed>
	 */
	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$stored = $this->moduleSettings()->all( self::getDefaultSettings() );
		$this->settings_cache = self::sanitizeSettings( is_array( $stored ) ? $stored : [] );

		return $this->settings_cache;
	}

	private function getMaxSessionDuration(): int {
		$settings = $this->getSettings();
		$minutes  = max( 5, min( 1440, (int) ( $settings['session_duration_minutes'] ?? 60 ) ) );
		$seconds  = $minutes * MINUTE_IN_SECONDS;

		$filtered = (int) apply_filters( 'wp_powersuite_login_as_max_duration', $seconds );
		return max( 300, min( DAY_IN_SECONDS, $filtered ) );
	}

	private function getAuditMaxEntries(): int {
		$settings = $this->getSettings();
		return max( 10, min( 500, (int) ( $settings['audit_max_entries'] ?? 100 ) ) );
	}

	private function getLoginAsRedirectSentinel(): string {
		if ( class_exists( 'RedirectAfterLogin\Module' ) ) {
			return \RedirectAfterLogin\Module::REQUESTED_REDIRECT_LOGIN_AS_SENTINEL;
		}

		return '__wpps_login_as__';
	}

	public function addSwitchBackButton(): void {
		$original_user_id = $this->getOriginalUserId();
		if ( ! $original_user_id ) {
			return;
		}

		$current_user_id = get_current_user_id();
		if ( $current_user_id === $original_user_id ) {
			return;
		}

		$original_user = get_userdata( $original_user_id );
		$target_user   = get_userdata( $current_user_id );
		if ( ! $original_user || ! $target_user ) {
			return;
		}

		static $printed = false;
		if ( $printed ) {
			return;
		}
		$printed = true;

		$is_admin_screen = is_admin();

		?>
		<div id="wpps-login-as-banner" class="wpps-login-as-banner <?php echo $is_admin_screen ? 'wpps-login-as-banner--admin' : 'wpps-login-as-banner--frontend'; ?>"<?php echo $is_admin_screen ? ' role="status" aria-live="polite"' : ''; ?>>
			<?php if ( $is_admin_screen ) : ?>
			<p class="wpps-login-as-banner__status">
				<?php
				printf(
					/* translators: 1: impersonated user display name, 2: administrator display name */
					esc_html__( 'Viewing as %1$s. Private account data may be visible. Signed in originally as %2$s.', 'wp-powersuite' ),
					esc_html( $target_user->display_name ),
					esc_html( $original_user->display_name )
				);
				?>
			</p>
			<?php endif; ?>
			<button type="button" id="wpps-switch-back-button" class="wpps-switch-back-trigger button">
				<?php
				if ( $is_admin_screen ) {
					printf(
						/* translators: %s: administrator display name */
						esc_html__( 'Switch back to %s', 'wp-powersuite' ),
						esc_html( $original_user->display_name )
					);
				} else {
					esc_html_e( 'Switch back', 'wp-powersuite' );
				}
				?>
			</button>
		</div>
		<?php
	}

	public function addUserRowAction( array $actions, \WP_User $user ): array {
		$current_user_id = get_current_user_id();
		if ( ! $current_user_id || ! $this->currentOperatorCanLoginAs() ) {
			return $actions;
		}

		if ( $user->ID === $current_user_id ) {
			return $actions;
		}

		if ( $this->isBlockedTargetUser( $user->ID ) ) {
			return $actions;
		}

		$nonce = wp_create_nonce( 'wpps_login_as_user_' . $user->ID );

		$actions['wpps_login_as'] = sprintf(
			'<a href="#" class="wpps-login-as-user-link" data-user-id="%d" data-nonce="%s" data-display-name="%s">%s</a>',
			esc_attr( $user->ID ),
			esc_attr( $nonce ),
			esc_attr( $user->display_name ),
			esc_html__( 'Login as', 'wp-powersuite' )
		);

		return $actions;
	}

	public function cleanupExpiredSessions(): void {
		$max_age = $this->getMaxSessionDuration();
		$cutoff  = time() - $max_age;
		$offset  = (int) get_transient( self::CLEANUP_OFFSET_TRANSIENT );

		$by_time = get_users(
			[
				'fields'       => 'ids',
				'meta_key'     => self::LOGIN_TIME_KEY,
				'meta_compare' => 'EXISTS',
				'number'       => self::CLEANUP_BATCH_SIZE,
				'offset'       => $offset,
			]
		);

		foreach ( $by_time as $uid ) {
			$login_time = (int) get_user_meta( $uid, self::LOGIN_TIME_KEY, true );
			if ( $login_time > 0 && $login_time < $cutoff ) {
				$original_id = (int) get_user_meta( $uid, self::SESSION_KEY, true );
				if ( $original_id > 0 ) {
					$this->recordAuditEvent( 'expired', $original_id, (int) $uid );
				}
				$this->clearImpersonationMetaForUser( (int) $uid );
			}
		}

		if ( count( $by_time ) === self::CLEANUP_BATCH_SIZE ) {
			set_transient( self::CLEANUP_OFFSET_TRANSIENT, $offset + self::CLEANUP_BATCH_SIZE, HOUR_IN_SECONDS );
		} else {
			delete_transient( self::CLEANUP_OFFSET_TRANSIENT );

			$orphan_offset = 0;
			$orphaned      = get_users(
				[
					'fields'       => 'ids',
					'meta_key'     => self::SESSION_KEY,
					'meta_compare' => 'EXISTS',
					'number'       => self::CLEANUP_BATCH_SIZE,
					'offset'       => $orphan_offset,
				]
			);

			foreach ( $orphaned as $uid ) {
				$login_time = get_user_meta( $uid, self::LOGIN_TIME_KEY, true );
				if ( $login_time === '' || $login_time === false ) {
					$this->clearImpersonationMetaForUser( (int) $uid );
				}
			}
		}
	}

	private static function purgeImpersonationUserMeta(): void {
		$keys = [ self::SESSION_KEY, self::LOGIN_TIME_KEY ];

		foreach ( $keys as $meta_key ) {
			$offset = 0;
			do {
				$user_ids = get_users(
					[
						'fields'       => 'ids',
						'meta_key'     => $meta_key,
						'meta_compare' => 'EXISTS',
						'number'       => self::CLEANUP_BATCH_SIZE,
						'offset'       => $offset,
					]
				);

				foreach ( $user_ids as $user_id ) {
					delete_user_meta( (int) $user_id, self::SESSION_KEY );
					delete_user_meta( (int) $user_id, self::LOGIN_TIME_KEY );
				}

				$offset += self::CLEANUP_BATCH_SIZE;
			} while ( count( $user_ids ) === self::CLEANUP_BATCH_SIZE );
		}
	}
}
