<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'ASA_Auth' ) ) {
class ASA_Auth {

	const REMOTE_HOST = 'aegisify.com';
	const REMOTE_SCHEME = 'https';
	const REMOTE_VERIFY_PATH = '/wp-json/aegisify-audit/v1/agent/verify';

	public function init() {
		// Intentionally no wp-admin credential sharing. This agent uses a dedicated security key over SSL.
	}

	public static function get_remote_host() {
		return self::REMOTE_SCHEME . '://' . self::REMOTE_HOST;
	}

	public static function get_security_key() {
		return self::normalize_key( (string) get_option( 'asa_security_key', '' ) );
	}

	public static function save_security_key( $key ) {
		$key = self::normalize_key( $key );
		update_option( 'asa_security_key', $key, false );
		update_option( 'asa_security_key_updated_at', current_time( 'mysql' ), false );
		delete_option( 'asa_agent_connection_status' );
		return $key;
	}

	public static function get_security_key_fingerprint() {
		$key = self::get_security_key();
		if ( '' === $key ) {
			return __( 'not set', 'aegisify-audit-agent' );
		}

		return substr( hash( 'sha256', $key ), 0, 32 );
	}

	private static function normalize_key( $key ) {
		$key = html_entity_decode( (string) $key, ENT_QUOTES, 'UTF-8' );
		$key = preg_replace( '/\s+/', '', (string) $key );
		return (string) $key;
	}

	private static function normalize_site_domain( $domain ) {
		$domain = trim( strtolower( (string) $domain ) );
		if ( '' === $domain ) {
			return '';
		}

		$parsed_host = wp_parse_url( $domain, PHP_URL_HOST );
		if ( is_string( $parsed_host ) && '' !== $parsed_host ) {
			$domain = $parsed_host;
		} else {
			$domain = preg_replace( '#^https?://#', '', $domain );
			$domain = preg_replace( '#/.*$#', '', $domain );
		}

		$domain = preg_replace( '#:\d+$#', '', (string) $domain );
		$domain = preg_replace( '/^www\./', '', (string) $domain );

		return sanitize_text_field( (string) $domain );
	}

	public static function get_connection_status() {
		$status = get_option( 'asa_agent_connection_status', array() );

		return is_array( $status ) ? $status : array();
	}

	private static function get_remote_address() {
		return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
	}

	private static function log_request_validation( WP_REST_Request $request, $status, $message, array $context = array(), $dedupe_window = 15 ) {
		ASA_Logger::log(
			'security',
			$status,
			'auth',
			'validate_request',
			$message,
			array_merge(
				array(
					'method'      => strtoupper( (string) $request->get_method() ),
					'route'       => (string) $request->get_route(),
					'remote_addr' => self::get_remote_address(),
				),
				$context
			),
			$dedupe_window
		);
	}

	private static function decode_key_payload( $key ) {
		$key = trim( (string) $key );
		if ( '' === $key ) {
			return array();
		}

		$segments = explode( '.', $key );
		$candidates = array( $key );

		if ( count( $segments ) >= 2 ) {
			$candidates[] = $segments[1];
			$candidates[] = $segments[0];
		}

		foreach ( $candidates as $candidate ) {
			$candidate = strtr( (string) $candidate, '-_', '+/' );
			$padding   = strlen( $candidate ) % 4;
			if ( $padding > 0 ) {
				$candidate .= str_repeat( '=', 4 - $padding );
			}
			$decoded = base64_decode( $candidate, true );
			if ( false === $decoded ) {
				continue;
			}
			$data = json_decode( $decoded, true );
			if ( is_array( $data ) ) {
				return $data;
			}
		}

		return array();
	}

	private static function key_targets_aegisify( $key ) {
		$payload = self::decode_key_payload( $key );
		if ( ! empty( $payload['destination'] ) ) {
			$host = wp_parse_url( (string) $payload['destination'], PHP_URL_HOST );
			if ( empty( $host ) || self::REMOTE_HOST !== strtolower( (string) $host ) ) {
				return false;
			}
		}
		if ( ! empty( $payload['host'] ) && self::REMOTE_HOST !== strtolower( (string) $payload['host'] ) ) {
			return false;
		}
		if ( ! empty( $payload['domain'] ) ) {
			$domain = self::normalize_site_domain( (string) $payload['domain'] );
			$home   = self::normalize_site_domain( home_url() );
			if ( '' === $domain || '' === $home || $domain !== $home ) {
				return false;
			}
		}

		return true;
	}

	public static function refresh_connection_status() {
		$key = self::get_security_key();
		$status = array(
			'verified'   => false,
			'message'    => __( 'Encryption key has not been validated yet.', 'aegisify-audit-agent' ),
			'checked_at' => current_time( 'mysql' ),
			'remote'     => self::get_remote_host(),
		);

		if ( '' === $key ) {
			$status['message'] = __( 'Encryption key is empty.', 'aegisify-audit-agent' );
			update_option( 'asa_agent_connection_status', $status, false );
			ASA_Logger::log(
				'connectivity',
				'warning',
				'auth',
				'refresh_connection_status',
				__( 'Connection verification was skipped because the agent security key is empty.', 'aegisify-audit-agent' ),
				array( 'remote' => $status['remote'] ),
				30
			);
			return $status;
		}

		if ( ! self::key_targets_aegisify( $key ) ) {
			$status['message'] = __( 'Connection blocked. The encryption key does not target aegisify.com or the verified domain payload does not match this site.', 'aegisify-audit-agent' );
			update_option( 'asa_agent_connection_status', $status, false );
			ASA_Logger::log(
				'connectivity',
				'error',
				'auth',
				'refresh_connection_status',
				__( 'Connection verification was blocked because the configured key does not match the expected SaaS destination or site domain.', 'aegisify-audit-agent' ),
				array( 'remote' => $status['remote'] ),
				30
			);
			return $status;
		}

		ASA_Logger::log(
			'network',
			'info',
			'auth',
			'verify_remote_connection',
			__( 'The agent is sending a verification request to Aegisify Audit SaaS.', 'aegisify-audit-agent' ),
			array(
				'remote'      => self::get_remote_host(),
				'verify_path' => self::REMOTE_VERIFY_PATH,
				'site'        => home_url(),
			),
			15
		);

		$response = wp_remote_post(
			self::get_remote_host() . self::REMOTE_VERIFY_PATH,
			array(
				'timeout'   => 15,
				'sslverify' => true,
				'headers'   => array(
					'Accept'                   => 'application/json',
					'Content-Type'             => 'application/json',
					'X-Aegisify-Agent-Key'     => $key,
					'X-Aegisify-Agent-Key-B64' => base64_encode( $key ),
				),
				'body'      => wp_json_encode(
					array(
						'domain'        => home_url(),
						'agent'         => 'aegisify-audit-agent',
						'version'       => ASA_VERSION,
						'agent_key'     => $key,
						'agent_key_b64' => base64_encode( $key ),
					)
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			/* translators: %s: WordPress HTTP API error message returned when SaaS verification fails. */
			$status['message'] = sprintf( __( 'Unable to verify against Aegisify Audit SaaS: %s', 'aegisify-audit-agent' ), $response->get_error_message() );
			update_option( 'asa_agent_connection_status', $status, false );
			ASA_Logger::log(
				'network',
				'error',
				'auth',
				'verify_remote_connection',
				__( 'The SaaS verification request failed at the network layer.', 'aegisify-audit-agent' ),
				array( 'message' => $response->get_error_message() ),
				15
			);
			return $status;
		}

		$code         = (int) wp_remote_retrieve_response_code( $response );
		$body         = json_decode( (string) wp_remote_retrieve_body( $response ), true );
		$body_message = is_array( $body ) && ! empty( $body['message'] ) ? sanitize_text_field( (string) $body['message'] ) : '';

		if ( 200 !== $code || ! is_array( $body ) ) {
			$status['message'] = '' !== $body_message ? $body_message : __( 'Aegisify Audit SaaS verification returned an unexpected response.', 'aegisify-audit-agent' );
			update_option( 'asa_agent_connection_status', $status, false );
			ASA_Logger::log(
				'network',
				'error',
				'auth',
				'verify_remote_connection',
				__( 'The SaaS verification request returned an unexpected response payload.', 'aegisify-audit-agent' ),
				array(
					'http_code' => $code,
					'message'   => $body_message,
				),
				15
			);
			return $status;
		}

		$verified_domain      = isset( $body['verified_domain'] ) ? (string) $body['verified_domain'] : '';
		$verified_domain_host = self::normalize_site_domain( $verified_domain );
		$current_domain       = home_url();
		$current_domain_host  = self::normalize_site_domain( $current_domain );
		$domain_match         = ( '' !== $verified_domain_host && '' !== $current_domain_host && $verified_domain_host === $current_domain_host );
		$is_verified          = ! empty( $body['verified'] ) && ! empty( $body['domain_verified'] ) && $domain_match;

		$status['verified']          = $is_verified;
		$status['domain_verified']   = ! empty( $body['domain_verified'] );
		$status['verified_domain']   = $verified_domain;
		$status['verified_host']     = $verified_domain_host;
		$status['current_host']      = $current_domain_host;
		$status['provider_settings'] = ! empty( $body['provider_settings'] ) && is_array( $body['provider_settings'] ) ? $body['provider_settings'] : array();
		if ( $is_verified ) {
			$status['message'] = __( 'Connected to Aegisify Audit SaaS and verified domain matched successfully.', 'aegisify-audit-agent' );
		} elseif ( '' !== $body_message ) {
			$status['message'] = $body_message;
		} elseif ( ! empty( $body['domain_verified'] ) && ! $domain_match ) {
			$status['message'] = __( 'Connection blocked. The verified SaaS domain does not match this WordPress site URL.', 'aegisify-audit-agent' );
		} else {
			$status['message'] = __( 'Connection blocked. This WordPress domain is not verified in the Aegisify Audit SaaS dashboard.', 'aegisify-audit-agent' );
		}

		update_option( 'asa_agent_connection_status', $status, false );
		ASA_Logger::log(
			'connectivity',
			$is_verified ? 'success' : 'error',
			'auth',
			'refresh_connection_status',
			$status['message'],
			array(
				'current_host'    => $current_domain_host,
				'verified_host'   => $verified_domain_host,
				'http_code'       => $code,
				'domain_verified' => ! empty( $body['domain_verified'] ) ? 'true' : 'false',
				'domain_match'    => $domain_match ? 'true' : 'false',
				'message'         => $body_message,
			),
			15
		);
		return $status;
	}

	private static function get_request_body_candidates( WP_REST_Request $request ) {
		$raw_body   = (string) $request->get_body();
		$candidates = array( $raw_body );

		if ( '' === $raw_body || 'GET' === strtoupper( (string) $request->get_method() ) ) {
			$candidates[] = '';
		}

		$parsed_params = $request->get_json_params();
		if ( is_array( $parsed_params ) && ! empty( $parsed_params ) ) {
			$normalized_json = wp_json_encode( $parsed_params );
			if ( false !== $normalized_json && '' !== $normalized_json ) {
				$candidates[] = $normalized_json;
			}
		}

		return array_values( array_unique( array_map( 'strval', $candidates ) ) );
	}

	private static function get_request_route_candidates( WP_REST_Request $request ) {
		$route      = (string) $request->get_route();
		$candidates = array( $route );

		if ( '' !== $route ) {
			$candidates[] = '/' . ltrim( $route, '/' );
			$candidates[] = untrailingslashit( '/' . ltrim( $route, '/' ) );
			$candidates[] = trailingslashit( '/' . ltrim( $route, '/' ) );
		}

		return array_values( array_unique( array_map( 'strval', $candidates ) ) );
	}

	private static function extract_request_key( WP_REST_Request $request ) {
		$raw_key = (string) $request->get_header( 'x-aegisify-agent-key' );
		if ( '' !== $raw_key ) {
			return self::normalize_key( $raw_key );
		}

		$raw_key_b64 = (string) $request->get_header( 'x-aegisify-agent-key-b64' );
		if ( '' === $raw_key_b64 ) {
			return '';
		}

		$decoded = base64_decode( strtr( $raw_key_b64, '-_', '+/' ), true );
		if ( false === $decoded ) {
			return '';
		}

		return self::normalize_key( $decoded );
	}

	private static function request_signature_is_valid( $timestamp, $method, array $routes, array $bodies, $key, $signature ) {
		foreach ( $routes as $route ) {
			foreach ( $bodies as $body ) {
				$expected = hash_hmac( 'sha256', $timestamp . "\n" . $method . "\n" . $route . "\n" . $body, $key );
				if ( hash_equals( $expected, $signature ) ) {
					return true;
				}
			}
		}

		return false;
	}

	private static function request_signature_has_been_seen( $timestamp, $signature ) {
		$cache_key = 'asa_sig_' . hash( 'sha256', (string) $timestamp . '|' . (string) $signature );
		if ( get_transient( $cache_key ) ) {
			return true;
		}

		$ttl = max( 1, (int) ( 2 * MINUTE_IN_SECONDS ) );
		set_transient( $cache_key, 1, $ttl );
		return false;
	}

	private static function request_route_allows_safe_retries( WP_REST_Request $request ) {
		$method = strtoupper( (string) $request->get_method() );
		if ( 'GET' === $method ) {
			return true;
		}

		$route = untrailingslashit( '/' . ltrim( (string) $request->get_route(), '/' ) );
		return in_array(
			$route,
			array(
				'/aegisify-agent/v1/report/full',
				'/aegisify-agent/v1/report/static',
				'/aegisify-agent/v1/report/vulnerability',
				'/aegisify-agent/v1/report/compliance',
				'/aegisify-agent/v1/telemetry',
				'/aegisify-agent/v1/capabilities',
				'/aegisify-agent/v1/logs/debug',
				'/aegisify-agent/v1/scan/manual/kickoff',
				'/aegisify-agent/v1/scan/static/kickoff',
				'/aegisify-agent/v1/scan/vulnerability/kickoff',
			),
			true
		);
	}

	private static function local_https_is_enforced() {
		$rest_url_host = (string) rest_url();
		$home          = (string) home_url();
		$server_https  = isset( $_SERVER['HTTPS'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTPS'] ) ) ) : '';
		$forwarded     = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) ) : '';

		if ( is_ssl() ) {
			return true;
		}

		if ( 'on' === $server_https || '1' === $server_https ) {
			return true;
		}

		if ( 'https' === $forwarded ) {
			return true;
		}

		return 0 === strpos( $rest_url_host, 'https://' ) && 0 === strpos( $home, 'https://' );
	}

	public static function validate_request( $request ) {
		$key = self::get_security_key();
		if ( '' === $key ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because the agent security key is missing.', 'aegisify-audit-agent' ) );
			return new WP_Error( 'asa_key_missing', __( 'The agent security key has not been configured yet.', 'aegisify-audit-agent' ), array( 'status' => 403 ) );
		}
		if ( ! self::local_https_is_enforced() ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because HTTPS is not enforced locally.', 'aegisify-audit-agent' ) );
			return new WP_Error( 'asa_ssl_required', __( 'The agent requires SSL / HTTPS for remote scan requests.', 'aegisify-audit-agent' ), array( 'status' => 403 ) );
		}

		if ( ! self::key_targets_aegisify( $key ) ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because the configured key does not target the expected SaaS destination or site domain.', 'aegisify-audit-agent' ) );
			return new WP_Error( 'asa_remote_invalid', __( 'The configured agent key is not bound to aegisify.com or this local domain.', 'aegisify-audit-agent' ), array( 'status' => 403 ) );
		}

		$header_key = self::extract_request_key( $request );
		$timestamp  = (string) $request->get_header( 'x-aegisify-agent-timestamp' );
		$signature  = (string) $request->get_header( 'x-aegisify-agent-signature' );
		$origin     = (string) $request->get_header( 'origin' );
		$referer    = (string) $request->get_header( 'referer' );
		if ( '' === $header_key || '' === $timestamp || '' === $signature ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because required authentication headers were missing.', 'aegisify-audit-agent' ) );
			return new WP_Error( 'asa_auth_required', __( 'Missing agent authentication headers.', 'aegisify-audit-agent' ), array( 'status' => 401 ) );
		}
		if ( ! hash_equals( $key, $header_key ) ) {
			$provided_fingerprint = substr( hash( 'sha256', $header_key ), 0, 32 );
			$local_fingerprint    = substr( hash( 'sha256', $key ), 0, 32 );
			self::log_request_validation(
				$request,
				'error',
				__( 'Remote request denied because the supplied agent key did not match the locally saved key.', 'aegisify-audit-agent' ),
				array(
					'provided_fingerprint' => $provided_fingerprint,
					'local_fingerprint'    => $local_fingerprint,
				)
			);
			return new WP_Error(
				'asa_key_invalid',
				__( 'Invalid agent key. Paste the current Encryption / Security Key into the Agent, save it, then retry.', 'aegisify-audit-agent' ),
				array(
					'status'               => 403,
					'provided_fingerprint' => $provided_fingerprint,
					'local_fingerprint'    => $local_fingerprint,
				)
			);
		}
		if ( '' !== $origin ) {
			$origin_host = wp_parse_url( $origin, PHP_URL_HOST );
			if ( self::REMOTE_HOST !== strtolower( (string) $origin_host ) ) {
				self::log_request_validation( $request, 'error', __( 'Remote request denied because the Origin host is not allowed.', 'aegisify-audit-agent' ), array( 'origin_host' => (string) $origin_host ) );
				return new WP_Error( 'asa_origin_invalid', __( 'Remote origin is not allowed.', 'aegisify-audit-agent' ), array( 'status' => 403 ) );
			}
		}
		if ( '' !== $referer ) {
			$referer_host = wp_parse_url( $referer, PHP_URL_HOST );
			if ( self::REMOTE_HOST !== strtolower( (string) $referer_host ) ) {
				self::log_request_validation( $request, 'error', __( 'Remote request denied because the Referer host is not allowed.', 'aegisify-audit-agent' ), array( 'referer_host' => (string) $referer_host ) );
				return new WP_Error( 'asa_referer_invalid', __( 'Remote referer is not allowed.', 'aegisify-audit-agent' ), array( 'status' => 403 ) );
			}
		}
		$time = absint( $timestamp );
		if ( $time < ( time() - 300 ) || $time > ( time() + 300 ) ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because the signed timestamp is outside the allowed window.', 'aegisify-audit-agent' ), array( 'timestamp' => $time ) );
			return new WP_Error( 'asa_timestamp_invalid', __( 'The signed request timestamp is outside the allowed window.', 'aegisify-audit-agent' ), array( 'status' => 401 ) );
		}

		$method = strtoupper( (string) $request->get_method() );
		$routes = self::get_request_route_candidates( $request );
		$bodies = self::get_request_body_candidates( $request );

		if ( ! self::request_signature_is_valid( $timestamp, $method, $routes, $bodies, $key, $signature ) ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because the signed request signature is invalid.', 'aegisify-audit-agent' ) );
			return new WP_Error( 'asa_signature_invalid', __( 'The request signature is invalid.', 'aegisify-audit-agent' ), array( 'status' => 401 ) );
		}

		$connection_status = self::refresh_connection_status();
		if ( empty( $connection_status['verified'] ) ) {
			$message = ! empty( $connection_status['message'] ) ? (string) $connection_status['message'] : __( 'This site is not verified in Aegisify Audit SaaS. Remote connection refused.', 'aegisify-audit-agent' );
			self::log_request_validation( $request, 'error', __( 'Remote request denied because the site is not verified in Aegisify Audit SaaS.', 'aegisify-audit-agent' ), array( 'message' => $message ) );
			return new WP_Error( 'asa_domain_not_verified', $message, array( 'status' => 403 ) );
		}

		$signature_seen = self::request_signature_has_been_seen( $timestamp, $signature );
		$allows_retry   = self::request_route_allows_safe_retries( $request );

		if ( $signature_seen && ! $allows_retry ) {
			self::log_request_validation( $request, 'error', __( 'Remote request denied because a replayed signature was detected.', 'aegisify-audit-agent' ), array( 'timestamp' => $time ) );
			return new WP_Error( 'asa_replay_blocked', __( 'This signed request has already been used. Replay blocked.', 'aegisify-audit-agent' ), array( 'status' => 401 ) );
		}

		if ( $signature_seen && $allows_retry ) {
			self::log_request_validation( $request, 'warning', __( 'A duplicate signed request was allowed because this authenticated route only returns cached or read-only data.', 'aegisify-audit-agent' ), array( 'timestamp' => $time ) );
		}

		update_option( 'asa_last_seen_at', current_time( 'mysql' ), false );
		if ( class_exists( 'AegisWAF_Whitelist' ) && method_exists( 'AegisWAF_Whitelist', 'allow_current_request_for_service' ) ) {
			AegisWAF_Whitelist::allow_current_request_for_service(
				'aegisify_audit_agent',
				array(
					'route'  => (string) $request->get_route(),
					'method' => $method,
				)
			);
		}
		self::log_request_validation( $request, 'success', __( 'Remote request authentication completed successfully.', 'aegisify-audit-agent' ), array( 'timestamp' => $time ), 10 );
		return true;
	}
}
}
