<?php

namespace DisablePasswordReset;

use WP_PowerSuite\Core\AbstractModule;

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

class Module extends AbstractModule {

	private const MODULE_ID = 'disable-password-reset';
	private const MODULE_VERSION = '1.3.0';

	public const DISABLED_QUERY_ARG = 'wpps_password_reset_disabled';

	/** Late priority so another plugin cannot re-enable password reset after this runs. */
	private const FILTER_PRIORITY_LATE = \PHP_INT_MAX;

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

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

	public function getName(): string {
		return __( 'Disable Password Reset', 'wp-powersuite' );
	}

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

	public function getDescription(): string {
		return __(
			'Turn off public “forgot password” self-service for everyone. Use only when you reset passwords another way (manual admin password, WP-CLI, or admin-sent reset links when enabled in settings). Existing email reset links stop working while this module is on.',
			'wp-powersuite'
		);
	}

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

	public function getDefaultSettings(): array {
		return [
			'allow_admin_reset_emails' => true,
			'disabled_message'         => '',
		];
	}

	/**
	 * @param array<string, mixed> $stored Raw settings from OptionStore.
	 * @return array{allow_admin_reset_emails: bool, disabled_message: string}
	 */
	public static function normalizeSettings( array $stored ): array {
		$defaults = [
			'allow_admin_reset_emails' => true,
			'disabled_message'         => '',
		];

		$resolved = $defaults;
		foreach ( $defaults as $key => $default_value ) {
			if ( ! array_key_exists( $key, $stored ) ) {
				continue;
			}

			if ( $key === 'disabled_message' ) {
				$resolved[ $key ] = is_string( $stored[ $key ] ) ? $stored[ $key ] : '';
				continue;
			}

			$resolved[ $key ] = (bool) $stored[ $key ];
		}

		return $resolved;
	}

	/**
	 * @param array<string, mixed> $settings Unsanitized settings payload.
	 * @return array{allow_admin_reset_emails: bool, disabled_message: string}
	 */
	public static function sanitizeSettings( array $settings ): array {
		$message = isset( $settings['disabled_message'] ) && is_string( $settings['disabled_message'] )
			? sanitize_textarea_field( $settings['disabled_message'] )
			: '';

		return [
			'allow_admin_reset_emails' => ! empty( $settings['allow_admin_reset_emails'] ),
			'disabled_message'         => $message,
		];
	}

	protected function registerHooks(): void {
		add_filter( 'allow_password_reset', [ $this, 'filterAllowPasswordReset' ], self::FILTER_PRIORITY_LATE, 2 );

		/**
		 * Runs after WooCommerce `wc_lostpassword_url` (priority 10) so checkout/My Account login forms
		 * do not point at the WooCommerce lost-password endpoint.
		 */
		add_filter( 'lostpassword_url', [ $this, 'replaceLostPasswordUrlWithLogin' ], self::FILTER_PRIORITY_LATE, 2 );
		add_filter( 'login_message', [ $this, 'filterLoginMessage' ], 10, 1 );
		add_filter( 'wp_powersuite_login_email_secure_account_url', [ $this, 'filterLoginEmailSecureAccountUrl' ], 10, 3 );

		add_action( 'login_init', [ $this, 'redirectLostPasswordRequests' ], 0 );
		add_action( 'login_enqueue_scripts', [ $this, 'hideLostPasswordLinkStyles' ], 20 );

		add_action( 'wp_enqueue_scripts', [ $this, 'enqueueFrontendHideLostPasswordUi' ], 100 );
		add_action( 'template_redirect', [ $this, 'redirectWooCommerceLostPasswordEndpoint' ], 1 );
	}

	/**
	 * @param bool $allow   Whether password reset is allowed.
	 * @param int  $user_id User ID.
	 */
	public function filterAllowPasswordReset( bool $allow, int $user_id ): bool {
		unset( $user_id );

		if ( ! $allow ) {
			return false;
		}

		if ( $this->shouldAllowAdminPasswordResetEmail() ) {
			return true;
		}

		return false;
	}

	/**
	 * WooCommerce prints lost-password links on account/checkout/cart; hide them without loading CSS on unrelated pages.
	 * Non-Woo themes still get {@see replaceLostPasswordUrlWithLogin()} so links typically point at the login screen instead.
	 */
	public function enqueueFrontendHideLostPasswordUi(): void {
		if ( is_admin() || ! $this->shouldEnqueueFrontendLostPasswordHideStyles() ) {
			return;
		}

		$handle = 'wps-disable-password-reset-fe';
		wp_register_style( $handle, false, [], self::MODULE_VERSION );
		wp_enqueue_style( $handle );

		$css = 'body:not(.wp-admin) p.lost_password,body:not(.wp-admin) .woocommerce-LostPassword,body:not(.wp-admin) .woocommerce-form-login p.lost_password,body:not(.wp-admin) .wc-block-components-login-form p.lost_password{display:none!important;}body:not(.wp-admin) a[href*="action=lostpassword"]{display:none!important;}';

		wp_add_inline_style( $handle, $css );
	}

	/**
	 * Prevent browsing the My Account lost-password endpoint (slug is configurable in WooCommerce settings).
	 *
	 * Keyed set-password / reset links (including WooCommerce new-account emails) must still work.
	 */
	public function redirectWooCommerceLostPasswordEndpoint(): void {
		if ( ! function_exists( 'is_wc_endpoint_url' ) ) {
			return;
		}

		$slug = (string) get_option( 'woocommerce_myaccount_lost_password_endpoint', 'lost-password' );
		if ( '' === $slug || ! is_wc_endpoint_url( $slug ) ) {
			return;
		}

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

		$target = function_exists( 'wc_get_page_permalink' ) ? wc_get_page_permalink( 'myaccount' ) : '';
		if ( ! is_string( $target ) || '' === $target ) {
			$target = home_url( '/' );
		}

		if ( function_exists( 'wc_add_notice' ) ) {
			wc_add_notice( wp_strip_all_tags( $this->getDisabledMessage() ), 'notice' );
		}

		wp_safe_redirect( $target );
		exit;
	}

