<?php

declare( strict_types=1 );

namespace Simplyin\Simplyin_W_Plugin\Tests\Unit;

use Brain\Monkey;
use Brain\Monkey\Functions;
use PHPUnit\Framework\TestCase;
use Simplyin\Simplyin_W_Plugin\Sync\Simplyin_Sync;

/**
 * persist_webhook_signing_secret() contract: the single shared writer for the per-shop HMAC
 * webhook secret, called from every code path that can receive it so
 * `simplyin_plugin_payment_webhook_secret` is always kept in sync.
 *
 * Calls are captured rather than asserted on the exact value: Secret_Cipher::encrypt() may or may
 * not seal the value (depends on whether WP salt constants happen to be defined in the run), so we
 * assert the option name + autoload flag + non-empty payload instead.
 */
final class Webhook_Secret_Persist_Test extends TestCase {

	private const OPTION = 'simplyin_plugin_payment_webhook_secret';

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

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

	public function test_stores_secret_when_present_and_non_empty(): void {
		$captured = null;
		Functions\when( 'update_option' )->alias(
			static function ( $name, $value, $autoload ) use ( &$captured ) {
				$captured = [ $name, $value, $autoload ];

				return true;
			}
		);
		Functions\when( 'delete_option' )->alias(
			static function (): void {
				throw new \RuntimeException( 'delete_option must not be called when a secret is present' );
			}
		);

		Simplyin_Sync::persist_webhook_signing_secret(
			(object) [ 'paymentWebhookSigningSecret' => 'whsec_live_value' ]
		);

		$this->assertIsArray( $captured, 'the secret must be stored' );
		$this->assertSame( self::OPTION, $captured[0] );
		$this->assertFalse( $captured[2], 'the secret option must be stored with autoload disabled' );
		$this->assertNotSame( '', (string) $captured[1], 'the stored value must not be empty' );
	}

	public function test_clears_secret_when_present_but_empty(): void {
		// Present + empty = explicit rotate/clear from SIN.CORE.
		$deleted = null;
		Functions\when( 'delete_option' )->alias(
			static function ( $name ) use ( &$deleted ) {
				$deleted = $name;

				return true;
			}
		);
		Functions\when( 'update_option' )->alias(
			static function (): void {
				throw new \RuntimeException( 'update_option must not be called on an explicit clear' );
			}
		);

		Simplyin_Sync::persist_webhook_signing_secret(
			(object) [ 'paymentWebhookSigningSecret' => '' ]
		);

		$this->assertSame( self::OPTION, $deleted );
	}

	public function test_no_change_when_field_absent(): void {
		// Absent field = "no change": must not touch the stored secret.
		$touched = false;
		$flag    = static function () use ( &$touched ): void {
			$touched = true;
		};
		Functions\when( 'update_option' )->alias( $flag );
		Functions\when( 'delete_option' )->alias( $flag );

		Simplyin_Sync::persist_webhook_signing_secret(
			(object) [ 'transactionHistoryLengthDays' => 180 ]
		);

		$this->assertFalse( $touched, 'an absent field must leave the stored secret untouched' );
	}

	public function test_no_change_when_response_not_object(): void {
		$touched = false;
		$flag    = static function () use ( &$touched ): void {
			$touched = true;
		};
		Functions\when( 'update_option' )->alias( $flag );
		Functions\when( 'delete_option' )->alias( $flag );

		Simplyin_Sync::persist_webhook_signing_secret( null );

		$this->assertFalse( $touched, 'a non-object response must be a no-op' );
	}
}
