<?php

declare(strict_types=1);

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

it('creates secure token with valid parameters', function () {
    $token = new SecureToken(
        value: 'test-token-value',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: 'ak',
        rawBytes: 'raw-bytes-data',
        metadata: ['purpose' => 'testing']
    );

    expect($token->getValue())->toBe('test-token-value');
    expect($token->getType())->toBe(SecureTokenGenerator::TYPE_API_KEY);
    expect($token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64_URL);
    expect($token->getLength())->toBe(32);
    expect($token->getPrefix())->toBe('ak');
    expect($token->getRawBytes())->toBe('raw-bytes-data');
    expect($token->getMetadata())->toBe(['purpose' => 'testing']);
});

it('throws exception for empty token value', function () {
    expect(fn () => new SecureToken(
        value: '',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    ))->toThrow(InvalidArgumentException::class, 'Token value cannot be empty');
});

it('provides string representation', function () {
    $tokenValue = 'test-token-value';
    $token = new SecureToken(
        value: $tokenValue,
        type: SecureTokenGenerator::TYPE_SESSION,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    expect($token->toString())->toBe($tokenValue);
    expect((string)$token)->toBe($tokenValue);
});

it('extracts value without prefix', function () {
    $token = new SecureToken(
        value: 'ak_abcd1234efgh5678',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: 'ak',
        rawBytes: 'raw-bytes',
        metadata: []
    );

    expect($token->getValueWithoutPrefix())->toBe('abcd1234efgh5678');
    expect($token->hasPrefix())->toBeTrue();
});

it('handles token without prefix', function () {
    $token = new SecureToken(
        value: 'abcd1234efgh5678',
        type: SecureTokenGenerator::TYPE_SESSION,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    expect($token->getValueWithoutPrefix())->toBe('abcd1234efgh5678');
    expect($token->hasPrefix())->toBeFalse();
});

it('provides raw bytes in different formats', function () {
    $rawBytes = "\x01\x02\x03\x04";
    $token = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: $rawBytes,
        metadata: []
    );

    expect($token->getRawBytesHex())->toBe('01020304');
    expect($token->getRawBytesBase64())->toBe(base64_encode($rawBytes));
});

it('checks equality correctly with timing-safe comparison', function () {
    $tokenValue = 'same-token-value';

    $token1 = new SecureToken(
        value: $tokenValue,
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    $token2 = new SecureToken(
        value: $tokenValue,
        type: SecureTokenGenerator::TYPE_SESSION,
        format: SecureTokenGenerator::FORMAT_HEX,
        length: 64,
        prefix: 'test',
        rawBytes: 'different-raw',
        metadata: ['different' => 'metadata']
    );

    $token3 = new SecureToken(
        value: 'different-token-value',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    expect($token1->equals($token2))->toBeTrue(); // Same value
    expect($token1->equals($token3))->toBeFalse(); // Different value
});

it('verifies token value with timing-safe comparison', function () {
    $tokenValue = 'secure-token-value';
    $token = new SecureToken(
        value: $tokenValue,
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    expect($token->verify($tokenValue))->toBeTrue();
    expect($token->verify('wrong-token-value'))->toBeFalse();
});

it('identifies token types correctly', function () {
    $apiKeyToken = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    $sessionToken = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_SESSION,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    expect($apiKeyToken->isApiKey())->toBeTrue();
    expect($apiKeyToken->isSessionToken())->toBeFalse();
    expect($sessionToken->isSessionToken())->toBeTrue();
    expect($sessionToken->isApiKey())->toBeFalse();
});

it('checks token properties from metadata', function () {
    $singleUseToken = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_VERIFICATION,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: ['single_use' => true, 'long_lived' => false]
    );

    $longLivedToken = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_REFRESH,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 64,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: ['single_use' => false, 'long_lived' => true]
    );

    expect($singleUseToken->isSingleUse())->toBeTrue();
    expect($singleUseToken->isLongLived())->toBeFalse();
    expect($longLivedToken->isLongLived())->toBeTrue();
    expect($longLivedToken->isSingleUse())->toBeFalse();
});

it('gets specific metadata values', function () {
    $token = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: ['purpose' => 'api_auth', 'expires' => 3600]
    );

    expect($token->getMetadataValue('purpose'))->toBe('api_auth');
    expect($token->getMetadataValue('expires'))->toBe(3600);
    expect($token->getMetadataValue('nonexistent', 'default'))->toBe('default');
});

it('calculates token age', function () {
    $token = new SecureToken(
        value: 'test-token',
        type: SecureTokenGenerator::TYPE_SESSION,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    sleep(1);
    $age = $token->getAgeInSeconds();
    expect($age)->toBeGreaterThanOrEqual(1);
});

it('exports to and imports from array', function () {
    $originalToken = new SecureToken(
        value: 'test-token-value',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: 'ak',
        rawBytes: 'raw-bytes-data',
        metadata: ['purpose' => 'testing', 'scope' => 'read']
    );

    $array = $originalToken->toArray();
    $restoredToken = SecureToken::fromArray($array);

    expect($restoredToken->getValue())->toBe($originalToken->getValue());
    expect($restoredToken->getType())->toBe($originalToken->getType());
    expect($restoredToken->getFormat())->toBe($originalToken->getFormat());
    expect($restoredToken->getLength())->toBe($originalToken->getLength());
    expect($restoredToken->getPrefix())->toBe($originalToken->getPrefix());
    expect($restoredToken->getRawBytes())->toBe($originalToken->getRawBytes());
    expect($restoredToken->getMetadata())->toBe($originalToken->getMetadata());
});

it('provides safe summary without sensitive data', function () {
    $token = new SecureToken(
        value: 'very-secret-token-value',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: 'ak',
        rawBytes: 'raw-bytes',
        metadata: ['purpose' => 'api_auth', 'scope' => 'admin']
    );

    $summary = $token->getSafeSummary();

    expect($summary)->toHaveKey('type');
    expect($summary)->toHaveKey('format');
    expect($summary)->toHaveKey('length');
    expect($summary)->toHaveKey('prefix');
    expect($summary)->toHaveKey('has_prefix');
    expect($summary)->toHaveKey('metadata_keys');
    expect($summary)->not->toHaveKey('value'); // Should not contain sensitive value
    expect($summary)->not->toHaveKey('raw_bytes'); // Should not contain raw bytes

    expect($summary['metadata_keys'])->toBe(['purpose', 'scope']);
});

it('generates fingerprint for identification', function () {
    $token = new SecureToken(
        value: 'test-token-value',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    $fingerprint = $token->getFingerprint();
    $shortFingerprint = $token->getShortFingerprint();

    expect($fingerprint)->toHaveLength(64); // SHA-256 hex
    expect($shortFingerprint)->toHaveLength(16);
    expect($fingerprint)->toStartWith($shortFingerprint);
});

it('masks token value for safe logging', function () {
    $longToken = new SecureToken(
        value: 'this-is-a-very-long-token-value-for-testing',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    $shortToken = new SecureToken(
        value: 'short',
        type: SecureTokenGenerator::TYPE_API_KEY,
        format: SecureTokenGenerator::FORMAT_BASE64_URL,
        length: 32,
        prefix: null,
        rawBytes: 'raw-bytes',
        metadata: []
    );

    $longMasked = $longToken->getMaskedValue();
    $shortMasked = $shortToken->getMaskedValue();

    expect($longMasked)->toStartWith('this');
    expect($longMasked)->toEndWith('ting');
    expect($longMasked)->toContain('*');
    expect($shortMasked)->toBe('***'); // Short tokens fully masked
});

it('throws exception for missing required fields in fromArray', function () {
    $incompleteData = [
        'value' => 'test-token',
        'type' => SecureTokenGenerator::TYPE_API_KEY,
        // Missing format, length, raw_bytes
    ];

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

it('throws exception for invalid base64 raw bytes in fromArray', function () {
    $invalidData = [
        'value' => 'test-token',
        'type' => SecureTokenGenerator::TYPE_API_KEY,
        'format' => SecureTokenGenerator::FORMAT_BASE64_URL,
        'length' => 32,
        'raw_bytes' => 'invalid-base64!@#',
    ];

    expect(fn () => SecureToken::fromArray($invalidData))
        ->toThrow(InvalidArgumentException::class, 'Invalid Base64 raw bytes');
});
