<?php

namespace ForceSslAdmin;

use WP_PowerSuite\Bootstrap\Kernel;
use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\Http\AjaxRouter;
use WP_PowerSuite\Core\NetworkScope;

class Module extends AbstractModule {

	private const MODULE_ID = 'force-ssl-admin';
	private const MODULE_VERSION = '1.0.4';
	private const EMERGENCY_KEY_MIN_LENGTH = 20;
	private const LOOP_COOKIE = 'wpps_force_ssl_rd';
	private const LOOP_MAX_VISITS = 3;

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

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

	public function getName(): string {
		return __( 'Force SSL Admin', 'wp-powersuite' );
	}

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

	public function getDescription(): string {
		return __( 'Always open your dashboard and login screen over a secure https:// link. Anyone using the old http:// address is sent to the encrypted URL automatically.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return 'both';
	}

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

	protected function registerHooks(): void {
		// Enabled state is enforced by ModuleManager::loadModule() before init() runs.

		add_action( 'wp_powersuite_module_toggled', [ $this, 'onModuleToggled' ], 10, 3 );

		$this->registerEmergencyHandlers();

		add_action( 'wp_ajax_wpps_fsa_regenerate_emergency_key', [ $this, 'handleRegenerateEmergencyKey' ] );

		$settings    = $this->getSettings();
		$force_admin = ! empty( $settings['force_admin'] );
		$force_login = ! empty( $settings['force_login'] );

		if ( ! $force_admin && ! $force_login ) {
			return;
		}

		if ( $this->hasAdminDisableNoticeQuery() ) {
			add_action( 'admin_init', [ $this, 'maybeRegisterDisableNotice' ], 1 );
		}

		if ( $force_admin ) {
			add_action( 'admin_init', [ $this, 'redirectAdminToHttps' ], 1 );
			add_filter( 'secure_auth_redirect', [ $this, 'filterSecureAuthRedirect' ], 10, 1 );
			add_filter( 'force_ssl_admin', [ $this, 'filterForceSslAdmin' ], 10, 1 );
		}

		if ( $force_login ) {
			// wp-login.php: redirect during critical boot (plugins_loaded) before login_init.
			$this->maybeRedirectWpLoginToHttps();

			add_action( 'login_init', [ $this, 'redirectLoginToHttps' ], 1 );

			if ( has_filter( 'wp_powersuite_is_custom_login_url_request' ) ) {
				add_action( 'template_redirect', [ $this, 'redirectCustomLoginUrlToHttps' ], 0 );
			}
		}
	}

	/**
	 * Whether the current request is served over HTTPS (core + trusted proxy signals).
	 */
	public static function isHttpsRequest(): bool {
		if ( is_ssl() ) {
			return true;
		}

		if ( ! empty( $_SERVER['HTTP_CF_VISITOR'] ) ) {
			$cf_visitor = json_decode( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_VISITOR'] ) ), true );
			if ( is_array( $cf_visitor ) && ( $cf_visitor['scheme'] ?? '' ) === 'https' ) {
				return true;
			}
		}

		if ( self::shouldTrustProxyHeaders() ) {
			foreach ( [ 'HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_SSL' ] as $header ) {
				if ( empty( $_SERVER[ $header ] ) ) {
					continue;
				}
				$value = strtolower( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) );
				if ( $value === 'https' || $value === 'on' ) {
					return true;
				}
			}
		}

		/**
		 * Filter whether Force SSL Admin treats the current request as HTTPS.
		 *
		 * @param bool $is_https False when core/proxy checks did not detect HTTPS.
		 */
		return (bool) apply_filters( 'wp_powersuite_force_ssl_admin_is_https', false );
	}

	private static function shouldTrustProxyHeaders(): bool {
		if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) || ! empty( $_SERVER['HTTP_CF_VISITOR'] ) ) {
			return true;
		}

