<?php

declare(strict_types=1);

use App\Framework\Cuid\Cuid;

it('creates Cuid from string', function () {
    $value = 'cjld2cjxh0000qzrmn831i7rn';
    $cuid = Cuid::fromString($value);

    expect($cuid->toString())->toBe($value);
    expect($cuid->getValue())->toBe($value);
});

it('creates Cuid from components', function () {
    $timestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC
    $counter = 1234;
    $fingerprint = 'abcd';
    $random = '12345678';

    $cuid = Cuid::fromComponents($timestamp, $counter, $fingerprint, $random);

    expect($cuid->getTimestamp())->toBe($timestamp);
    expect($cuid->getCounter())->toBe($counter);
    expect($cuid->getFingerprint())->toBe($fingerprint);
    expect($cuid->getRandom())->toBe($random);
    expect($cuid->toString())->toStartWith('c');
});

it('validates Cuid length', function () {
    expect(fn () => Cuid::fromString('c' . str_repeat('a', 30)))
        ->toThrow(InvalidArgumentException::class, 'Cuid must be exactly 25 characters long');

    expect(fn () => Cuid::fromString('ctooshort'))
        ->toThrow(InvalidArgumentException::class, 'Cuid must be exactly 25 characters long');
});

it('validates Cuid prefix', function () {
    expect(fn () => Cuid::fromString('x' . str_repeat('a', 24)))
        ->toThrow(InvalidArgumentException::class, 'Cuid must start with "c"');
});

it('validates Cuid characters', function () {
    expect(fn () => Cuid::fromString('c' . str_repeat('!', 24)))
        ->toThrow(InvalidArgumentException::class, 'Cuid contains invalid characters');
});

it('validates component lengths', function () {
    $timestamp = 1609459200000;
    $counter = 1234;

    expect(fn () => Cuid::fromComponents($timestamp, $counter, 'abc', '12345678'))
        ->toThrow(InvalidArgumentException::class, 'Fingerprint must be exactly 4 characters');

    expect(fn () => Cuid::fromComponents($timestamp, $counter, 'abcd', '1234567'))
        ->toThrow(InvalidArgumentException::class, 'Random part must be exactly 8 characters');
});

it('parses timestamp correctly', function () {
    $expectedTimestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC
    $cuid = Cuid::fromComponents($expectedTimestamp, 0, 'abcd', '12345678');

    expect($cuid->getTimestamp())->toBe($expectedTimestamp);
});

it('gets DateTime from timestamp', function () {
    $timestamp = 1609459200500; // 2021-01-01 00:00:00.500 UTC
    $cuid = Cuid::fromComponents($timestamp, 0, 'abcd', '12345678');

    $dateTime = $cuid->getDateTime();

    expect($dateTime->getTimestamp())->toBe(1609459200); // Seconds part
    expect($dateTime->format('u'))->toBe('500000'); // Microseconds part
});

it('checks equality between Cuids', function () {
    $value = 'cjld2cjxh0000qzrmn831i7rn';
    $cuid1 = Cuid::fromString($value);
    $cuid2 = Cuid::fromString($value);
    $cuid3 = Cuid::fromString('cjld2cjxh0000qzrmn831i7ro');

    expect($cuid1->equals($cuid2))->toBeTrue();
    expect($cuid1->equals($cuid3))->toBeFalse();
});

it('compares Cuids for sorting', function () {
    $value1 = 'cjld2cjxh0000qzrmn831i7rn';
    $value2 = 'cjld2cjxh0000qzrmn831i7ro'; // Lexicographically greater

    $cuid1 = Cuid::fromString($value1);
    $cuid2 = Cuid::fromString($value2);

    expect($cuid1->compare($cuid2))->toBeLessThan(0);
    expect($cuid2->compare($cuid1))->toBeGreaterThan(0);
    expect($cuid1->compare($cuid1))->toBe(0);
});

it('checks age comparisons', function () {
    $olderTimestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC
    $newerTimestamp = 1609459201000; // 1 second later

    $olderCuid = Cuid::fromComponents($olderTimestamp, 0, 'abcd', '12345678');
    $newerCuid = Cuid::fromComponents($newerTimestamp, 0, 'abcd', '87654321');

    expect($olderCuid->isOlderThan($newerCuid))->toBeTrue();
    expect($newerCuid->isNewerThan($olderCuid))->toBeTrue();
    expect($olderCuid->isNewerThan($newerCuid))->toBeFalse();
    expect($newerCuid->isOlderThan($olderCuid))->toBeFalse();
});

