<?php

declare(strict_types=1);

use App\Framework\Cryptography\DerivedKey;
use App\Framework\Cryptography\KeyDerivationFunction;
use App\Framework\Random\SecureRandomGenerator;

it('generates salt with correct length', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $salt = $kdf->generateSalt(32);

    expect(strlen($salt))->toBe(32);
});

it('throws exception for too short salt', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());

    expect(fn () => $kdf->generateSalt(8))
        ->toThrow(InvalidArgumentException::class, 'Salt length must be at least 16 bytes');
});

it('derives key using PBKDF2', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'test-password';
    $salt = $kdf->generateSalt();

    $derivedKey = $kdf->pbkdf2($password, $salt, 10000, 32);

    expect($derivedKey)->toBeInstanceOf(DerivedKey::class);
    expect($derivedKey->getAlgorithm())->toBe('pbkdf2-sha256');
    expect($derivedKey->getKeyLength())->toBe(32);
    expect($derivedKey->getIterations())->toBe(10000);
    expect(strlen($derivedKey->getKey()))->toBe(32);
});

it('derives key using Argon2ID if available', function () {
    if (! function_exists('sodium_crypto_pwhash') || ! defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID')) {
        $this->markTestSkipped('Sodium extension or Argon2ID constant not available');
    }

    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'test-password';
    $salt = str_repeat("\x00", 32); // Argon2ID needs exactly 32 bytes

    $derivedKey = $kdf->argon2id($password, $salt, 65536, 4, 3, 32);

    expect($derivedKey)->toBeInstanceOf(DerivedKey::class);
    expect($derivedKey->getAlgorithm())->toBe('argon2id');
    expect($derivedKey->getKeyLength())->toBe(32);
    expect(strlen($derivedKey->getKey()))->toBe(32);
});

it('derives key using scrypt if available', function () {
    if (! function_exists('scrypt')) {
        $this->markTestSkipped('scrypt function not available');
    }

    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'test-password';
    $salt = $kdf->generateSalt();

    $derivedKey = $kdf->scrypt($password, $salt, 16384, 8, 1, 32);

    expect($derivedKey)->toBeInstanceOf(DerivedKey::class);
    expect($derivedKey->getAlgorithm())->toBe('scrypt');
    expect($derivedKey->getKeyLength())->toBe(32);
    expect(strlen($derivedKey->getKey()))->toBe(32);
});

it('verifies password correctly', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'correct-password';
    $wrongPassword = 'wrong-password';
    $salt = $kdf->generateSalt();

    $derivedKey = $kdf->pbkdf2($password, $salt, 10000, 32);

    expect($kdf->verify($password, $derivedKey))->toBeTrue();
    expect($kdf->verify($wrongPassword, $derivedKey))->toBeFalse();
});

it('hashes password with automatic salt generation', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'test-password';

    $derivedKey = $kdf->hashPassword($password, 'pbkdf2-sha256');

    expect($derivedKey)->toBeInstanceOf(DerivedKey::class);
    expect($derivedKey->getAlgorithm())->toBe('pbkdf2-sha256');
    expect(strlen($derivedKey->getSalt()))->toBe(32);
    expect($kdf->verify($password, $derivedKey))->toBeTrue();
});

it('throws exception for empty password', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $salt = $kdf->generateSalt();

    expect(fn () => $kdf->pbkdf2('', $salt))
        ->toThrow(InvalidArgumentException::class, 'Password cannot be empty');
});

it('throws exception for short salt', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());

    expect(fn () => $kdf->pbkdf2('password', 'short'))
        ->toThrow(InvalidArgumentException::class, 'Salt must be at least 16 bytes');
});

it('throws exception for too few iterations', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $salt = $kdf->generateSalt();

    expect(fn () => $kdf->pbkdf2('password', $salt, 5000))
        ->toThrow(InvalidArgumentException::class, 'Iterations must be at least 10,000');
});

it('supports different hash algorithms', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'test-password';
    $salt = $kdf->generateSalt();

    $sha256Key = $kdf->pbkdf2($password, $salt, 10000, 32, 'sha256');
    $sha512Key = $kdf->pbkdf2($password, $salt, 10000, 32, 'sha512');

    expect($sha256Key->getAlgorithm())->toBe('pbkdf2-sha256');
    expect($sha512Key->getAlgorithm())->toBe('pbkdf2-sha512');
    expect($sha256Key->getKey())->not->toBe($sha512Key->getKey());
});

it('provides recommended parameters', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());

    $params = $kdf->getRecommendedParameters('pbkdf2-sha256', 'standard');

    expect($params)->toHaveKey('iterations');
    expect($params)->toHaveKey('key_length');
    expect($params['iterations'])->toBe(100000);
    expect($params['key_length'])->toBe(32);
});

it('throws exception for unsupported algorithm in recommendations', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());

    expect(fn () => $kdf->getRecommendedParameters('unsupported', 'standard'))
        ->toThrow(InvalidArgumentException::class);
});

it('creates instance using factory method', function () {
    $randomGen = new SecureRandomGenerator();
    $kdf = KeyDerivationFunction::create($randomGen);

    expect($kdf)->toBeInstanceOf(KeyDerivationFunction::class);
});

it('generates different keys for same password with different salts', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'same-password';

    $salt1 = $kdf->generateSalt();
    $salt2 = $kdf->generateSalt();

    $key1 = $kdf->pbkdf2($password, $salt1);
    $key2 = $kdf->pbkdf2($password, $salt2);

    expect($key1->getKey())->not->toBe($key2->getKey());
});

it('generates consistent keys for same inputs', function () {
    $kdf = new KeyDerivationFunction(new SecureRandomGenerator());
    $password = 'test-password';
    $salt = str_repeat('a', 16);

    $key1 = $kdf->pbkdf2($password, $salt, 10000, 32);
    $key2 = $kdf->pbkdf2($password, $salt, 10000, 32);

    expect($key1->getKey())->toBe($key2->getKey());
});
