<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Async;

use App\Framework\Async\AsyncPool;
use App\Framework\Async\FiberManager;
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\SystemTimer;

describe('AsyncPool', function () {
    beforeEach(function () {
        $clock = new SystemClock();
        $timer = new SystemTimer();
        $fiberManager = new FiberManager($clock, $timer);

        $this->pool = new AsyncPool($fiberManager, maxConcurrency: 3);
    });

    it('executes operations with limited concurrency', function () {
        $executedOperations = [];

        for ($i = 1; $i <= 10; $i++) {
            $this->pool->add(function () use ($i, &$executedOperations) {
                $executedOperations[] = $i;
                return "result-{$i}";
            }, "op{$i}");
        }

        $results = $this->pool->execute();

        expect(count($results))->toBe(10);
        expect($results['op1'])->toBe('result-1');
        expect($results['op10'])->toBe('result-10');
    });

    it('respects max concurrency limit', function () {
        $stats = $this->pool->getStats();

        expect($stats['max_concurrency'])->toBe(3);
        expect($stats['pending'])->toBe(0);
        expect($stats['active'])->toBe(0);
        expect($stats['completed'])->toBe(0);
    });

    it('tracks operation statistics', function () {
        $this->pool->add(fn() => 'test1', 'op1');
        $this->pool->add(fn() => 'test2', 'op2');

        $statsBefore = $this->pool->getStats();
        expect($statsBefore['pending'])->toBe(2);

        $this->pool->execute();

        $statsAfter = $this->pool->getStats();
        expect($statsAfter['completed'])->toBe(2);
        expect($statsAfter['pending'])->toBe(0);
    });

    it('awaits specific operation result', function () {
        $this->pool->add(fn() => 'result-1', 'op1');
        $this->pool->add(fn() => 'result-2', 'op2');

        // Start execution in background
        $this->pool->execute();

        $result = $this->pool->await('op1');
        expect($result)->toBe('result-1');
    });

    it('handles exceptions in operations', function () {
        // Test that exceptions during execution are caught and stored as results
        $this->pool->add(fn() => 'success', 'op1');
        $this->pool->add(function () {
            throw new \RuntimeException('error');
        }, 'op2');

        // Execute all operations - exceptions are caught and stored as results
        $results = $this->pool->execute();

        expect($results['op1'])->toBe('success');
        expect($results['op2'])->toBeInstanceOf(\RuntimeException::class);
        expect($results['op2']->getMessage())->toBe('error');
    });

    it('generates unique IDs when not provided', function () {
        $id1 = $this->pool->add(fn() => 'test1');
        $id2 = $this->pool->add(fn() => 'test2');

        expect($id1)->not->toBe($id2);
        expect(str_starts_with($id1, 'pool_'))->toBeTrue();
        expect(str_starts_with($id2, 'pool_'))->toBeTrue();
    });

    it('executes all operations eventually', function () {
        $completed = [];

        for ($i = 1; $i <= 50; $i++) {
            $this->pool->add(function () use ($i, &$completed) {
                usleep(1000); // 1ms delay
                $completed[] = $i;
                return $i;
            });
        }

        $results = $this->pool->execute();

        expect(count($results))->toBe(50);
        expect(count($completed))->toBe(50);
    });

    it('maintains operation order in results', function () {
        $operations = [
            'first' => fn() => 'first-result',
            'second' => fn() => 'second-result',
            'third' => fn() => 'third-result',
        ];

        foreach ($operations as $id => $operation) {
            $this->pool->add($operation, $id);
        }

        $results = $this->pool->execute();

        expect($results['first'])->toBe('first-result');
        expect($results['second'])->toBe('second-result');
        expect($results['third'])->toBe('third-result');
    });
});
