<?php
/**
 * Centralized settings schema and validation.
 *
 * @package ZA_Creative_Login_Shield
 */

defined( 'ABSPATH' ) || exit;

/**
 * Validates and sanitizes plugin settings against a central schema.
 */
class ZACLS_Settings {

	/**
	 * Reserved slugs that cannot be used as the custom login URL.
	 *
	 * @return string[]
	 */
	public static function reserved_login_slugs() {
		return array(
			'wp-admin',
			'wp-login',
			'wp-content',
			'wp-includes',
			'feed',
			'api',
			'xmlrpc',
			'trackback',
			'wp-json',
			'robots',
			'admin',
			'login',
			'dashboard',
			'wp-cron',
		);
	}

	/**
	 * The settings schema.
	 *
	 * @return array
	 */
	public static function schema() {
		return array(
			'zacls_2fa_enabled'          => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_2fa_forced_roles'     => array( 'type' => 'string_array' ),
			'zacls_webauthn_enabled'     => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_webauthn_bypasses_2fa'=> array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_block_wp_login'       => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_redirect_unauthorized'=> array( 'type' => 'string', 'allowed' => array( 'homepage', '404' ) ),
			'zacls_custom_login_slug'    => array( 'type' => 'slug' ),
			'zacls_device_tracking'      => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_cloudflare_enabled'   => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_geoip_enabled'        => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_tor_detection'        => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_report_frequency'     => array( 'type' => 'string', 'allowed' => array( 'none', 'daily', 'weekly', 'monthly' ) ),
			'zacls_cleanup_frequency'    => array( 'type' => 'string', 'allowed' => array( 'daily', 'weekly', 'monthly' ) ),
			'zacls_cleanup_retention_days' => array( 'type' => 'integer', 'min' => 7, 'max' => 365 ),
			'zacls_session_timeout'      => array( 'type' => 'integer', 'min' => 5, 'max' => 10080 ),
			'zacls_session_inactive_limit' => array( 'type' => 'integer', 'min' => 1, 'max' => 1440 ),
			'zacls_auto_block_threshold' => array( 'type' => 'integer', 'min' => 1, 'max' => 1000 ),
			'zacls_auto_block_duration'  => array( 'type' => 'integer', 'min' => 5, 'max' => 1440 ),
			'zacls_admin_email'          => array( 'type' => 'email' ),
			'zacls_onboarding_completed' => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
			'zacls_delete_data_on_uninstall' => array( 'type' => 'string', 'allowed' => array( 'yes', 'no' ) ),
		);
	}

	/**
	 * Sanitize a single setting value against the schema.
	 *
	 * @param string $key   Setting name.
	 * @param mixed  $value Raw value.
	 * @return mixed Sanitized value (or schema default when invalid).
	 */
	public static function sanitize( $key, $value ) {
		$schema = self::schema();

		if ( ! isset( $schema[ $key ] ) ) {
			return $value;
		}

		$rules = $schema[ $key ];
		$type  = $rules['type'];

		switch ( $type ) {
			case 'string':
				$value = sanitize_text_field( (string) $value );
				if ( ! empty( $rules['allowed'] ) && ! in_array( $value, $rules['allowed'], true ) ) {
					return isset( $rules['default'] ) ? $rules['default'] : '';
				}
				return $value;

			case 'string_array':
				$values = array();
				foreach ( (array) $value as $item ) {
					$item = sanitize_text_field( (string) $item );
					if ( '' !== $item ) {
						$values[] = $item;
					}
				}
				return array_values( array_unique( $values ) );

			case 'integer':
				$int = (int) $value;
				if ( isset( $rules['min'] ) && $int < $rules['min'] ) {
					$int = $rules['min'];
				}
				if ( isset( $rules['max'] ) && $int > $rules['max'] ) {
					$int = $rules['max'];
				}
				return $int;

			case 'email':
				$email = sanitize_email( (string) $value );
				return $email;

			case 'slug':
				$slug = sanitize_title( (string) $value );
				$slug = strtolower( trim( $slug, '/' ) );
				if ( '' === $slug || in_array( $slug, self::reserved_login_slugs(), true ) ) {
					return '';
				}
				return $slug;

			default:
				return sanitize_text_field( (string) $value );
		}
	}

