<?php

declare(strict_types=1);

use App\Framework\Cache\Cache;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\Discovery\DiscoveryRegistry;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Logging\Logger;
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 TestFiveMinuteJob
{
    public static int $executionCount = 0;

    public function handle(): array
    {
        self::$executionCount++;
        return ['status' => 'success', 'count' => self::$executionCount];
    }
}

#[Schedule(at: new Every(hours: 1))]
final class TestHourlyJob
{
    public static int $executionCount = 0;

    public function __invoke(): string
    {
        self::$executionCount++;
        return 'hourly job executed';
    }
}

describe('ScheduleDiscoveryService Integration', function () {
    beforeEach(function () {
        // Reset execution counters
        TestFiveMinuteJob::$executionCount = 0;
        TestHourlyJob::$executionCount = 0;

        // Create minimal logger mock
        $this->logger = Mockery::mock(Logger::class);
        $this->logger->shouldReceive('debug')->andReturn(null);
        $this->logger->shouldReceive('info')->andReturn(null);
        $this->logger->shouldReceive('warning')->andReturn(null);
        $this->logger->shouldReceive('error')->andReturn(null);

        $this->schedulerService = new SchedulerService(
            $this->logger
        );

        // Create minimal DiscoveryRegistry mock
        $this->discoveryRegistry = Mockery::mock(DiscoveryRegistry::class);

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

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

    it('discovers and registers scheduled jobs from attribute registry', function () {
        // Mock discovery to return our test jobs
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([
                TestFiveMinuteJob::class,
                TestHourlyJob::class
            ]);

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

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

        // Verify tasks were registered with scheduler
        $scheduledTasks = $this->schedulerService->getScheduledTasks();
        expect($scheduledTasks)->toHaveCount(2);
    });

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

        $this->scheduleDiscovery->discoverAndRegister();

        $scheduledTasks = $this->schedulerService->getScheduledTasks();

        $taskIds = array_map(fn($task) => $task->taskId, $scheduledTasks);

        expect($taskIds)->toContain('test-five-minute-job');
        expect($taskIds)->toContain('test-hourly-job');
    });

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

        $this->scheduleDiscovery->discoverAndRegister();

        // Get the scheduled task
        $scheduledTasks = $this->schedulerService->getScheduledTasks();
        expect($scheduledTasks)->toHaveCount(1);

        $task = $scheduledTasks[0];

        // Execute the task
        $result = $this->schedulerService->executeTask($task);

        expect($result->success)->toBeTrue();
        expect($result->result)->toBeArray();
        expect($result->result['status'])->toBe('success');
        expect($result->result['count'])->toBe(1);
        expect(TestFiveMinuteJob::$executionCount)->toBe(1);
    });

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

        $this->scheduleDiscovery->discoverAndRegister();

        $scheduledTasks = $this->schedulerService->getScheduledTasks();
        expect($scheduledTasks)->toHaveCount(1);

        $task = $scheduledTasks[0];

        // Execute the task
        $result = $this->schedulerService->executeTask($task);

        expect($result->success)->toBeTrue();
        expect($result->result)->toBe('hourly job executed');
        expect(TestHourlyJob::$executionCount)->toBe(1);
    });

    it('uses correct intervals from Every value object', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([
                TestFiveMinuteJob::class,  // 5 minutes = 300 seconds
                TestHourlyJob::class        // 1 hour = 3600 seconds
            ]);

        $this->scheduleDiscovery->discoverAndRegister();

        $scheduledTasks = $this->schedulerService->getScheduledTasks();

        // Find the 5-minute job
        $fiveMinuteTask = array_values(array_filter(
            $scheduledTasks,
            fn($task) => $task->taskId === 'test-five-minute-job'
        ))[0] ?? null;

        expect($fiveMinuteTask)->not->toBeNull();

        // Execute task
        $result = $this->schedulerService->executeTask($fiveMinuteTask);

        expect($result->success)->toBeTrue();

        // Get updated task
        $scheduledTasks = $this->schedulerService->getScheduledTasks();
        $updatedTask = array_values(array_filter(
            $scheduledTasks,
            fn($task) => $task->taskId === 'test-five-minute-job'
        ))[0] ?? null;

        // Next execution should be set (schedule updated)
        expect($updatedTask->nextExecution)->not->toBeNull();
    });

    it('handles jobs without handle() or __invoke() gracefully', function () {
        // Create a job class without handle() or __invoke()
        $invalidJobClass = new class {
            // No handle() or __invoke()
        };

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

        $this->scheduleDiscovery->discoverAndRegister();

        $scheduledTasks = $this->schedulerService->getScheduledTasks();
        expect($scheduledTasks)->toHaveCount(1);

        $task = $scheduledTasks[0];

        // Executing should throw RuntimeException
        $result = $this->schedulerService->executeTask($task);

        expect($result->success)->toBeFalse();
        expect($result->error)->toContain('must have handle() method or be callable');
    });

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

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

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

        $scheduledTasks = $this->schedulerService->getScheduledTasks();
        expect($scheduledTasks)->toHaveCount(0);
    });

    it('can retrieve scheduled tasks via getScheduledTasks()', function () {
        $this->discoveryRegistry
            ->shouldReceive('getClassesWithAttribute')
            ->with(Schedule::class)
            ->once()
            ->andReturn([
                TestFiveMinuteJob::class,
                TestHourlyJob::class
            ]);

        $this->scheduleDiscovery->discoverAndRegister();

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

        expect($tasks)->toHaveCount(2);
        expect($tasks[0])->toHaveProperty('taskId');
        expect($tasks[0])->toHaveProperty('nextExecution');
    });

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

        $this->scheduleDiscovery->discoverAndRegister();

        $scheduledTasks = $this->schedulerService->getScheduledTasks();

        // Execute both jobs
        foreach ($scheduledTasks as $task) {
            $result = $this->schedulerService->executeTask($task);
            expect($result->success)->toBeTrue();
        }

        // Both counters should have incremented
        expect(TestFiveMinuteJob::$executionCount)->toBe(1);
        expect(TestHourlyJob::$executionCount)->toBe(1);
    });
});
