<?php

namespace MintyPHP\Tests\Service\Scheduler;

use MintyPHP\Repository\Scheduler\ScheduledJobRepository;
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepository;
use MintyPHP\Service\Scheduler\ScheduleCalculator;
use MintyPHP\Service\Scheduler\ScheduledJobRegistry;
use MintyPHP\Service\Scheduler\ScheduledJobService;
use PHPUnit\Framework\TestCase;

class ScheduledJobServiceTest extends TestCase
{
    public function testEnsureSystemJobsCreatesModuleJobDefinition(): void
    {
        $jobRepository = $this->createMock(ScheduledJobRepository::class);
        $runRepository = $this->createMock(ScheduledJobRunRepository::class);
        $registry = $this->createMock(ScheduledJobRegistry::class);
        $calculator = new ScheduleCalculator();

        $registry->expects($this->once())->method('definitions')->willReturn([
            'addressbook.sync' => [
                'label' => 'Addressbook Sync',
                'description' => 'Syncs address book data',
                'default_enabled' => 1,
                'default_timezone' => 'UTC',
                'default_schedule_type' => 'daily',
                'default_schedule_interval' => 1,
                'default_schedule_time' => '03:00',
                'default_schedule_weekdays_csv' => null,
                'default_catchup_once' => 1,
                'allowed_schedule_types' => ['hourly', 'daily', 'weekly'],
            ],
        ]);
        $jobRepository->expects($this->once())
            ->method('findByKey')
            ->with('addressbook.sync')
            ->willReturn(null);
        $jobRepository->expects($this->once())
            ->method('create')
            ->with($this->callback(static function (array $job): bool {
                return ($job['job_key'] ?? null) === 'addressbook.sync'
                    && ($job['label'] ?? null) === 'Addressbook Sync'
                    && ($job['timezone'] ?? null) === 'UTC'
                    && ($job['schedule_type'] ?? null) === 'daily';
            }))
            ->willReturn(99);
        $jobRepository->expects($this->never())->method('updateJobMeta');

        $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator);
        $service->ensureSystemJobs();
    }

    public function testEnsureSystemJobsUpdatesExistingModuleJobMetadata(): void
    {
        $jobRepository = $this->createMock(ScheduledJobRepository::class);
        $runRepository = $this->createMock(ScheduledJobRunRepository::class);
        $registry = $this->createMock(ScheduledJobRegistry::class);
        $calculator = new ScheduleCalculator();

        $registry->expects($this->once())->method('definitions')->willReturn([
            'addressbook.sync' => [
                'label' => 'Addressbook Sync',
                'description' => 'Syncs address book data',
                'default_enabled' => 1,
                'default_timezone' => 'UTC',
                'default_schedule_type' => 'daily',
                'default_schedule_interval' => 1,
                'default_schedule_time' => '03:00',
                'default_schedule_weekdays_csv' => null,
                'default_catchup_once' => 1,
                'allowed_schedule_types' => ['hourly', 'daily', 'weekly'],
            ],
        ]);
        $jobRepository->expects($this->once())
            ->method('findByKey')
            ->with('addressbook.sync')
            ->willReturn([
                'id' => 12,
                'job_key' => 'addressbook.sync',
                'label' => 'Old Label',
                'description' => 'Old description',
                'enabled' => 1,
                'timezone' => 'UTC',
                'schedule_type' => 'daily',
                'schedule_interval' => 1,
                'schedule_time' => '03:00',
                'schedule_weekdays_csv' => null,
                'catchup_once' => 1,
                'next_run_at' => '2026-01-01 03:00:00',
            ]);
        $jobRepository->expects($this->once())
            ->method('updateJobMeta')
            ->with(12, $this->callback(static function (array $job): bool {
                return ($job['label'] ?? null) === 'Addressbook Sync'
                    && ($job['description'] ?? null) === 'Syncs address book data';
            }))
            ->willReturn(true);
        $jobRepository->expects($this->never())->method('create');

        $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator);
        $service->ensureSystemJobs();
    }

    public function testEnsureSystemJobsDisablesOrphanedJobs(): void
    {
        $jobRepository = $this->createMock(ScheduledJobRepository::class);
        $runRepository = $this->createMock(ScheduledJobRunRepository::class);
        $registry = $this->createMock(ScheduledJobRegistry::class);
        $calculator = new ScheduleCalculator();

        $registry->expects($this->once())->method('definitions')->willReturn([
            'system_audit_purge' => [
                'label' => 'System audit log cleanup',
                'description' => 'Purges system audit log entries',
                'default_enabled' => 1,
                'default_timezone' => 'UTC',
                'default_schedule_type' => 'daily',
                'default_schedule_interval' => 1,
                'default_schedule_time' => '03:00',
                'default_schedule_weekdays_csv' => null,
                'default_catchup_once' => 1,
                'allowed_schedule_types' => ['daily', 'weekly'],
            ],
        ]);
        $jobRepository->method('findByKey')->willReturn(null);
        $jobRepository->method('create')->willReturn(1);

        $jobRepository->expects($this->once())
            ->method('deleteOrphanedJobs')
            ->with(['system_audit_purge'])
            ->willReturn(1);

        $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator);
        $service->ensureSystemJobs();
    }

    public function testUpdateFromAdminReturnsValidationErrorForWeeklyWithoutWeekdays(): void
    {
        $jobRepository = $this->createMock(ScheduledJobRepository::class);
        $runRepository = $this->createMock(ScheduledJobRunRepository::class);
        $registry = $this->createMock(ScheduledJobRegistry::class);
        $calculator = new ScheduleCalculator();

        $registry->expects($this->once())->method('definitions')->willReturn([]);
        $jobRepository->expects($this->once())->method('find')->with(10)->willReturn([
            'id' => 10,
            'job_key' => 'user_lifecycle_run',
            'label' => 'User lifecycle run',
            'description' => '',
            'enabled' => 1,
            'timezone' => 'UTC',
            'schedule_type' => 'daily',
            'schedule_interval' => 1,
            'schedule_time' => '02:15',
            'schedule_weekdays_csv' => null,
            'catchup_once' => 1,
        ]);
        $registry->expects($this->once())->method('get')->with('user_lifecycle_run')->willReturn([
            'label' => 'User lifecycle run',
            'description' => '',
            'allowed_schedule_types' => ['hourly', 'daily', 'weekly'],
        ]);
        $jobRepository->expects($this->never())->method('updateJobMeta');

        $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator);
        $result = $service->updateFromAdmin(10, [
            'enabled' => '1',
            'timezone' => 'UTC',
            'schedule_type' => 'weekly',
            'schedule_interval' => '1',
            'schedule_time' => '03:00',
        ]);

        $this->assertFalse($result['ok']);
        $this->assertNotEmpty($result['errors']);
    }

    public function testUpdateFromAdminPersistsNormalizedValuesAndReturnsFreshJob(): void
    {
        $jobRepository = $this->createMock(ScheduledJobRepository::class);
        $runRepository = $this->createMock(ScheduledJobRunRepository::class);
        $registry = $this->createMock(ScheduledJobRegistry::class);
        $calculator = new ScheduleCalculator();

        $registry->expects($this->exactly(2))->method('definitions')->willReturn([]);
        $jobRepository->expects($this->exactly(2))->method('find')->with(11)->willReturnOnConsecutiveCalls(
            [
                'id' => 11,
                'job_key' => 'user_lifecycle_run',
                'label' => 'User lifecycle run',
                'description' => 'Runs lifecycle',
                'enabled' => 1,
                'timezone' => 'UTC',
                'schedule_type' => 'daily',
                'schedule_interval' => 1,
                'schedule_time' => '02:15',
                'schedule_weekdays_csv' => null,
                'catchup_once' => 1,
            ],
            [
                'id' => 11,
                'job_key' => 'user_lifecycle_run',
                'label' => 'User lifecycle run',
                'description' => 'Runs lifecycle',
                'enabled' => 0,
                'timezone' => 'UTC',
                'schedule_type' => 'daily',
                'schedule_interval' => 1,
                'schedule_time' => '04:00',
                'schedule_weekdays_csv' => null,
                'catchup_once' => 0,
                'next_run_at' => null,
            ]
        );
        $registry->expects($this->once())->method('get')->with('user_lifecycle_run')->willReturn([
            'label' => 'User lifecycle run',
            'description' => 'Runs lifecycle',
            'allowed_schedule_types' => ['hourly', 'daily', 'weekly'],
        ]);
        $capturedUpdate = null;
        $jobRepository->expects($this->once())
            ->method('updateJobMeta')
            ->with(11, $this->anything())
            ->willReturnCallback(function (int $id, array $data) use (&$capturedUpdate): bool {
                $capturedUpdate = $data;
                return true;
            });

        $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator);
        $result = $service->updateFromAdmin(11, [
            'timezone' => 'UTC',
            'schedule_type' => 'daily',
            'schedule_interval' => '1',
            'schedule_time' => '04:00',
        ]);

        $this->assertTrue($result['ok']);
        $this->assertSame(11, (int) (($result['job'] ?? [])['id'] ?? 0));
        $this->assertSame(0, (int) (($result['job'] ?? [])['enabled'] ?? 1));
        $this->assertIsArray($capturedUpdate);
        $this->assertSame(0, (int) ($capturedUpdate['enabled'] ?? -1));
        $this->assertSame('UTC', (string) ($capturedUpdate['timezone'] ?? ''));
        $this->assertSame('daily', (string) ($capturedUpdate['schedule_type'] ?? ''));
        $this->assertSame(1, (int) ($capturedUpdate['schedule_interval'] ?? -1));
        $this->assertSame('04:00', (string) ($capturedUpdate['schedule_time'] ?? ''));
        $this->assertSame(0, (int) ($capturedUpdate['catchup_once'] ?? -1));
        $this->assertNull($capturedUpdate['next_run_at'] ?? null);
    }
}
