<?php

namespace MintyPHP\Tests\Service\Scheduler;

use MintyPHP\Repository\Scheduler\ScheduledJobRepository;
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepository;
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepository;
use MintyPHP\Repository\Support\DatabaseSessionRepository;
use MintyPHP\Service\Audit\AuditRecorderInterface;
use MintyPHP\Service\Scheduler\ScheduleCalculator;
use MintyPHP\Service\Scheduler\ScheduledJobRegistry;
use MintyPHP\Service\Scheduler\ScheduledJobService;
use MintyPHP\Service\Scheduler\SchedulerRunService;
use PHPUnit\Framework\TestCase;

class SchedulerRunServiceTest extends TestCase
{
    public function testRunJobNowRejectsInvalidRequest(): void
    {
        $scheduledJobService = $this->createMock(ScheduledJobService::class);
        $scheduledJobService->expects($this->once())->method('ensureSystemJobs');
        $systemAuditService = $this->createMock(AuditRecorderInterface::class);
        $systemAuditService->expects($this->once())
            ->method('record')
            ->with(
                'scheduler.run',
                'failed',
                $this->callback(static function (array $context): bool {
                    $metadata = is_array($context['metadata'] ?? null) ? $context['metadata'] : [];
                    return ($metadata['trigger_type'] ?? '') === 'manual'
                        && ($metadata['run_status'] ?? '') === 'failed';
                })
            );

        $service = new SchedulerRunService(
            $scheduledJobService,
            $this->createMock(ScheduledJobRepository::class),
            $this->createMock(ScheduledJobRunRepository::class),
            $this->createMock(SchedulerRuntimeRepository::class),
            $this->createMock(ScheduledJobRegistry::class),
            new ScheduleCalculator(),
            $this->createMock(DatabaseSessionRepository::class),
            $systemAuditService
        );

        $result = $service->runJobNow(0, 0);

        $this->assertFalse($result['ok']);
        $this->assertSame('invalid_request', $result['error']);
    }

    public function testRunJobNowSkippedRunIsAuditedAsSuccessOutcome(): void
    {
        $scheduledJobService = $this->createMock(ScheduledJobService::class);
        $scheduledJobService->expects($this->once())->method('ensureSystemJobs');

        $scheduledJobRepository = $this->createMock(ScheduledJobRepository::class);
        $scheduledJobRepository->expects($this->once())
            ->method('find')
            ->with(7)
            ->willReturn([
                'id' => 7,
                'job_key' => 'user_lifecycle_run',
                'enabled' => 1,
                'next_run_at' => gmdate('Y-m-d H:i:s'),
                'catchup_once' => 1,
            ]);
        $scheduledJobRepository->expects($this->once())
            ->method('markRunning')
            ->with(7, $this->isString())
            ->willReturn(false);

        $scheduledJobRunRepository = $this->createMock(ScheduledJobRunRepository::class);
        $scheduledJobRunRepository->expects($this->once())
            ->method('create')
            ->with($this->isArray())
            ->willReturn(1);

        $databaseSessionRepository = $this->createMock(DatabaseSessionRepository::class);
        $databaseSessionRepository->expects($this->once())
            ->method('acquireAdvisoryLock')
            ->with('scheduled_jobs_runner', 0)
            ->willReturn(true);
        $databaseSessionRepository->expects($this->once())
            ->method('releaseAdvisoryLock')
            ->with('scheduled_jobs_runner');

        $records = [];
        $systemAuditService = $this->createMock(AuditRecorderInterface::class);
        $systemAuditService->expects($this->exactly(2))
            ->method('record')
            ->willReturnCallback(static function (string $eventType, string $outcome, array $context) use (&$records): int {
                $records[] = [
                    'event_type' => $eventType,
                    'outcome' => $outcome,
                    'context' => $context,
                ];
                return 1;
            });

        $service = new SchedulerRunService(
            $scheduledJobService,
            $scheduledJobRepository,
            $scheduledJobRunRepository,
            $this->createMock(SchedulerRuntimeRepository::class),
            $this->createMock(ScheduledJobRegistry::class),
            new ScheduleCalculator(),
            $databaseSessionRepository,
            $systemAuditService
        );

        $result = $service->runJobNow(7, 42);

        $this->assertTrue($result['ok']);
        $this->assertSame('skipped', $result['status']);
        $this->assertSame('job_already_running', $result['error']);
        $this->assertCount(2, $records);

        $jobRecord = null;
        $runRecord = null;
        foreach ($records as $record) {
            if ($record['event_type'] === 'scheduler.job.run') {
                $jobRecord = $record;
            }
            if ($record['event_type'] === 'scheduler.run') {
                $runRecord = $record;
            }
        }

        $this->assertNotNull($jobRecord);
        $this->assertNotNull($runRecord);
        $this->assertSame('success', $jobRecord['outcome']);
        $this->assertSame('success', $runRecord['outcome']);

        $jobMetadata = is_array($jobRecord['context']['metadata'] ?? null) ? $jobRecord['context']['metadata'] : [];
        $runMetadata = is_array($runRecord['context']['metadata'] ?? null) ? $runRecord['context']['metadata'] : [];
        $this->assertSame('manual', $jobMetadata['trigger_type'] ?? null);
        $this->assertSame('skipped', $jobMetadata['run_status'] ?? null);
        $this->assertSame('manual', $runMetadata['trigger_type'] ?? null);
        $this->assertSame('skipped', $runMetadata['run_status'] ?? null);
    }
}
