<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Discovery\DiscoveryRegistry;
use App\Framework\Scheduler\Services\SchedulerService;
use App\Framework\Worker\Every;
use App\Framework\Worker\Schedule;
use App\Framework\Worker\ScheduleDiscoveryService;

// Test job classes with Schedule attribute
#[Schedule(at: new Every(minutes: 5))]
final class TestScheduledJob
{
    public function handle(): array
    {
        return ['status' => 'success', 'executed_at' => time()];
    }
}

#[Schedule(at: new Every(hours: 1))]
final class HourlyTestJob
{
    public function __invoke(): string
    {
        return 'hourly job executed';
    }
}

#[Schedule(at: new Every(days: 1))]
final class DailyTestJob
{
    // No handle() or __invoke() - should throw exception
}

describe('ScheduleDiscoveryService', function () {
    beforeEach(function () {
        // Create mock DiscoveryRegistry
        $this->discoveryRegistry = Mockery::mock(DiscoveryRegistry::class);

        // Create mock SchedulerService
        $this->schedulerService = Mockery::mock(SchedulerService::class);

        $this->scheduleDiscovery = new ScheduleDiscoveryService(
            $this->discoveryRegistry,
            $this->schedulerService
        );
    });

    afterEach(function () {
        Mockery::close();
    });

    it('discovers and registers scheduled jobs', function () {
        // Mock discovery registry to return test job classes
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([
                TestScheduledJob::class,
                HourlyTestJob::class
            ]);

        // Expect scheduler to be called for each job
        $this->schedulerService
            ->shouldReceive('schedule')
            ->twice()
            ->withArgs(function ($taskId, $schedule, $task) {
                // Verify task ID is kebab-case
                expect($taskId)->toMatch('/^[a-z0-9-]+$/');

                // Verify schedule is IntervalSchedule
                expect($schedule)->toBeInstanceOf(\App\Framework\Scheduler\Schedules\IntervalSchedule::class);

                // Verify task is callable
                expect($task)->toBeCallable();

                return true;
            });

        $registered = $this->scheduleDiscovery->discoverAndRegister();

        expect($registered)->toBe(2);
    });

    it('converts Every to IntervalSchedule correctly', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([TestScheduledJob::class]);

        $this->schedulerService
            ->shouldReceive('schedule')
            ->once()
            ->withArgs(function ($taskId, $schedule, $task) {
                // TestScheduledJob has Every(minutes: 5) = 300 seconds
                // IntervalSchedule should use this duration
                expect($schedule)->toBeInstanceOf(\App\Framework\Scheduler\Schedules\IntervalSchedule::class);

                return true;
            });

        $this->scheduleDiscovery->discoverAndRegister();
    });

    it('generates kebab-case task IDs from class names', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([TestScheduledJob::class, HourlyTestJob::class]);

        $capturedTaskIds = [];

        $this->schedulerService
            ->shouldReceive('schedule')
            ->twice()
            ->withArgs(function ($taskId) use (&$capturedTaskIds) {
                $capturedTaskIds[] = $taskId;
                return true;
            });

        $this->scheduleDiscovery->discoverAndRegister();

        expect($capturedTaskIds)->toContain('test-scheduled-job');
        expect($capturedTaskIds)->toContain('hourly-test-job');
    });

    it('executes jobs with handle() method', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([TestScheduledJob::class]);

        $capturedTask = null;

        $this->schedulerService
            ->shouldReceive('schedule')
            ->once()
            ->withArgs(function ($taskId, $schedule, $task) use (&$capturedTask) {
                $capturedTask = $task;
                return true;
            });

        $this->scheduleDiscovery->discoverAndRegister();

        // Execute the captured task
        $result = $capturedTask();

        expect($result)->toBeArray();
        expect($result['status'])->toBe('success');
        expect($result)->toHaveKey('executed_at');
    });

    it('executes callable jobs', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([HourlyTestJob::class]);

        $capturedTask = null;

        $this->schedulerService
            ->shouldReceive('schedule')
            ->once()
            ->withArgs(function ($taskId, $schedule, $task) use (&$capturedTask) {
                $capturedTask = $task;
                return true;
            });

        $this->scheduleDiscovery->discoverAndRegister();

        // Execute the captured task
        $result = $capturedTask();

        expect($result)->toBe('hourly job executed');
    });

    it('throws exception for jobs without handle() or __invoke()', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([DailyTestJob::class]);

        $capturedTask = null;

        $this->schedulerService
            ->shouldReceive('schedule')
            ->once()
            ->withArgs(function ($taskId, $schedule, $task) use (&$capturedTask) {
                $capturedTask = $task;
                return true;
            });

        $this->scheduleDiscovery->discoverAndRegister();

        // Executing the task should throw exception
        expect(fn() => $capturedTask())->toThrow(
            \RuntimeException::class,
            'must have handle() method or be callable'
        );
    });

    it('handles multiple Schedule attributes on same class', function () {
        // Create a test class with multiple schedules (IS_REPEATABLE)
        $testClass = new class {
            #[Schedule(at: new Every(minutes: 5))]
            #[Schedule(at: new Every(hours: 1))]
            public function handle(): string
            {
                return 'multi-schedule job';
            }
        };

        $className = $testClass::class;

        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([$className]);

        // Should register twice (one for each Schedule attribute)
        $this->schedulerService
            ->shouldReceive('schedule')
            ->twice();

        $registered = $this->scheduleDiscovery->discoverAndRegister();

        expect($registered)->toBe(2);
    });

    it('returns 0 when no scheduled jobs found', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([]);

        $this->schedulerService
            ->shouldReceive('schedule')
            ->never();

        $registered = $this->scheduleDiscovery->discoverAndRegister();

        expect($registered)->toBe(0);
    });

    it('delegates getScheduledTasks to SchedulerService', function () {
        $expectedTasks = [
            ['taskId' => 'test-task-1'],
            ['taskId' => 'test-task-2']
        ];

        $this->schedulerService
            ->shouldReceive('getScheduledTasks')
            ->once()
            ->andReturn($expectedTasks);

        $tasks = $this->scheduleDiscovery->getScheduledTasks();

        expect($tasks)->toBe($expectedTasks);
    });
});
