<?php

use classes\cron_schedule;
use classes\cron_task_registry;

it('discovers module-owned cron task definitions', function (): void {
    $registry = new cron_task_registry(app_path('modules'));
    $definitions = $registry->definitions();

    expect($definitions)->toHaveCount(22);
    expect(array_keys($definitions))->toContain(
        'system.sync_logs',
        'backups.process_jobs',
        'backups.prune_retention',
        'economic.transfer_queue',
        'dynamicimages.pre_render',
        'weatherapi.preload_department_responses',
        'goals.progress_alerts',
        'selfserve.activate_opening_cleaner_relays'
    );

    $transferQueue = $registry->get('EconomicTransferQueueCron');
    expect($transferQueue)->not->toBeNull();
    expect($transferQueue->id)->toBe('economic.transfer_queue');
    expect($transferQueue->module)->toBe('economic');
    expect($transferQueue->schedule)->toBe(['type' => 'interval', 'seconds' => 30]);
});

it('keeps every discovered cron task in a module cron folder', function (): void {
    $files = glob(app_path('modules/*/cron/tasks.php')) ?: [];
    $modules = array_map(
        static fn(string $file): string => basename(dirname(dirname($file))),
        $files
    );

    $registry = new cron_task_registry(app_path('modules'));
    foreach ($registry->definitions() as $definition) {
        expect($modules)->toContain($definition->module);
    }
});

it('normalizes and advances interval schedules without tight loops', function (): void {
    expect(cron_schedule::normalize(['type' => 'interval', 'seconds' => 60]))
        ->toBe(['type' => 'interval', 'seconds' => 60]);

    $now = strtotime('2026-07-09 12:10:00');
    $next = cron_schedule::nextRunAt(
        ['type' => 'interval', 'seconds' => 300],
        '2026-07-09 12:00:00',
        $now
    );

    expect($next)->toBe('2026-07-09 12:15:00');
});

it('rejects unsafe cron intervals', function (): void {
    expect(fn() => cron_schedule::normalize(['type' => 'interval', 'seconds' => 5]))
        ->toThrow(InvalidArgumentException::class);
});
