<?php

declare(strict_types=1);

use App\Framework\StateManagement\Transformers\EncryptionTransformer;
use App\Framework\StateManagement\SerializableState;
use App\Framework\LiveComponents\Serialization\EncryptedStateSerializer;
use App\Framework\LiveComponents\Serialization\StateEncryptor;
use App\Framework\Cryptography\CryptographicUtilities;
use App\Framework\Random\SecureRandomGenerator;

// Test State for transformer tests
final readonly class TransformerTestState implements SerializableState
{
    public function __construct(
        public string $name,
        public int $value
    ) {
    }

    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'value' => $this->value,
        ];
    }

    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'] ?? '',
            value: $data['value'] ?? 0
        );
    }
}

describe('EncryptionTransformer', function () {
    beforeEach(function () {
        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);

        $this->transformer = new EncryptionTransformer(
            $serializer,
            TransformerTestState::class
        );

        $this->testState = new TransformerTestState(
            name: 'Test User',
            value: 42
        );
    });

    it('has correct name', function () {
        expect($this->transformer->getName())->toBe('encryption');
    });

    it('has medium priority', function () {
        expect($this->transformer->getPriority())->toBe(50);
    });

    it('transforms state to encrypted string', function () {
        $transformed = $this->transformer->transformIn($this->testState);

        expect($transformed)->toBeString();
        expect($transformed)->not->toBeEmpty();

        // Should be base64-encoded encrypted data
        $decoded = base64_decode($transformed, strict: true);
        expect($decoded)->not->toBeFalse();
    });

    it('transforms encrypted string back to state', function () {
        $encrypted = $this->transformer->transformIn($this->testState);
        $state = $this->transformer->transformOut($encrypted);

        expect($state)->toBeInstanceOf(TransformerTestState::class);
        expect($state->name)->toBe('Test User');
        expect($state->value)->toBe(42);
    });

    it('round-trip transformation preserves state', function () {
        $original = new TransformerTestState(
            name: 'Round Trip Test',
            value: 999
        );

        $encrypted = $this->transformer->transformIn($original);
        $restored = $this->transformer->transformOut($encrypted);

        expect($restored)->toBeInstanceOf(TransformerTestState::class);
        expect($restored->name)->toBe($original->name);
        expect($restored->value)->toBe($original->value);
    });

    it('throws exception for non-string data in transformOut', function () {
        $this->transformer->transformOut(['not' => 'string']);
    })->throws(\InvalidArgumentException::class, 'expects string data');

    it('throws exception for non-SerializableState in transformOut', function () {
        // Create invalid encrypted data that decrypts to non-SerializableState
        // This would happen if stateClass is wrong
        $transformer = new EncryptionTransformer(
            $this->transformer->serializer,
            \stdClass::class  // Wrong class
        );

        $encrypted = $this->transformer->transformIn($this->testState);

        $transformer->transformOut($encrypted);
    })->throws(\RuntimeException::class, 'must implement SerializableState');

    it('produces different encrypted output for same state', function () {
        $encrypted1 = $this->transformer->transformIn($this->testState);
        $encrypted2 = $this->transformer->transformIn($this->testState);

        // Should be different due to unique nonces
        expect($encrypted1)->not->toBe($encrypted2);

        // But both should decrypt to same state
        $state1 = $this->transformer->transformOut($encrypted1);
        $state2 = $this->transformer->transformOut($encrypted2);

        expect($state1->name)->toBe($state2->name);
        expect($state1->value)->toBe($state2->value);
    });

    it('handles state with special characters', function () {
        $state = new TransformerTestState(
            name: "Special: 🔐 <script>alert('xss')</script>",
            value: 123
        );

        $encrypted = $this->transformer->transformIn($state);
        $restored = $this->transformer->transformOut($encrypted);

        expect($restored->name)->toBe($state->name);
    });
});

describe('EncryptionTransformer Integration', function () {
    it('works in transformer pipeline with multiple transformers', function () {
        // Simulate transformer pipeline
        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);

        $encryptionTransformer = new EncryptionTransformer(
            $serializer,
            TransformerTestState::class
        );

        $state = new TransformerTestState('Pipeline Test', 789);

        // transformIn pipeline (high to low priority)
        $data = $encryptionTransformer->transformIn($state);

        // transformOut pipeline (reverse order)
        $restored = $encryptionTransformer->transformOut($data);

        expect($restored)->toBeInstanceOf(TransformerTestState::class);
        expect($restored->name)->toBe('Pipeline Test');
        expect($restored->value)->toBe(789);
    });

    it('maintains type safety through transformation', function () {
        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);

        $transformer = new EncryptionTransformer(
            $serializer,
            TransformerTestState::class
        );

        $state = new TransformerTestState('Type Safety', 0);

        $encrypted = $transformer->transformIn($state);
        $restored = $transformer->transformOut($encrypted);

        // Type checks
        expect($restored)->toBeInstanceOf(SerializableState::class);
        expect($restored)->toBeInstanceOf(TransformerTestState::class);
        expect($restored->value)->toBeInt();
        expect($restored->name)->toBeString();
    });
});
