<?php

declare(strict_types=1);

use App\Framework\Cuid\Cuid;
use App\Framework\Cuid\CuidGenerator;
use App\Framework\Random\SecureRandomGenerator;

it('generates Cuid with current timestamp', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $cuid = $generator->generate();

    expect($cuid)->toBeInstanceOf(Cuid::class);
    expect($cuid->toString())->toStartWith('c');
    expect($cuid->toString())->toHaveLength(Cuid::LENGTH);

    $currentTimeMs = intval(microtime(true) * 1000);
    expect($cuid->getTimestamp())->toBeGreaterThanOrEqual($currentTimeMs - 100);
    expect($cuid->getTimestamp())->toBeLessThanOrEqual($currentTimeMs + 100);
});

it('generates Cuid at specific timestamp', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $timestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC

    $cuid = $generator->generateAt($timestamp);

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

it('generates Cuid in the past', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $millisecondsAgo = 5000; // 5 seconds ago

    $cuid = $generator->generateInPast($millisecondsAgo);

    $expectedTimestamp = intval(microtime(true) * 1000) - $millisecondsAgo;
    expect($cuid->getTimestamp())->toBeGreaterThanOrEqual($expectedTimestamp - 100);
    expect($cuid->getTimestamp())->toBeLessThanOrEqual($expectedTimestamp + 100);
});

it('generates batch of Cuids with incrementing counters', function () {
    $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'test');
    $generator->resetCounter(); // Start from 0

    $cuids = $generator->generateBatch(5);

    expect($cuids)->toHaveCount(5);
    expect($cuids[0])->toBeInstanceOf(Cuid::class);

    // All should have the same timestamp and fingerprint
    $firstTimestamp = $cuids[0]->getTimestamp();
    foreach ($cuids as $i => $cuid) {
        expect($cuid->getTimestamp())->toBe($firstTimestamp);
        expect($cuid->getFingerprint())->toBe('test');
        expect($cuid->getCounter())->toBe($i); // Incrementing counter
    }

    // All should be unique
    $values = array_map(fn ($cuid) => $cuid->toString(), $cuids);
    $uniqueValues = array_unique($values);
    expect($uniqueValues)->toHaveCount(5);
});

it('generates sequence of Cuids with incrementing timestamps', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $count = 3;
    $interval = 1000; // 1 second intervals

    $cuids = $generator->generateSequence($count, $interval);

    expect($cuids)->toHaveCount($count);

    // Check timestamps are incrementing
    for ($i = 1; $i < $count; $i++) {
        $timeDiff = $cuids[$i]->getTimestamp() - $cuids[$i - 1]->getTimestamp();
        expect($timeDiff)->toBe($interval);
    }
});

it('generates Cuid with specific counter', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $counter = 1234;

    $cuid = $generator->generateWithCounter($counter);

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

it('increments counter correctly', function () {
    $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'test');
    $generator->resetCounter();

    $cuid1 = $generator->generate();
    $cuid2 = $generator->generate();
    $cuid3 = $generator->generate();

    expect($cuid1->getCounter())->toBe(0);
    expect($cuid2->getCounter())->toBe(1);
    expect($cuid3->getCounter())->toBe(2);
});

it('counter increments and resets correctly', function () {
    $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'test');
    $generator->resetCounter();

    // Generate a few Cuids to test counter increment
    $cuid1 = $generator->generate();
    $cuid2 = $generator->generate();
    $cuid3 = $generator->generate();

    expect($cuid1->getCounter())->toBe(0);
    expect($cuid2->getCounter())->toBe(1);
    expect($cuid3->getCounter())->toBe(2);
    expect($generator->getCurrentCounter())->toBe(3);

    // Test reset
    $generator->resetCounter();
    expect($generator->getCurrentCounter())->toBe(0);
});

it('validates Cuid strings', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());

    $validCuid = $generator->generate();
    expect($generator->isValid($validCuid->toString()))->toBeTrue();

    expect($generator->isValid('invalid'))->toBeFalse();
    expect($generator->isValid(''))->toBeFalse();
    expect($generator->isValid('x' . str_repeat('a', 24)))->toBeFalse(); // Wrong prefix
    expect($generator->isValid('c' . str_repeat('!', 24)))->toBeFalse(); // Invalid characters
});

