<?php

declare( strict_types=1 );

namespace Simplyin\Simplyin_W_Plugin\Tests\Unit;

use Brain\Monkey;
use Brain\Monkey\Functions;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionMethod;
use Simplyin\Simplyin_W_Plugin\Payments\Gateway\Simplyin_Gateway;

/**
 * Webhook HMAC receiver contract tests. Covers Simplyin_Gateway::verify_webhook_signature()
 * behaviour: replay window, missing secret/headers, malformed timestamp, signature mismatch
 * and the case-insensitive hex comparison.
 */
final class Webhook_Hmac_Test extends TestCase {

	private const SECRET = 'whsec_test_0123456789abcdef0123456789abcdef';
	private const RAW_BODY = '{"merchantPaymentId":"pay_123","status":"PAID","amount":1999,"currency":"PLN"}';

	protected function setUp(): void {
		parent::setUp();
		Monkey\setUp();
		// Headers are read from $_SERVER; ensure a clean slate per test.
		unset( $_SERVER['HTTP_X_SIMPLYIN_WEBHOOK_SIGNATURE'], $_SERVER['HTTP_X_SIMPLYIN_WEBHOOK_TIMESTAMP'] );
		// verify_webhook_signature reads these crypto headers raw via wp_unslash.
		Functions\when( 'wp_unslash' )->returnArg();
	}

	protected function tearDown(): void {
		unset( $_SERVER['HTTP_X_SIMPLYIN_WEBHOOK_SIGNATURE'], $_SERVER['HTTP_X_SIMPLYIN_WEBHOOK_TIMESTAMP'] );
		Monkey\tearDown();
		parent::tearDown();
	}

	public function test_hmac_matches_backend_golden_vector(): void {
		$timestamp = '1750000000';

		$expected = '8eb4a499a7e12c06a647b5a5e756d5fbd8a719f36d89a62db9693e6b2cd5e65e';

		$this->assertSame(
			$expected,
			hash_hmac( 'sha256', $timestamp . '.' . self::RAW_BODY, self::SECRET )
		);
	}

	public function test_accepts_a_valid_signature(): void {
		$this->withSecret( self::SECRET );
		$timestamp = (string) time();
		$this->setHeaders( $this->sign( $timestamp ), $timestamp );

		$this->assertTrue( $this->verify( self::RAW_BODY ) );
	}

	public function test_accepts_uppercase_hex_signature(): void {
		// hash_equals is case-sensitive; the receiver normalizes case so an uppercase-hex
		// sender is not wrongly rejected (review #190 #6).
		$this->withSecret( self::SECRET );
		$timestamp = (string) time();
		$this->setHeaders( strtoupper( $this->sign( $timestamp ) ), $timestamp );

		$this->assertTrue( $this->verify( self::RAW_BODY ) );
	}

	public function test_rejects_when_secret_is_not_configured(): void {
		$this->withSecret( '' );
		$timestamp = (string) time();
		$this->setHeaders( $this->sign( $timestamp ), $timestamp );

		$this->assertFalse( $this->verify( self::RAW_BODY ) );
	}

	public function test_rejects_missing_headers(): void {
		$this->withSecret( self::SECRET );
		// No headers set.
		$this->assertFalse( $this->verify( self::RAW_BODY ) );
	}

	public function test_rejects_a_wrong_signature(): void {
		$this->withSecret( self::SECRET );
		$timestamp = (string) time();
		$this->setHeaders( str_repeat( 'a', 64 ), $timestamp );

		$this->assertFalse( $this->verify( self::RAW_BODY ) );
	}

	public function test_rejects_a_non_digit_timestamp(): void {
		$this->withSecret( self::SECRET );
		$timestamp = '17500000xx';
		$this->setHeaders( $this->sign( $timestamp ), $timestamp );

		$this->assertFalse( $this->verify( self::RAW_BODY ) );
	}

	public function test_rejects_a_stale_timestamp_replay(): void {
		$this->withSecret( self::SECRET );
		// 1000s in the past — beyond the ±900s freshness window.
		$timestamp = (string) ( time() - 1000 );
		$this->setHeaders( $this->sign( $timestamp ), $timestamp );

		$this->assertFalse( $this->verify( self::RAW_BODY ) );
	}

	private function withSecret( string $secret ): void {
		Functions\when( 'get_option' )->alias(
			static function ( $name, $default = '' ) use ( $secret ) {
				return 'simplyin_plugin_payment_webhook_secret' === $name ? $secret : $default;
			}
		);
	}

	private function sign( string $timestamp ): string {
		return hash_hmac( 'sha256', $timestamp . '.' . self::RAW_BODY, self::SECRET );
	}

	private function setHeaders( string $signature, string $timestamp ): void {
		$_SERVER['HTTP_X_SIMPLYIN_WEBHOOK_SIGNATURE'] = $signature;
		$_SERVER['HTTP_X_SIMPLYIN_WEBHOOK_TIMESTAMP'] = $timestamp;
	}

	private function verify( string $raw_body ): bool {
		$gateway = ( new ReflectionClass( Simplyin_Gateway::class ) )->newInstanceWithoutConstructor();
		$method  = new ReflectionMethod( Simplyin_Gateway::class, 'verify_webhook_signature' );
		$method->setAccessible( true ); // Required on the plugin's PHP floor (7.4); harmless no-op on 8.1+.

		return (bool) $method->invoke( $gateway, $raw_body );
	}
}
