<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\LiveComponents\Services\UploadSessionIdGenerator;
use App\Framework\LiveComponents\ValueObjects\ChunkMetadata;
use App\Framework\LiveComponents\ValueObjects\ChunkHash;
use App\Framework\LiveComponents\ValueObjects\UploadSession;
use App\Framework\LiveComponents\ValueObjects\UploadSessionId;
use App\Framework\LiveComponents\ValueObjects\QuarantineStatus;
use App\Framework\Random\RandomGenerator;
use Tests\Support\InMemoryUploadSessionStore;

describe('InMemoryUploadSessionStore', function () {
    beforeEach(function () {
        $this->store = new InMemoryUploadSessionStore();

        // Create RandomGenerator for UploadSessionId generation
        $this->randomGen = new class implements RandomGenerator {
            public function bytes(int $length): string
            {
                return random_bytes($length);
            }

            public function int(int $min, int $max): int
            {
                return random_int($min, $max);
            }

            public function float(): float
            {
                return (float) random_int(0, PHP_INT_MAX) / PHP_INT_MAX;
            }
        };

        $this->sessionIdGenerator = new UploadSessionIdGenerator($this->randomGen);
    });

    it('saves and retrieves session', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        $this->store->save($session);

        $retrieved = $this->store->get($sessionId);
        expect($retrieved !== null)->toBeTrue();
        expect($retrieved->sessionId->equals($sessionId))->toBeTrue();
        expect($retrieved->fileName)->toBe('test.pdf');
        expect($retrieved->totalChunks)->toBe(10);
    });

    it('returns null for non-existent session', function () {
        $sessionId = $this->sessionIdGenerator->generate();

        $retrieved = $this->store->get($sessionId);
        expect($retrieved)->toBeNull();
    });

    it('checks session existence correctly', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        expect($this->store->exists($sessionId))->toBeFalse();

        $this->store->save($session);

        expect($this->store->exists($sessionId))->toBeTrue();
    });

    it('deletes session', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        $this->store->save($session);
        expect($this->store->exists($sessionId))->toBeTrue();

        $this->store->delete($sessionId);
        expect($this->store->exists($sessionId))->toBeFalse();
        expect($this->store->get($sessionId))->toBeNull();
    });

    it('deletes non-existent session without error', function () {
        $sessionId = $this->sessionIdGenerator->generate();

        expect($this->store->exists($sessionId))->toBeFalse();

        $this->store->delete($sessionId);

        expect($this->store->exists($sessionId))->toBeFalse();
    });

    it('updates existing session', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        $this->store->save($session);

        // Add chunk to session
        $chunk = ChunkMetadata::create(
            index: 0,
            size: Byte::fromKilobytes(512),
            hash: ChunkHash::fromData('chunk data')
        );
        $updatedSession = $session->withChunk($chunk);

        $this->store->save($updatedSession);

        $retrieved = $this->store->get($sessionId);
        expect($retrieved !== null)->toBeTrue();
        expect(count($retrieved->chunks))->toBe(1);
        expect($retrieved->chunks[0]->index)->toBe(0);
    });

    it('cleans up expired sessions', function () {
        // Create expired session (TTL in past)
        $expiredSessionId = $this->sessionIdGenerator->generate();
        $expiredSession = new UploadSession(
            sessionId: $expiredSessionId,
            componentId: 'test-component',
            fileName: 'expired.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10,
            createdAt: new DateTimeImmutable('-2 hours'),
            expiresAt: new DateTimeImmutable('-1 hour')
        );

        // Create valid session
        $validSessionId = $this->sessionIdGenerator->generate();
        $validSession = UploadSession::create(
            sessionId: $validSessionId,
            componentId: 'test-component',
            fileName: 'valid.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        $this->store->save($expiredSession);
        $this->store->save($validSession);

        expect($this->store->count())->toBe(2);

        $cleaned = $this->store->cleanupExpired();

        expect($cleaned)->toBe(1);
        expect($this->store->count())->toBe(1);
        expect($this->store->exists($expiredSessionId))->toBeFalse();
        expect($this->store->exists($validSessionId))->toBeTrue();
    });

    it('cleanup returns zero when no expired sessions', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        $this->store->save($session);

        $cleaned = $this->store->cleanupExpired();

        expect($cleaned)->toBe(0);
        expect($this->store->count())->toBe(1);
    });

    it('cleanup works on empty store', function () {
        expect($this->store->count())->toBe(0);

        $cleaned = $this->store->cleanupExpired();

        expect($cleaned)->toBe(0);
        expect($this->store->count())->toBe(0);
    });

    it('handles multiple sessions', function () {
        $sessions = [];
        for ($i = 0; $i < 5; $i++) {
            $sessionId = $this->sessionIdGenerator->generate();
            $session = UploadSession::create(
                sessionId: $sessionId,
                componentId: "component-{$i}",
                fileName: "file-{$i}.pdf",
                totalSize: Byte::fromMegabytes($i + 1),
                totalChunks: ($i + 1) * 2
            );
            $sessions[] = $session;
            $this->store->save($session);
        }

        expect($this->store->count())->toBe(5);

        foreach ($sessions as $session) {
            $retrieved = $this->store->get($session->sessionId);
            expect($retrieved !== null)->toBeTrue();
            expect($retrieved->sessionId->equals($session->sessionId))->toBeTrue();
        }
    });

    it('getAll returns all sessions as array', function () {
        $session1 = UploadSession::create(
            sessionId: $this->sessionIdGenerator->generate(),
            componentId: 'component-1',
            fileName: 'file1.pdf',
            totalSize: Byte::fromMegabytes(1),
            totalChunks: 2
        );

        $session2 = UploadSession::create(
            sessionId: $this->sessionIdGenerator->generate(),
            componentId: 'component-2',
            fileName: 'file2.pdf',
            totalSize: Byte::fromMegabytes(2),
            totalChunks: 4
        );

        $this->store->save($session1);
        $this->store->save($session2);

        $allSessions = $this->store->getAll();

        expect($allSessions)->toBeArray();
        expect(count($allSessions))->toBe(2);
        expect($allSessions[0])->toBeInstanceOf(UploadSession::class);
        expect($allSessions[1])->toBeInstanceOf(UploadSession::class);
    });

    it('count returns correct session count', function () {
        expect($this->store->count())->toBe(0);

        $session1 = UploadSession::create(
            sessionId: $this->sessionIdGenerator->generate(),
            componentId: 'component-1',
            fileName: 'file1.pdf',
            totalSize: Byte::fromMegabytes(1),
            totalChunks: 2
        );
        $this->store->save($session1);

        expect($this->store->count())->toBe(1);

        $session2 = UploadSession::create(
            sessionId: $this->sessionIdGenerator->generate(),
            componentId: 'component-2',
            fileName: 'file2.pdf',
            totalSize: Byte::fromMegabytes(2),
            totalChunks: 4
        );
        $this->store->save($session2);

        expect($this->store->count())->toBe(2);

        $this->store->delete($session1->sessionId);

        expect($this->store->count())->toBe(1);
    });

    it('clear removes all sessions', function () {
        for ($i = 0; $i < 3; $i++) {
            $session = UploadSession::create(
                sessionId: $this->sessionIdGenerator->generate(),
                componentId: "component-{$i}",
                fileName: "file-{$i}.pdf",
                totalSize: Byte::fromMegabytes($i + 1),
                totalChunks: ($i + 1) * 2
            );
            $this->store->save($session);
        }

        expect($this->store->count())->toBe(3);

        $this->store->clear();

        expect($this->store->count())->toBe(0);
        expect($this->store->getAll())->toBe([]);
    });

    it('handles session with quarantine status', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 10
        );

        // Proper lifecycle: PENDING → SCANNING → APPROVED
        $scanningSession = $session->withQuarantineStatus(QuarantineStatus::SCANNING);
        $approvedSession = $scanningSession->withQuarantineStatus(QuarantineStatus::APPROVED);

        $this->store->save($approvedSession);

        $retrieved = $this->store->get($sessionId);
        expect($retrieved !== null)->toBeTrue();
        expect($retrieved->quarantineStatus)->toBe(QuarantineStatus::APPROVED);
    });

    it('handles session with chunks', function () {
        $sessionId = $this->sessionIdGenerator->generate();
        $session = UploadSession::create(
            sessionId: $sessionId,
            componentId: 'test-component',
            fileName: 'test.pdf',
            totalSize: Byte::fromMegabytes(5),
            totalChunks: 3
        );

        // Add chunks
        $chunk1 = ChunkMetadata::create(
            index: 0,
            size: Byte::fromKilobytes(512),
            hash: ChunkHash::fromData('chunk 0 data')
        );
        $chunk2 = ChunkMetadata::create(
            index: 1,
            size: Byte::fromKilobytes(512),
            hash: ChunkHash::fromData('chunk 1 data')
        );

        $sessionWithChunks = $session->withChunk($chunk1)->withChunk($chunk2);

        $this->store->save($sessionWithChunks);

        $retrieved = $this->store->get($sessionId);
        expect($retrieved !== null)->toBe(true);
        expect(count($retrieved->chunks))->toBe(2);
    });

    it('isolates sessions by different session IDs', function () {
        $sessionId1 = $this->sessionIdGenerator->generate();
        $sessionId2 = $this->sessionIdGenerator->generate();

        $session1 = UploadSession::create(
            sessionId: $sessionId1,
            componentId: 'component-1',
            fileName: 'file1.pdf',
            totalSize: Byte::fromMegabytes(1),
            totalChunks: 2
        );

        $session2 = UploadSession::create(
            sessionId: $sessionId2,
            componentId: 'component-2',
            fileName: 'file2.pdf',
            totalSize: Byte::fromMegabytes(2),
            totalChunks: 4
        );

        $this->store->save($session1);
        $this->store->save($session2);

        $retrieved1 = $this->store->get($sessionId1);
        $retrieved2 = $this->store->get($sessionId2);

        expect($retrieved1->fileName)->toBe('file1.pdf');
        expect($retrieved2->fileName)->toBe('file2.pdf');
        expect($retrieved1->totalChunks)->toBe(2);
        expect($retrieved2->totalChunks)->toBe(4);
    });
});
