<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Async;

use App\Framework\Async\AsyncPromiseFactory;
use App\Framework\Async\AsyncService;
use App\Framework\Async\AsyncTimer;
use App\Framework\Async\FiberManager;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\SystemTimer;

describe('AsyncService', function () {
    beforeEach(function () {
        $this->clock = new SystemClock();
        $this->timer = new SystemTimer($this->clock);
        $this->fiberManager = new FiberManager($this->clock, $this->timer);
        $this->promiseFactory = new AsyncPromiseFactory($this->fiberManager);
        $this->asyncTimer = new AsyncTimer($this->fiberManager, $this->clock, $this->timer);

        $this->service = new AsyncService(
            $this->fiberManager,
            $this->promiseFactory,
            $this->asyncTimer,
            $this->clock,
            $this->timer
        );
    });

    it('executes async operation', function () {
        $fiber = $this->service->async(fn() => 'async-result');

        expect($fiber->isTerminated())->toBeTrue();
        expect($fiber->getReturn())->toBe('async-result');
    });

    it('creates promise from operation', function () {
        $promise = $this->service->promise(fn() => 'promise-result');

        usleep(10000); // Give fiber time to execute

        expect($promise->await())->toBe('promise-result');
    });

    it('runs multiple operations in parallel with variadic', function () {
        $promise = $this->service->parallel(
            fn() => 'result1',
            fn() => 'result2',
            fn() => 'result3'
        );

        usleep(20000);

        $results = $promise->await();
        expect($results)->toHaveLength(3);
        expect($results[0])->toBe('result1');
        expect($results[1])->toBe('result2');
        expect($results[2])->toBe('result3');
    });

    it('executes operation with timeout', function () {
        $result = $this->service->withTimeout(
            fn() => 'completed',
            Duration::fromSeconds(1)
        );

        expect($result)->toBe('completed');
    });

    it('delays execution', function () {
        $start = $this->clock->time();

        $fiber = $this->service->delay(Duration::fromMilliseconds(50));

        // Wait for delay to complete
        usleep(60000);

        $elapsed = $start->age($this->clock);
        expect($elapsed->toMilliseconds())->toBeGreaterThanOrEqual(50);
    });

    it('measures execution time', function () {
        $promise = $this->service->measure(function () {
            usleep(20000); // 20ms
            return 'measured-result';
        });

        usleep(30000);

        $result = $promise->await();

        expect($result)->toHaveKey('result');
        expect($result)->toHaveKey('duration');
        expect($result)->toHaveKey('milliseconds');
        expect($result['result'])->toBe('measured-result');
        expect($result['milliseconds'])->toBeGreaterThan(0);
    });

    it('schedules callback after delay', function () {
        $executed = false;

        $id = $this->service->schedule(
            function () use (&$executed) {
                $executed = true;
            },
            Duration::fromMilliseconds(20)
        );

        expect($id)->toBeString();

        // Wait for schedule to execute
        usleep(30000);

        // Note: Timer execution might vary, this is a basic check
        expect($id)->not->toBeEmpty();
    });

    it('repeats callback at interval', function () {
        $counter = 0;

        $id = $this->service->repeat(
            function () use (&$counter) {
                $counter++;
            },
            Duration::fromMilliseconds(10)
        );

        // Wait for multiple executions
        usleep(50000);

        // Cancel the repeat
        $cancelled = $this->service->cancel($id);
        expect($cancelled)->toBeTrue();

        // Counter should have incremented at least once
        // Note: Exact count depends on timing
        expect($id)->not->toBeEmpty();
    });

    it('cancels scheduled operation', function () {
        $executed = false;

        $id = $this->service->schedule(
            function () use (&$executed) {
                $executed = true;
            },
            Duration::fromMilliseconds(50)
        );

        $cancelled = $this->service->cancel($id);
        expect($cancelled)->toBeTrue();

        // Wait longer than scheduled delay
        usleep(60000);

        // Should still be false since we cancelled
        expect($executed)->toBeFalse();
    });

    it('batches operations with concurrency control using variadic', function () {
        $results = $this->service->batch(
            5, // max concurrency
            fn() => 'batch1',
            fn() => 'batch2',
            fn() => 'batch3',
            fn() => 'batch4',
            fn() => 'batch5',
            fn() => 'batch6'
        );

        expect($results)->toHaveLength(6);
    });

    it('provides async statistics', function () {
        $this->service->async(fn() => 'test');

        $stats = $this->service->getStats();

        expect($stats)->toHaveKey('fiber_manager');
        expect($stats)->toHaveKey('async_timer');
        expect($stats['fiber_manager'])->toBeArray();
    });

    it('handles exceptions in async operations', function () {
        expect(function () {
            $fiber = $this->service->async(function () {
                throw new \RuntimeException('async error');
            });

            // Exception will be thrown when we try to get the return value
            $fiber->getReturn();
        })->toThrow(\RuntimeException::class);
    });

    it('chains multiple async operations', function () {
        $result = null;

        $this->service->async(function () use (&$result) {
            return 'step1';
        });

        $this->service->async(function () use (&$result) {
            $result = 'step2';
            return $result;
        });

        expect($result)->toBe('step2');
    });

    it('combines async and promise operations', function () {
        // Start with async fiber
        $fiber = $this->service->async(fn() => 10);

        // Continue with promise
        $promise = $this->service->promise(fn() => $fiber->getReturn() * 2);

        usleep(10000);

        expect($promise->await())->toBe(20);
    });
});
