<?php

namespace WPProofShield\Security;

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


use WP_Error;
use WPProofShield\Support\Settings;

final class TokenService {

	public function __construct( private Settings $settings ) {
	}

	public function issue( string $context, int $difficulty, ?string $honeypotField = null ): string {

		$iat           = time();
		$payload       = array(
			'v'     => 1,
			'ctx'   => $context,
			'nonce' => rtrim( strtr( base64_encode( random_bytes( 16 ) ), '+/', '-_' ), '=' ),
			'iat'   => $iat,
			'exp'   => $iat + $this->settings->maxAgeSeconds(),
			'diff'  => $difficulty,
			'ipb'   => $this->bindFingerprint( $this->remoteIp() ),
			'ua'    => $this->bindFingerprint( $this->userAgent() ),
		);
		$honeypotField = $this->sanitizeHoneypotField( $honeypotField );
		if ( $honeypotField !== '' ) {
			$payload['hpn'] = $honeypotField;
		}
		$payloadEncoded = self::base64UrlEncode( wp_json_encode( $payload ) );
		$sig            = hash_hmac( 'sha256', $payloadEncoded, wp_salt( 'auth' ) );

		return $payloadEncoded . '.' . $sig;
	}

	public function parseAndVerify( string $token ): array|WP_Error {
		$parts = explode( '.', $token, 2 );
		if ( count( $parts ) !== 2 ) {
			return new WP_Error( 'wpsh_pow_invalid', __( 'Invalid ProofShield token format.', 'proofshield' ) );
		}

		[$payloadEncoded, $sig] = $parts;
		$expected               = hash_hmac( 'sha256', $payloadEncoded, wp_salt( 'auth' ) );
		if ( ! hash_equals( $expected, $sig ) ) {
			return new WP_Error( 'wpsh_pow_invalid', __( 'Invalid ProofShield token signature.', 'proofshield' ) );
		}

		$json = self::base64UrlDecode( $payloadEncoded );
		if ( $json === false ) {
			return new WP_Error( 'wpsh_pow_invalid', __( 'Invalid ProofShield token payload.', 'proofshield' ) );
		}

		$payload = json_decode( $json, true );
		if ( ! is_array( $payload ) ) {
			return new WP_Error( 'wpsh_pow_invalid', __( 'Malformed ProofShield token payload.', 'proofshield' ) );
		}

		foreach ( array( 'v', 'ctx', 'nonce', 'iat', 'exp', 'diff', 'ipb', 'ua' ) as $key ) {
			if ( ! array_key_exists( $key, $payload ) ) {
				return new WP_Error( 'wpsh_pow_invalid', __( 'Missing token field.', 'proofshield' ) );
			}
		}

		$now = time();
		if ( $now < ( (int) $payload['iat'] - 15 ) || $now > ( (int) $payload['exp'] + 15 ) ) {
			return new WP_Error( 'wpsh_expired', __( 'ProofShield challenge expired.', 'proofshield' ) );
		}

		if ( ! hash_equals( (string) $payload['ipb'], $this->bindFingerprint( $this->remoteIp() ) ) ) {
			return new WP_Error( 'wpsh_pow_invalid', __( 'IP mismatch for ProofShield token.', 'proofshield' ) );
		}

		if ( ! hash_equals( (string) $payload['ua'], $this->bindFingerprint( $this->userAgent() ) ) ) {
			return new WP_Error( 'wpsh_pow_invalid', __( 'Browser fingerprint mismatch for ProofShield token.', 'proofshield' ) );
		}

		$payload['_encoded'] = $payloadEncoded;
		return $payload;
	}

	public static function base64UrlEncode( string $data ): string {
		return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
	}

	public static function base64UrlDecode( string $data ): string|false {
		$padding = strlen( $data ) % 4;
		if ( $padding > 0 ) {
			$data .= str_repeat( '=', 4 - $padding );
		}
		return base64_decode( strtr( $data, '-_', '+/' ), true );
	}

	private function bindFingerprint( string $value ): string {
		return substr( hash_hmac( 'sha256', $value, wp_salt( 'secure_auth' ) ), 0, 24 );
	}

	private function remoteIp(): string {
		$candidate = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) : '';
		if ( $this->isLocalHost() ) {
			$sim = isset( $_SERVER['HTTP_X_WPSH_SIM_IP'] ) ? trim( sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_X_WPSH_SIM_IP'] ) ) ) : '';
			if ( filter_var( $sim, FILTER_VALIDATE_IP ) ) {
				$candidate = $sim;
			}
		}

		if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
			$parts     = explode( ',', sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
			$forwarded = trim( (string) ( $parts[0] ?? '' ) );
			if ( filter_var( $forwarded, FILTER_VALIDATE_IP ) ) {
				$candidate = $forwarded;
			}
		}

		return filter_var( $candidate, FILTER_VALIDATE_IP ) ? $candidate : '0.0.0.0';
	}

	private function isLocalHost(): bool {
		$host = (string) wp_parse_url( home_url( '/' ), PHP_URL_HOST );
		return in_array( $host, array( 'localhost', '127.0.0.1', '::1' ), true );
	}

	private function userAgent(): string {

		return isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_USER_AGENT'] ) ) : 'unknown';
	}

	private function sanitizeHoneypotField( ?string $name ): string {

		if ( ! is_string( $name ) ) {
			return '';
		}
		$name = sanitize_key( $name );
		if ( $name === '' || strlen( $name ) < 3 || strlen( $name ) > 32 ) {
			return '';
		}
		if ( ! preg_match( '/^[a-z][a-z0-9_]+$/', $name ) ) {
			return '';
		}
		return $name;
	}
}