	/**
	 * Sanitize a list of trusted proxy entries (IPs and CIDR ranges).
	 *
	 * @param mixed $value Raw list.
	 * @return string[]
	 */
	public static function sanitize_trusted_proxies( $value ) {
		$list = array();
		foreach ( (array) $value as $entry ) {
			$entry = trim( sanitize_text_field( (string) $entry ) );
			if ( '' === $entry ) {
				continue;
			}
			if ( false !== strpos( $entry, '/' ) ) {
				$parts = explode( '/', $entry, 2 );
				if ( filter_var( $parts[0], FILTER_VALIDATE_IP ) && (int) $parts[1] >= 0 && (int) $parts[1] <= ( false !== strpos( $entry, ':' ) ? 128 : 32 ) ) {
					$list[] = $parts[0] . '/' . (int) $parts[1];
				}
			} elseif ( filter_var( $entry, FILTER_VALIDATE_IP ) ) {
				$list[] = $entry;
			}
		}
		return array_values( array_unique( $list ) );
	}

	/**
	 * Sanitize the login limits option array.
	 *
	 * @param mixed $value Raw value.
	 * @return array
	 */
	public static function sanitize_login_limits( $value ) {
		$value = is_array( $value ) ? $value : array();
		return array(
			'max_attempts' => min( max( (int) ( $value['max_attempts'] ?? 0 ), 0 ), 100 ),
			'time_window'  => min( max( (int) ( $value['time_window'] ?? 15 ), 1 ), 120 ),
			'lockout_time' => min( max( (int) ( $value['lockout_time'] ?? 15 ), 1 ), 1440 ),
		);
	}

	/**
	 * Sanitize the password policy option array.
	 *
	 * @param mixed $value Raw value.
	 * @return array
	 */
	public static function sanitize_password_policy( $value ) {
		$value    = is_array( $value ) ? $value : array();
		$existing = get_option( 'zacls_password_policy', array() );
		if ( ! is_array( $existing ) ) {
			$existing = array();
		}

		$merged = wp_parse_args( $value, $existing );

		$policy = array(
			'min_length'      => min( max( (int) ( $merged['min_length'] ?? 0 ), 0 ), 64 ),
			'require_upper'   => 'yes' === ( $merged['require_upper'] ?? 'no' ) ? 'yes' : 'no',
			'require_lower'   => 'yes' === ( $merged['require_lower'] ?? 'no' ) ? 'yes' : 'no',
			'require_number'  => 'yes' === ( $merged['require_number'] ?? 'no' ) ? 'yes' : 'no',
			'require_special' => 'yes' === ( $merged['require_special'] ?? 'no' ) ? 'yes' : 'no',
			'history_count'   => min( max( (int) ( $merged['history_count'] ?? 0 ), 0 ), 24 ),
			'expiration_days' => min( max( (int) ( $merged['expiration_days'] ?? 0 ), 0 ), 365 ),
			'block_common'    => 'yes' === ( $merged['block_common'] ?? 'no' ) ? 'yes' : 'no',
		);

		return $policy;
	}

	/**
	 * Sanitize the email notifications option array.
	 *
	 * @param mixed $value Raw value.
	 * @return array
	 */
	public static function sanitize_email_notifications( $value ) {
		$value = is_array( $value ) ? $value : array();
		$out   = array();
		foreach ( array( 'failed_login', 'new_ip_login', 'ip_blocked' ) as $key ) {
			$out[ $key ] = 'yes' === ( $value[ $key ] ?? 'no' ) ? 'yes' : 'no';
		}
		return $out;
	}
}