it('parses Cuid strings', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $original = $generator->generate();

    $parsed = $generator->parse($original->toString());

    expect($parsed->equals($original))->toBeTrue();
});

it('generates consistent fingerprints', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());

    $cuid1 = $generator->generate();
    $cuid2 = $generator->generate();

    // Same generator should produce same fingerprint
    expect($cuid1->getFingerprint())->toBe($cuid2->getFingerprint());
    expect($generator->isSameGenerator($cuid1))->toBeTrue();
    expect($generator->isSameGenerator($cuid2))->toBeTrue();
});

it('uses custom fingerprint', function () {
    $customFingerprint = 'abcd';
    $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), $customFingerprint);

    expect($generator->getFingerprint())->toBe($customFingerprint);

    $cuid = $generator->generate();
    expect($cuid->getFingerprint())->toBe($customFingerprint);
});

it('generates different fingerprints for different generators', function () {
    $generator1 = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'aaaa');
    $generator2 = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'bbbb');

    expect($generator1->getFingerprint())->not->toBe($generator2->getFingerprint());

    $cuid1 = $generator1->generate();
    $cuid2 = $generator2->generate();

    expect($generator1->isSameGenerator($cuid2))->toBeFalse();
    expect($generator2->isSameGenerator($cuid1))->toBeFalse();
});

it('resets counter correctly', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());

    // Generate a few to increment counter
    $generator->generate();
    $generator->generate();
    expect($generator->getCurrentCounter())->toBeGreaterThan(0);

    // Reset and check
    $generator->resetCounter();
    expect($generator->getCurrentCounter())->toBe(0);
});

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

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

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

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

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

    expect(fn () => $generator->generateSequence(1001))
        ->toThrow(InvalidArgumentException::class, 'Sequence size cannot exceed 1000');
});

it('throws exception for negative interval', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());

    expect(fn () => $generator->generateSequence(3, -1))
        ->toThrow(InvalidArgumentException::class, 'Interval must be non-negative');
});

it('throws exception for invalid counter value', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $maxCounter = 36 ** Cuid::COUNTER_LENGTH;

    expect(fn () => $generator->generateWithCounter(-1))
        ->toThrow(InvalidArgumentException::class, 'Counter must be between 0 and');

    expect(fn () => $generator->generateWithCounter($maxCounter))
        ->toThrow(InvalidArgumentException::class, 'Counter must be between 0 and');
});

it('throws exception for invalid fingerprint length', function () {
    expect(fn () => CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'abc'))
        ->toThrow(InvalidArgumentException::class, 'Fingerprint must be exactly 4 characters');

    expect(fn () => CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'abcde'))
        ->toThrow(InvalidArgumentException::class, 'Fingerprint must be exactly 4 characters');
});

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

    expect($generator)->toBeInstanceOf(CuidGenerator::class);
    expect($generator->generate())->toBeInstanceOf(Cuid::class);
});

it('generates sortable Cuids by timestamp', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());

    $older = $generator->generateInPast(5000);
    $newer = $generator->generate();

    // Lexicographic comparison should match timestamp order
    expect($older->compare($newer))->toBeLessThan(0);
    expect($older->toString() < $newer->toString())->toBeTrue();
});

it('generates unique Cuids across multiple calls', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $cuids = [];
    $count = 1000;

    for ($i = 0; $i < $count; $i++) {
        $cuids[] = $generator->generate()->toString();
    }

    $uniqueCuids = array_unique($cuids);
    expect($uniqueCuids)->toHaveCount($count);
});

it('generates only lowercase characters', function () {
    $generator = new CuidGenerator(new SecureRandomGenerator());
    $cuid = $generator->generate();

    $cuidString = $cuid->toString();
    expect($cuidString)->toBe(strtolower($cuidString)); // Should already be lowercase
    expect($cuidString)->toMatch('/^[c0-9a-z]+$/'); // Only valid Base36 chars
});
