<?php

declare(strict_types=1);

use App\Framework\Cryptography\DerivedKey;

it('creates derived key with valid parameters', function () {
    $key = str_repeat('a', 32);
    $salt = str_repeat('b', 32);

    $derivedKey = new DerivedKey(
        key: $key,
        salt: $salt,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    expect($derivedKey->getKey())->toBe($key);
    expect($derivedKey->getSalt())->toBe($salt);
    expect($derivedKey->getAlgorithm())->toBe('pbkdf2-sha256');
    expect($derivedKey->getIterations())->toBe(100000);
    expect($derivedKey->getKeyLength())->toBe(32);
});

it('throws exception for empty key', function () {
    expect(fn () => new DerivedKey(
        key: '',
        salt: str_repeat('b', 32),
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    ))->toThrow(InvalidArgumentException::class, 'Key cannot be empty');
});

it('throws exception for empty salt', function () {
    expect(fn () => new DerivedKey(
        key: str_repeat('a', 32),
        salt: '',
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    ))->toThrow(InvalidArgumentException::class, 'Salt cannot be empty');
});

it('throws exception for key length mismatch', function () {
    expect(fn () => new DerivedKey(
        key: str_repeat('a', 16), // 16 bytes
        salt: str_repeat('b', 32),
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32 // Claims 32 bytes
    ))->toThrow(InvalidArgumentException::class, 'Key length does not match specified length');
});

it('provides hex representation', function () {
    $key = "\x01\x02\x03\x04";
    $salt = "\x05\x06\x07\x08";

    $derivedKey = new DerivedKey(
        key: $key,
        salt: $salt,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 4
    );

    expect($derivedKey->getKeyHex())->toBe('01020304');
    expect($derivedKey->getSaltHex())->toBe('05060708');
});

it('provides base64 representation', function () {
    $key = 'test-key-data';
    $salt = 'test-salt-data';

    $derivedKey = new DerivedKey(
        key: $key,
        salt: $salt,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: strlen($key)
    );

    expect($derivedKey->getKeyBase64())->toBe(base64_encode($key));
    expect($derivedKey->getSaltBase64())->toBe(base64_encode($salt));
});

it('checks equality correctly', function () {
    $key = str_repeat('a', 32);
    $salt = str_repeat('b', 32);

    $derivedKey1 = new DerivedKey(
        key: $key,
        salt: $salt,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    $derivedKey2 = new DerivedKey(
        key: $key,
        salt: $salt,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    $derivedKey3 = new DerivedKey(
        key: str_repeat('c', 32), // Different key
        salt: $salt,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    expect($derivedKey1->equals($derivedKey2))->toBeTrue();
    expect($derivedKey1->equals($derivedKey3))->toBeFalse();
});

it('exports to array correctly', function () {
    $derivedKey = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 32),
        algorithm: 'argon2id',
        iterations: 4,
        keyLength: 32,
        memoryCost: 65536,
        threads: 3
    );

    $array = $derivedKey->toArray();

    expect($array)->toHaveKey('key');
    expect($array)->toHaveKey('salt');
    expect($array)->toHaveKey('algorithm');
    expect($array)->toHaveKey('iterations');
    expect($array)->toHaveKey('key_length');
    expect($array)->toHaveKey('memory_cost');
    expect($array)->toHaveKey('threads');

    expect($array['algorithm'])->toBe('argon2id');
    expect($array['memory_cost'])->toBe(65536);
    expect($array['threads'])->toBe(3);
});

it('creates from array correctly', function () {
    $originalKey = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 32),
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    $array = $originalKey->toArray();
    $restoredKey = DerivedKey::fromArray($array);

    expect($restoredKey->equals($originalKey))->toBeTrue();
});

it('creates from hex correctly', function () {
    $keyHex = '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20';
    $saltHex = 'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef';

    $derivedKey = DerivedKey::fromHex(
        keyHex: $keyHex,
        saltHex: $saltHex,
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    expect($derivedKey->getKeyHex())->toBe($keyHex);
    expect($derivedKey->getSaltHex())->toBe($saltHex);
});

it('provides summary information', function () {
    $derivedKey = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 16),
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    $summary = $derivedKey->getSummary();

    expect($summary)->toHaveKey('algorithm');
    expect($summary)->toHaveKey('iterations');
    expect($summary)->toHaveKey('key_length');
    expect($summary)->toHaveKey('salt_length');
    expect($summary['salt_length'])->toBe(16);
});

it('identifies algorithm types correctly', function () {
    $pbkdf2Key = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 32),
        algorithm: 'pbkdf2-sha256',
        iterations: 100000,
        keyLength: 32
    );

    $argon2Key = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 32),
        algorithm: 'argon2id',
        iterations: 4,
        keyLength: 32
    );

    $scryptKey = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 32),
        algorithm: 'scrypt',
        iterations: 16384,
        keyLength: 32
    );

    expect($pbkdf2Key->isPbkdf2())->toBeTrue();
    expect($pbkdf2Key->isArgon2())->toBeFalse();
    expect($pbkdf2Key->isScrypt())->toBeFalse();

    expect($argon2Key->isArgon2())->toBeTrue();
    expect($argon2Key->isPbkdf2())->toBeFalse();

    expect($scryptKey->isScrypt())->toBeTrue();
    expect($scryptKey->isPbkdf2())->toBeFalse();
});

it('handles scrypt parameters correctly', function () {
    $derivedKey = new DerivedKey(
        key: str_repeat('a', 32),
        salt: str_repeat('b', 32),
        algorithm: 'scrypt',
        iterations: 16384,
        keyLength: 32,
        blockSize: 8,
        parallelization: 1
    );

    expect($derivedKey->getBlockSize())->toBe(8);
    expect($derivedKey->getParallelization())->toBe(1);
});

it('throws exception for missing required fields in fromArray', function () {
    $incompleteData = [
        'key' => base64_encode(str_repeat('a', 32)),
        'salt' => base64_encode(str_repeat('b', 32)),
        'algorithm' => 'pbkdf2-sha256',
        // Missing iterations and key_length
    ];

    expect(fn () => DerivedKey::fromArray($incompleteData))
        ->toThrow(InvalidArgumentException::class, 'Missing required field');
});

it('throws exception for invalid base64 in fromArray', function () {
    $invalidData = [
        'key' => 'invalid-base64!@#',
        'salt' => base64_encode(str_repeat('b', 32)),
        'algorithm' => 'pbkdf2-sha256',
        'iterations' => 100000,
        'key_length' => 32,
    ];

    expect(fn () => DerivedKey::fromArray($invalidData))
        ->toThrow(InvalidArgumentException::class, 'Failed to decode Base64 data');
});
