<?php

namespace MintyPHP\Tests\Service\Settings;

use MintyPHP\Service\Settings\SettingsCryptoGateway;
use MintyPHP\Service\Settings\SettingsCryptoGatewayInterface;
use PHPUnit\Framework\TestCase;

class SettingsCryptoGatewayTest extends TestCase
{
    private SettingsCryptoGateway $gateway;

    protected function setUp(): void
    {
        if (!defined('APP_CRYPTO_KEY')) {
            define('APP_CRYPTO_KEY', str_repeat('ab', 32));
        }

        $this->gateway = new SettingsCryptoGateway();
    }

    // --- Interface compliance ---

    public function testImplementsSettingsCryptoGatewayInterface(): void
    {
        $this->assertInstanceOf(SettingsCryptoGatewayInterface::class, $this->gateway);
    }

    // --- encryptString / decryptString round-trip ---

    public function testEncryptDecryptRoundTrip(): void
    {
        $plaintext = 'client-secret-for-sso';
        $encrypted = $this->gateway->encryptString($plaintext);

        $this->assertNotSame($plaintext, $encrypted);
        $this->assertNotEmpty($encrypted);

        $decrypted = $this->gateway->decryptString($encrypted);
        $this->assertSame($plaintext, $decrypted);
    }

    public function testEncryptEmptyStringProducesMalformedPayloadOnDecrypt(): void
    {
        $encrypted = $this->gateway->encryptString('');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('malformed');
        $this->gateway->decryptString($encrypted);
    }

    public function testEncryptDecryptRoundTripWithSpecialChars(): void
    {
        $plaintext = 'p@$$w0rd!&key=val<ue>';
        $encrypted = $this->gateway->encryptString($plaintext);
        $decrypted = $this->gateway->decryptString($encrypted);

        $this->assertSame($plaintext, $decrypted);
    }

    // --- Unique ciphertext per encryption ---

    public function testEncryptProducesDifferentCiphertextEachTime(): void
    {
        $plaintext = 'same-settings-value';
        $encrypted1 = $this->gateway->encryptString($plaintext);
        $encrypted2 = $this->gateway->encryptString($plaintext);

        $this->assertNotSame($encrypted1, $encrypted2);
    }

    // --- Delegation to Crypto class (GR-SEC-005) ---

    public function testEncryptStringOutputIsValidCryptoPayload(): void
    {
        $encrypted = $this->gateway->encryptString('test');

        $decoded = base64_decode($encrypted, true);
        $this->assertIsString($decoded);

        $payload = json_decode($decoded, true);
        $this->assertIsArray($payload);
        $this->assertArrayHasKey('v', $payload);
        $this->assertArrayHasKey('iv', $payload);
        $this->assertArrayHasKey('tag', $payload);
        $this->assertArrayHasKey('ct', $payload);
        $this->assertSame(1, $payload['v']);
    }

    // --- Error handling ---

    public function testDecryptThrowsOnInvalidPayload(): void
    {
        $this->expectException(\RuntimeException::class);
        $this->gateway->decryptString('garbage-data');
    }

    public function testDecryptThrowsOnEmptyJsonPayload(): void
    {
        $this->expectException(\RuntimeException::class);
        $this->gateway->decryptString(base64_encode('{}'));
    }

    public function testDecryptThrowsOnTamperedCiphertext(): void
    {
        $encrypted = $this->gateway->encryptString('secret');
        $tampered = substr($encrypted, 0, 5) . 'Z' . substr($encrypted, 6);

        $this->expectException(\RuntimeException::class);
        $this->gateway->decryptString($tampered);
    }
}