it('calculates age in milliseconds', function () {
    $timestamp = intval(microtime(true) * 1000) - 5000; // 5 seconds ago
    $cuid = Cuid::fromComponents($timestamp, 0, 'abcd', '12345678');

    $ageMs = $cuid->getAgeInMilliseconds();

    expect($ageMs)->toBeGreaterThanOrEqual(4900); // Allow some tolerance
    expect($ageMs)->toBeLessThanOrEqual(5100);
});

it('calculates age in seconds', function () {
    $timestamp = intval(microtime(true) * 1000) - 3000; // 3 seconds ago
    $cuid = Cuid::fromComponents($timestamp, 0, 'abcd', '12345678');

    $ageSeconds = $cuid->getAgeInSeconds();

    expect($ageSeconds)->toBeGreaterThanOrEqual(2.9);
    expect($ageSeconds)->toBeLessThanOrEqual(3.1);
});

it('checks same process', function () {
    $cuid1 = Cuid::fromComponents(1609459200000, 0, 'abcd', '12345678');
    $cuid2 = Cuid::fromComponents(1609459201000, 1, 'abcd', '87654321'); // Same fingerprint
    $cuid3 = Cuid::fromComponents(1609459202000, 2, 'efgh', '11223344'); // Different fingerprint

    expect($cuid1->isSameProcess($cuid2))->toBeTrue();
    expect($cuid1->isSameProcess($cuid3))->toBeFalse();
});

it('converts to string using magic method', function () {
    $value = 'cjld2cjxh0000qzrmn831i7rn';
    $cuid = Cuid::fromString($value);

    expect((string)$cuid)->toBe($value);
});

it('parses all components correctly', function () {
    // Create a known Cuid and verify all components are parsed correctly
    $timestamp = 1609459200000;
    $counter = 1234;
    $fingerprint = 'test';
    $random = 'abcd1234';

    $cuid = Cuid::fromComponents($timestamp, $counter, $fingerprint, $random);

    // Parse it back
    $parsed = Cuid::fromString($cuid->toString());

    expect($parsed->getTimestamp())->toBe($timestamp);
    expect($parsed->getCounter())->toBe($counter);
    expect($parsed->getFingerprint())->toBe($fingerprint);
    expect($parsed->getRandom())->toBe($random);
});

it('handles Base36 conversion correctly', function () {
    // Test with various numbers to ensure Base36 conversion works
    $testCases = [
        ['timestamp' => 1609459200000, 'counter' => 0],
        ['timestamp' => 1609459200000, 'counter' => 35], // Max single digit in Base36
        ['timestamp' => 1609459200000, 'counter' => 36], // Two digits in Base36
        ['timestamp' => 1609459200000, 'counter' => 1295], // Multiple digits
    ];

    foreach ($testCases as $case) {
        $cuid = Cuid::fromComponents(
            $case['timestamp'],
            $case['counter'],
            'test',
            'abcd1234'
        );

        $parsed = Cuid::fromString($cuid->toString());

        expect($parsed->getTimestamp())->toBe($case['timestamp']);
        expect($parsed->getCounter())->toBe($case['counter']);
    }
});

it('throws exception for empty Cuid', function () {
    expect(fn () => Cuid::fromString(''))
        ->toThrow(InvalidArgumentException::class, 'Cuid cannot be empty');
});

it('maintains lexicographic ordering by timestamp', function () {
    $timestamp1 = 1609459200000;
    $timestamp2 = 1609459201000; // 1 second later

    $cuid1 = Cuid::fromComponents($timestamp1, 0, 'abcd', '12345678');
    $cuid2 = Cuid::fromComponents($timestamp2, 0, 'abcd', '12345678');

    // String comparison should match timestamp order
    expect($cuid1->toString() < $cuid2->toString())->toBeTrue();
});

it('handles maximum timestamp values', function () {
    $maxTimestamp = (36 ** 8) - 1; // Max value for 8 Base36 digits
    $cuid = Cuid::fromComponents($maxTimestamp, 0, 'abcd', '12345678');

    expect($cuid->getTimestamp())->toBe($maxTimestamp);
});

it('handles maximum counter values', function () {
    $maxCounter = (36 ** 4) - 1; // Max value for 4 Base36 digits
    $cuid = Cuid::fromComponents(1609459200000, $maxCounter, 'abcd', '12345678');

    expect($cuid->getCounter())->toBe($maxCounter);
});
