<?php

declare( strict_types=1 );

namespace Simplyin\Simplyin_W_Plugin\Tests\Unit;

use Brain\Monkey;
use Brain\Monkey\Functions;
use GuzzleHttp\Psr7\Request;
use PHPUnit\Framework\TestCase;
use Simplyin\Simplyin_W_Plugin\Lib\Remote\Sin_Core_Api\Plugin_Rsa_Guzzle_Client;
use Simplyin\Simplyin_W_Plugin\Lib\Remote\Sin_Core_Api\Plugin_Rsa_Signer;

/**
 * Auth boundary for the generated PaymentsApi Guzzle client: the mapRequest
 * middleware must preserve the request body, and in RSA mode swap the legacy API-key header
 * for the four RSA headers; when RSA is inactive it must pass the request through untouched.
 */
final class Plugin_Rsa_Guzzle_Client_Test extends TestCase {

	protected function setUp(): void {
		parent::setUp();
		Monkey\setUp();
	}

	protected function tearDown(): void {
		Monkey\tearDown();
		parent::tearDown();
	}

	public function test_passes_request_through_untouched_when_rsa_inactive(): void {
		// No key / credential stored -> is_active() false -> legacy API-key auth stays intact.
		Functions\when( 'get_option' )->alias(
			static function ( $name, $default = '' ) {
				return $default;
			}
		);

		$request = new Request(
			'POST',
			'https://api.example/api/pay?foo=bar',
			[ 'X-Auth-Merchant-Api-Key' => 'legacy' ],
			'{"a":1}'
		);

		$out = Plugin_Rsa_Guzzle_Client::sign_request( $request );

		$this->assertSame( 'legacy', $out->getHeaderLine( 'X-Auth-Merchant-Api-Key' ) );
		$this->assertSame( '', $out->getHeaderLine( Plugin_Rsa_Signer::HEADER_SIGNATURE ) );
		$this->assertSame( '{"a":1}', (string) $out->getBody() );
	}

	public function test_signs_swaps_headers_and_preserves_body_when_active(): void {
		if ( ! function_exists( 'openssl_pkey_new' ) ) {
			$this->markTestSkipped( 'OpenSSL extension is required to exercise RSA signing.' );
		}

		$key = openssl_pkey_new(
			[
				'private_key_bits' => 2048,
				'private_key_type' => OPENSSL_KEYTYPE_RSA,
			]
		);
		openssl_pkey_export( $key, $private_pem );
		$public_pem = openssl_pkey_get_details( $key )['key'];

		Functions\when( 'get_option' )->alias(
			static function ( $name, $default = '' ) use ( $private_pem ) {
				switch ( $name ) {
					case Plugin_Rsa_Signer::OPTION_CREDENTIAL_ID:
						return 'cred_1';
					case Plugin_Rsa_Signer::OPTION_PRIVATE_KEY:
						return $private_pem;
					default:
						return $default;
				}
			}
		);

		$body    = '{"amount":1999}';
		$request = new Request(
			'POST',
			'https://api.example/api/pay?foo=bar',
			[ 'X-Auth-Merchant-Api-Key' => 'legacy' ],
			$body
		);

		$out = Plugin_Rsa_Guzzle_Client::sign_request( $request );

		// Legacy key dropped, the four RSA headers added, body preserved (not consumed).
		$this->assertFalse( $out->hasHeader( 'X-Auth-Merchant-Api-Key' ) );
		$this->assertSame( 'cred_1', $out->getHeaderLine( Plugin_Rsa_Signer::HEADER_CREDENTIAL_ID ) );
		$this->assertNotSame( '', $out->getHeaderLine( Plugin_Rsa_Signer::HEADER_SIGNATURE ) );
		$this->assertNotSame( '', $out->getHeaderLine( Plugin_Rsa_Signer::HEADER_NONCE ) );
		$this->assertSame( $body, (string) $out->getBody() );

		// The emitted signature must verify over the canonical string of the transmitted request.
		$canonical = implode(
			"\n",
			[
				$out->getHeaderLine( Plugin_Rsa_Signer::HEADER_TIMESTAMP ),
				'POST',
				'/api/pay?foo=bar',
				hash( 'sha256', $body ),
				$out->getHeaderLine( Plugin_Rsa_Signer::HEADER_NONCE ),
			]
		);
		$signature = base64_decode( $out->getHeaderLine( Plugin_Rsa_Signer::HEADER_SIGNATURE ), true );
		$this->assertNotFalse( $signature, 'Signature header must be valid base64.' );
		$this->assertSame( 1, openssl_verify( $canonical, $signature, $public_pem, OPENSSL_ALGO_SHA256 ) );
	}
}
