<?php

namespace MintyPHP\Tests\Service\Auth;

use MintyPHP\Service\Auth\AuthCryptoGateway;
use PHPUnit\Framework\TestCase;

class AuthCryptoGatewayTest extends TestCase
{
    private AuthCryptoGateway $gateway;

    protected function setUp(): void
    {
        // Define APP_CRYPTO_KEY once for the entire test run (valid 64-char hex = 32 bytes).
        if (!defined('APP_CRYPTO_KEY')) {
            define('APP_CRYPTO_KEY', str_repeat('ab', 32));
        }

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

    // --- isConfigured ---

    public function testIsConfiguredReturnsTrueWhenKeyIsSet(): void
    {
        $this->assertTrue($this->gateway->isConfigured());
    }

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

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

        $this->assertNotSame($plaintext, $encrypted, 'Encrypted value must differ from plaintext');
        $this->assertNotEmpty($encrypted);

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

    public function testEncryptEmptyStringProducesMalformedPayloadOnDecrypt(): void
    {
        // AES-256-GCM with empty plaintext yields empty ciphertext,
        // which Crypto::decryptString rejects as malformed — expected behavior.
        $encrypted = $this->gateway->encryptString('');

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

    public function testEncryptDecryptRoundTripWithUnicode(): void
    {
        $plaintext = 'Ünïcödé-tëst 🔑';
        $encrypted = $this->gateway->encryptString($plaintext);
        $decrypted = $this->gateway->decryptString($encrypted);

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

    public function testEncryptDecryptRoundTripWithLongString(): void
    {
        $plaintext = str_repeat('long-secret-', 100);
        $encrypted = $this->gateway->encryptString($plaintext);
        $decrypted = $this->gateway->decryptString($encrypted);

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

    // --- encryptString produces unique ciphertext ---

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

        $this->assertNotSame($encrypted1, $encrypted2, 'Each encryption should use a unique IV');
    }

    // --- decryptString error handling ---

    public function testDecryptThrowsOnInvalidBase64(): void
    {
        $this->expectException(\RuntimeException::class);
        $this->gateway->decryptString('not-valid-base64!!!');
    }

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

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

        // Tamper with the base64 payload — change one character in the middle
        $tampered = substr($encrypted, 0, 10) . 'X' . substr($encrypted, 11);

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

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

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

        // The Crypto class wraps JSON in base64. Verify the outer structure.
        $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']);
    }
}