	/**
	 * Whether the current request is a password set/reset link with a valid-looking key payload.
	 */
	private function isKeyedPasswordResetRequest(): bool {
		$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : '';
		if ( $action === 'newaccount' ) {
			return true;
		}

		$has_key = isset( $_GET['key'] ) && is_string( $_GET['key'] ) && $_GET['key'] !== '';
		if ( ! $has_key ) {
			return isset( $_GET['show-reset-form'] ) && (string) wp_unslash( $_GET['show-reset-form'] ) === 'true';
		}

		return isset( $_GET['login'] ) || isset( $_GET['id'] ) || isset( $_GET['user'] );
	}

	/**
	 * @param string $lostpassword_url Previous URL from core or WooCommerce.
	 * @param string $redirect        Redirect argument from {@see wp_lostpassword_url()}.
	 */
	public function replaceLostPasswordUrlWithLogin( $lostpassword_url, $redirect = '' ): string {
		unset( $lostpassword_url );

		return $this->getLoginRedirectWithNotice( is_string( $redirect ) ? $redirect : '' );
	}

	/**
	 * Runs only on wp-login.php — blocks request, email, and key-based reset screens before core handles them.
	 */
	public function redirectLostPasswordRequests(): void {
		$action = isset( $_REQUEST['action'] ) && is_string( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';

		if ( $action === 'checkemail' ) {
			$checkemail = isset( $_GET['checkemail'] ) && is_string( $_GET['checkemail'] ) ? sanitize_key( wp_unslash( $_GET['checkemail'] ) ) : '';
			if ( $checkemail !== 'confirm' ) {
				return;
			}
		} elseif ( ! in_array( $action, [ 'lostpassword', 'retrievepassword', 'rp', 'resetpass' ], true ) ) {
			return;
		}

		wp_safe_redirect( $this->getLoginRedirectWithNotice() );
		exit;
	}

	public function filterLoginMessage( string $message ): string {
		if ( ! isset( $_GET[ self::DISABLED_QUERY_ARG ] ) || (string) wp_unslash( $_GET[ self::DISABLED_QUERY_ARG ] ) !== '1' ) {
			return $message;
		}

		if ( ! function_exists( 'wp_get_admin_notice' ) ) {
			return $message . '<p class="message">' . esc_html( wp_strip_all_tags( $this->getDisabledMessage() ) ) . '</p>';
		}

		return $message . wp_get_admin_notice(
			esc_html( wp_strip_all_tags( $this->getDisabledMessage() ) ),
			[
				'type'               => 'info',
				'additional_classes' => [ 'message' ],
			]
		);
	}

	/**
	 * @param string        $url  Prior secure-account URL.
	 * @param \WP_User      $user User who logged in.
	 * @param string        $ip   IP address from the notification.
	 */
	public function filterLoginEmailSecureAccountUrl( string $url, \WP_User $user, string $ip ): string {
		unset( $user, $ip );

		return $this->getLoginRedirectWithNotice();
	}

	/**
	 * Runs only on wp-login.php — hides the lost-password nav link without hiding registration.
	 */
	public function hideLostPasswordLinkStyles(): void {
		$css = '.login #nav a.wp-login-lost-password,.login #nav a[href*="action=lostpassword"],.login #backtoblog a[href*="lostpassword"]{display:none!important;}';

		wp_add_inline_style( 'login', $css );
	}

	public function getDisabledMessage(): string {
		$settings = $this->getSettings();
		$custom   = trim( (string) ( $settings['disabled_message'] ?? '' ) );

		if ( $custom !== '' ) {
			return $custom;
		}

		return __( 'Password recovery by email is disabled on this site. Contact an administrator if you need access.', 'wp-powersuite' );
	}

	private function getLoginRedirectWithNotice( string $redirect = '' ): string {
		$url = wp_login_url( $redirect );

		return add_query_arg( self::DISABLED_QUERY_ARG, '1', $url );
	}

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

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

		return $this->settings_cache;
	}

	private function shouldAllowAdminPasswordResetEmail(): bool {
		$settings = $this->getSettings();
		if ( empty( $settings['allow_admin_reset_emails'] ) ) {
			return false;
		}

		if ( ! is_user_logged_in() || ! current_user_can( 'edit_users' ) ) {
			return false;
		}

		if ( wp_doing_ajax() ) {
			$action = isset( $_REQUEST['action'] ) && is_string( $_REQUEST['action'] )
				? sanitize_key( wp_unslash( $_REQUEST['action'] ) )
				: '';

			return $action === 'send-password-reset';
		}

		if ( is_admin() && isset( $_REQUEST['action'] ) && is_string( $_REQUEST['action'] ) ) {
			return sanitize_key( wp_unslash( $_REQUEST['action'] ) ) === 'resetpassword';
		}

		return false;
	}

	/**
	 * Lost-password UI is mainly relevant on WooCommerce login surfaces; avoid frontend CSS elsewhere.
	 */
	private function shouldEnqueueFrontendLostPasswordHideStyles(): bool {
		if ( ! function_exists( 'is_account_page' ) ) {
			return false;
		}

		return is_account_page() || is_checkout() || is_cart();
	}
}
