<?php

declare(strict_types=1);

use App\Framework\Cryptography\SecureToken;
use App\Framework\Cryptography\SecureTokenGenerator;
use App\Framework\Random\SecureRandomGenerator;

it('generates secure token with default parameters', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generate('api_key');

    expect($token)->toBeInstanceOf(SecureToken::class);
    expect($token->getType())->toBe('api_key');
    expect($token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64_URL);
    expect($token->getLength())->toBe(32);
});

it('generates API key with prefix', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateApiKey('myapp');

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_API_KEY);
    expect($token->getPrefix())->toBe('myapp');
    expect($token->getValue())->toStartWith('myapp_');
});

it('generates session token', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateSessionToken();

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_SESSION);
    expect($token->getMetadataValue('purpose'))->toBe('session_management');
    expect($token->getMetadataValue('secure'))->toBeTrue();
});

it('generates CSRF token', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateCsrfToken();

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_CSRF);
    expect($token->getMetadataValue('purpose'))->toBe('csrf_protection');
});

it('generates verification token', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateVerificationToken('email_verification');

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_VERIFICATION);
    expect($token->getMetadataValue('purpose'))->toBe('email_verification');
    expect($token->getMetadataValue('single_use'))->toBeTrue();
});

it('generates OTP token with correct digits', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateOtpToken(6);

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_OTP);
    expect($token->getValue())->toMatch('/^\d{6}$/');
    expect($token->getLength())->toBe(6);
    expect($token->getMetadataValue('digits'))->toBe(6);
});

it('generates webhook token', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateWebhookToken();

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_WEBHOOK);
    expect($token->getPrefix())->toBe('whsec');
    expect($token->getValue())->toStartWith('whsec_');
});

it('generates bearer token', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateBearerToken();

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_BEARER);
    expect($token->getMetadataValue('purpose'))->toBe('api_authorization');
});

it('generates refresh token with longer length', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $token = $generator->generateRefreshToken();

    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_REFRESH);
    expect($token->getLength())->toBe(64);
    expect($token->getMetadataValue('long_lived'))->toBeTrue();
});

it('generates tokens in different formats', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    $base64Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE64);
    $hexToken = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_HEX);
    $base32Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE32);

    expect($base64Token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64);
    expect($hexToken->getFormat())->toBe(SecureTokenGenerator::FORMAT_HEX);
    expect($base32Token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE32);

    // Check format patterns
    expect($hexToken->getValue())->toMatch('/^[0-9a-f]+$/');
    expect($base32Token->getValue())->toMatch('/^[A-Z2-7]+$/');
});

it('generates custom tokens with alphabet', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $alphabet = '0123456789ABCDEF';
    $token = $generator->generateCustom($alphabet, 16);

    expect($token->getValue())->toMatch('/^[0-9A-F]{16}$/');
    expect($token->getMetadataValue('alphabet'))->toBe($alphabet);
    expect($token->getMetadataValue('alphabet_size'))->toBe(16);
});

it('generates batch of tokens', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $tokens = $generator->generateBatch('session', 5, 32);

    expect($tokens)->toHaveCount(5);
    expect($tokens[0])->toBeInstanceOf(SecureToken::class);

    // All tokens should be unique
    $values = array_map(fn ($token) => $token->getValue(), $tokens);
    $uniqueValues = array_unique($values);
    expect($uniqueValues)->toHaveCount(5);
});

it('validates token formats correctly', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect($generator->isValidFormat('dGVzdA', SecureTokenGenerator::FORMAT_BASE64))->toBeTrue();
    expect($generator->isValidFormat('dGVzdA-_', SecureTokenGenerator::FORMAT_BASE64_URL))->toBeTrue();
    expect($generator->isValidFormat('deadbeef', SecureTokenGenerator::FORMAT_HEX))->toBeTrue();
    expect($generator->isValidFormat('MFRGG', SecureTokenGenerator::FORMAT_BASE32))->toBeTrue();
    expect($generator->isValidFormat('Test123', SecureTokenGenerator::FORMAT_ALPHANUMERIC))->toBeTrue();

    // Invalid formats
    expect($generator->isValidFormat('invalid!@#', SecureTokenGenerator::FORMAT_BASE64_URL))->toBeFalse();
    expect($generator->isValidFormat('invalidhex', SecureTokenGenerator::FORMAT_HEX))->toBeFalse();
});

it('calculates token entropy correctly', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    $base64Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE64_URL);
    $hexToken = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_HEX);

    $base64Entropy = $generator->getEntropy($base64Token);
    $hexEntropy = $generator->getEntropy($hexToken);

    expect($base64Entropy)->toBeGreaterThan($hexEntropy); // Base64 has higher entropy per character
    expect($base64Entropy)->toBeGreaterThan(100); // Should have significant entropy
});

it('throws exception for too short token length', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generate('test', 8))
        ->toThrow(InvalidArgumentException::class, 'Token length must be at least 16 bytes');
});

it('throws exception for too long token length', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generate('test', 512))
        ->toThrow(InvalidArgumentException::class, 'Token length cannot exceed 256 bytes');
});

it('throws exception for unsupported token type', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generate('unsupported_type'))
        ->toThrow(InvalidArgumentException::class, 'Unsupported token type');
});

it('throws exception for unsupported format', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generate('api_key', 32, 'unsupported_format'))
        ->toThrow(InvalidArgumentException::class, 'Unsupported format');
});

it('throws exception for invalid OTP digits', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generateOtpToken(2))
        ->toThrow(InvalidArgumentException::class, 'OTP digits must be between 4 and 12');

    expect(fn () => $generator->generateOtpToken(15))
        ->toThrow(InvalidArgumentException::class, 'OTP digits must be between 4 and 12');
});

it('throws exception for too small custom alphabet', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generateCustom('ABC', 16))
        ->toThrow(InvalidArgumentException::class, 'Alphabet must contain at least 16 characters');
});

it('throws exception for too short custom token', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generateCustom('0123456789ABCDEF', 4))
        ->toThrow(InvalidArgumentException::class, 'Token length must be at least 8 characters');
});

it('throws exception for invalid batch count', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generateBatch('session', 0))
        ->toThrow(InvalidArgumentException::class, 'Count must be positive');

    expect(fn () => $generator->generateBatch('session', 2000))
        ->toThrow(InvalidArgumentException::class, 'Batch size cannot exceed 1000');
});

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

    expect($generator)->toBeInstanceOf(SecureTokenGenerator::class);
});

it('removes duplicate characters from custom alphabet', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $alphabetWithDuplicates = '0123456789ABCDEFABCDEF'; // Has duplicate ABCDEF
    $token = $generator->generateCustom($alphabetWithDuplicates, 8);

    $metadata = $token->getMetadata();
    $actualAlphabet = $metadata['alphabet'];
    expect($actualAlphabet)->toBe('0123456789ABCDEF'); // Duplicates removed
    expect($metadata['alphabet_size'])->toBe(16);
});

it('generates unique tokens in batch', function () {
    $generator = new SecureTokenGenerator(new SecureRandomGenerator());
    $tokens = $generator->generateBatch('api_key', 10, 32);

    $values = array_map(fn ($token) => $token->getValue(), $tokens);
    $uniqueValues = array_unique($values);

    expect($uniqueValues)->toHaveCount(10); // All should be unique
});