		return (bool) apply_filters( 'wp_powersuite_trust_proxy_headers', false );
	}

	/**
	 * @param array<string, mixed>|null $metadata Unused.
	 */
	public function onModuleToggled( string $module_id, bool $enable, ?array $metadata ): void {
		if ( $module_id !== self::MODULE_ID || ! $enable ) {
			return;
		}

		$this->persistEmergencyKeyIfMissing();
	}

	/**
	 * Emergency disable and frontend success page (only when query flags are present).
	 */
	private function registerEmergencyHandlers(): void {
		add_action( 'login_init', [ $this, 'handleEmergencyDisableRequest' ], 0 );
		add_action( 'admin_init', [ $this, 'handleEmergencyDisableRequest' ], 0 );

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

		add_action( 'template_redirect', [ $this, 'handlePublicEmergencyRequest' ], 0 );
	}

	private function hasEmergencyQueryFlag(): bool {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Presence check only; handlers verify key.
		return isset( $_GET['powersuite_disable_ssl'] );
	}

	private function hasAdminDisableNoticeQuery(): bool {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only flash from safe redirect.
		return isset( $_GET['wpps_force_ssl_disabled'] );
	}

	/**
	 * Registers the success notice only when the redirect query flag is present.
	 */
	public function maybeRegisterDisableNotice(): void {
		if ( ! is_admin() || ! $this->hasAdminDisableNoticeQuery() ) {
			return;
		}

		$flag = sanitize_text_field( wp_unslash( $_GET['wpps_force_ssl_disabled'] ) );
		if ( $flag !== '1' ) {
			return;
		}

		add_action( 'admin_notices', [ $this, 'renderDisableSuccessNotice' ] );
	}

	public function renderDisableSuccessNotice(): void {
		?>
		<div class="notice notice-success is-dismissible">
			<p><strong><?php esc_html_e( 'Done', 'wp-powersuite' ); ?>:</strong> <?php esc_html_e( 'Force SSL Admin was turned off. You can turn it on again from WP PowerSuite when your site is ready for HTTPS.', 'wp-powersuite' ); ?></p>
		</div>
		<?php
	}

	/**
	 * Emergency disable success page on the front end, or no-op.
	 */
	public function handlePublicEmergencyRequest(): void {
		$flag = $this->getQueryString( 'powersuite_disable_ssl' );

		if ( $flag === 'success' ) {
			wp_die(
				'<p>' . esc_html__( 'Force SSL Admin was turned off. Redirects to HTTPS for the login page and dashboard are stopped.', 'wp-powersuite' ) . '</p>'
				. '<p>' . esc_html__( 'You can sign in normally now. When your site is ready, turn the module on again in WP PowerSuite and copy your new safety link.', 'wp-powersuite' ) . '</p>'
				. '<p><a class="button button-primary" href="' . esc_url( wp_login_url() ) . '">' . esc_html__( 'Go to sign-in page', 'wp-powersuite' ) . '</a></p>',
				esc_html__( 'Module turned off', 'wp-powersuite' ),
				[ 'response' => 200, 'back_link' => false ]
			);
		}

		if ( $flag !== 'true' ) {
			return;
		}

		$this->runEmergencyDisable();
	}

	public function handleEmergencyDisableRequest(): void {
		if ( $this->getQueryString( 'powersuite_disable_ssl' ) !== 'true' ) {
			return;
		}

		$this->runEmergencyDisable();
	}

	private function runEmergencyDisable(): void {
		// The secret key in the URL is the credential — no sign-in required (lockout recovery).
		if ( ! $this->isValidEmergencyKey( $this->getQueryString( 'key' ) ) ) {
			wp_die(
				'<p>' . esc_html__( 'This safety link is not valid or is incomplete. Use the full link you saved from WP PowerSuite → Force SSL Admin.', 'wp-powersuite' ) . '</p>'
				. '<p>' . esc_html__( 'If you are locked out, ask another administrator for help, or temporarily rename the WP PowerSuite plugin folder via your hosting file manager.', 'wp-powersuite' ) . '</p>',
				esc_html__( 'Invalid safety link', 'wp-powersuite' ),
				[ 'response' => 403 ]
			);
		}

		$this->optionStore()->disableModule( self::MODULE_ID );

		// One-time use: rotate the key so an old saved link cannot be reused.
		$settings = $this->moduleSettings()->all( [] );
		if ( ! is_array( $settings ) ) {
			$settings = [];
		}
		$settings['emergency_key'] = self::generateEmergencyKey();
		$this->moduleSettings()->save( $settings );
		$this->settings_cache = null;

		if ( is_admin() && is_user_logged_in() && current_user_can( NetworkScope::adminCapability() ) ) {
			wp_safe_redirect( NetworkScope::adminUrl( 'admin.php', [ 'page' => 'powersuite', 'wpps_force_ssl_disabled' => '1' ] ) );
		} else {
			wp_safe_redirect( home_url( '/?powersuite_disable_ssl=success' ) );
		}
		exit;
	}

	private function isValidEmergencyKey( string $provided_key ): bool {
		$expected = (string) ( $this->getSettings()['emergency_key'] ?? '' );
		if ( $expected === '' || strlen( $expected ) < self::EMERGENCY_KEY_MIN_LENGTH ) {
			return false;
		}

		return hash_equals( $expected, $provided_key );
	}

	/**
	 * Build the personal safety link shown in module settings.
	 */
	public static function buildEmergencyDisableUrl( string $emergency_key ): string {
		return add_query_arg(
			[
				'powersuite_disable_ssl' => 'true',
				'key'                    => $emergency_key,
			],
			home_url( '/' )
		);
	}

	/**
	 * @return string Alphanumeric emergency key (empty if unavailable).
	 */
	public function getEmergencyKey(): string {
		return (string) ( $this->getSettings()['emergency_key'] ?? '' );
	}

	/**
	 * Raw query value (after unslash + sanitize).
	 */
	private function getQueryString( string $key ): string {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Gate for redirect/emergency UX only.
		if ( ! isset( $_GET[ $key ] ) ) {
			return '';
		}

		return sanitize_text_field( wp_unslash( $_GET[ $key ] ) );
	}

	private function isEmergencyDisableRequested(): bool {
		return $this->getQueryString( 'powersuite_disable_ssl' ) === 'true';
	}

	/**
	 * Match WordPress core behavior when FORCE_SSL_ADMIN is on (secure cookies + admin redirect).
	 *
	 * @param bool $secure Current value.
	 */
	public function filterSecureAuthRedirect( bool $secure ): bool {
		return $secure || ! empty( $this->getSettings()['force_admin'] );
	}

	/**
	 * Align with core force_ssl_admin() when dashboard protection is enabled.
	 *
	 * @param bool $force Current value.
	 */
	public function filterForceSslAdmin( bool $force ): bool {
		return $force || ! empty( $this->getSettings()['force_admin'] );
	}

	public function redirectAdminToHttps(): void {
		if ( ! is_admin() ) {
			return;
		}

		$this->performHttpsRedirectIfNeeded();
	}

	public function redirectLoginToHttps(): void {
		$this->performHttpsRedirectIfNeeded();
	}

	public function redirectCustomLoginUrlToHttps(): void {
		if ( ! apply_filters( 'wp_powersuite_is_custom_login_url_request', false ) ) {
			return;
		}

		$this->performHttpsRedirectIfNeeded();
	}

	/**
	 * Redirect wp-login.php over HTTP during critical boot (plugins_loaded).
	 */
	private function maybeRedirectWpLoginToHttps(): void {
		if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
			return;
		}

		$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] );
		if ( stripos( $request_uri, 'wp-login.php' ) === false ) {
			return;
		}

		$this->performHttpsRedirectIfNeeded();
	}

	private function performHttpsRedirectIfNeeded(): void {
		if ( ! $this->shouldEnforceSslRedirect() || $this->isEmergencyDisableRequested() || self::isHttpsRequest() ) {
			return;
		}

		if ( $this->isRedirectLoopDetected() ) {
			return;
		}

		$target = $this->buildHttpsRedirectTarget();
		if ( $target === '' ) {
			return;
		}

		$this->recordRedirectAttempt();

		$redirected = wp_safe_redirect( $target, $this->getRedirectStatus() );
		if ( $redirected === false ) {
			return;
		}

		exit;
	}

	private function shouldEnforceSslRedirect(): bool {
		if ( wp_doing_ajax() || wp_installing() || wp_doing_cron() || $this->isAdminPostRequest() ) {
			return false;
		}

		return true;
	}

	private function isAdminPostRequest(): bool {
		if ( isset( $GLOBALS['pagenow'] ) && $GLOBALS['pagenow'] === 'admin-post.php' ) {
			return true;
		}

		try {
			return Kernel::getInstance()->isAdminPost();
		} catch ( \Throwable $e ) {
			return false;
		}
	}

	private function isRedirectLoopDetected(): bool {
		if ( empty( $_COOKIE[ self::LOOP_COOKIE ] ) ) {
			return false;
		}

		$count = (int) sanitize_text_field( wp_unslash( $_COOKIE[ self::LOOP_COOKIE ] ) );

		return $count >= self::LOOP_MAX_VISITS;
	}

	private function recordRedirectAttempt(): void {
		if ( headers_sent() ) {
			return;
		}

		$count = isset( $_COOKIE[ self::LOOP_COOKIE ] )
			? (int) sanitize_text_field( wp_unslash( $_COOKIE[ self::LOOP_COOKIE ] ) )
			: 0;
		++$count;

		setcookie(
			self::LOOP_COOKIE,
			(string) $count,
			time() + 300,
			COOKIEPATH,
			COOKIE_DOMAIN,
			self::isHttpsRequest(),
			true
		);
	}

	private function buildHttpsRedirectTarget(): string {
		if ( ! isset( $_SERVER['REQUEST_URI'], $_SERVER['HTTP_HOST'] ) ) {
			return '';
		}

		$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] );
		if ( str_starts_with( $request_uri, 'http' ) ) {
			return set_url_scheme( $request_uri, 'https' );
		}

		return 'https://' . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . $request_uri;
	}

	private function getRedirectStatus(): int {
		$settings      = $this->getSettings();
		$redirect_code = isset( $settings['redirect_code'] ) && in_array( $settings['redirect_code'], [ '301', '302' ], true )
			? (int) $settings['redirect_code']
			: 301;

		return $redirect_code === 302 ? 302 : 301;
	}

	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$settings             = $this->moduleSettings()->all( [] );
		$defaults               = $this->getDefaultSettings();
		$this->settings_cache   = array_merge( $defaults, is_array( $settings ) ? $settings : [] );

		return $this->settings_cache;
	}

	public function getDefaultSettings(): array {
		return [
			'force_admin'   => false,
			'force_login'   => false,
			'redirect_code' => '301',
			'emergency_key' => '',
		];
	}

	/**
	 * Create and store a safety link secret when the module is enabled or settings are opened without one.
	 */
	public function handleRegenerateEmergencyKey(): void {
		if ( ! AjaxRouter::verifyAdminRequest() ) {
			return;
		}

		$settings = $this->moduleSettings()->all( [] );
		if ( ! is_array( $settings ) ) {
			$settings = [];
		}

		$settings['emergency_key'] = self::generateEmergencyKey();
		$this->moduleSettings()->save( $settings );
		$this->settings_cache = null;

		wp_send_json_success(
			[
				'disable_url' => self::buildEmergencyDisableUrl( (string) $settings['emergency_key'] ),
				'message'     => __( 'New safety link created. Save the new link — your old one no longer works.', 'wp-powersuite' ),
			]
		);
	}

	public function persistEmergencyKeyIfMissing(): void {
		$settings = $this->moduleSettings()->all( [] );
		if ( ! is_array( $settings ) ) {
			$settings = [];
		}

		$key = isset( $settings['emergency_key'] ) ? (string) $settings['emergency_key'] : '';
		if ( $key !== '' && strlen( $key ) >= self::EMERGENCY_KEY_MIN_LENGTH ) {
			return;
		}

		$settings['emergency_key'] = self::generateEmergencyKey();
		$this->moduleSettings()->save( $settings );
		$this->settings_cache = null;
	}

	public static function generateEmergencyKey(): string {
		return wp_generate_password( 32, false, false );
	}
}
