<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Unit;

use OCA\Mitgliederverwaltung\BackgroundJob\SyncQueueJob;
use OCA\Mitgliederverwaltung\Service\CalendarSyncService;
use OCA\Mitgliederverwaltung\Service\ContactsSyncService;
use OCP\AppFramework\Utility\ITimeFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class SyncQueueJobTest extends TestCase {

    private CalendarSyncService&MockObject $calendarSyncService;
    private ContactsSyncService&MockObject $contactsSyncService;
    private LoggerInterface&MockObject $logger;
    private ITimeFactory&MockObject $timeFactory;
    private SyncQueueJob $job;

    protected function setUp(): void {
        parent::setUp();

        $this->calendarSyncService = $this->createMock(CalendarSyncService::class);
        $this->contactsSyncService = $this->createMock(ContactsSyncService::class);
        $this->logger = $this->createMock(LoggerInterface::class);
        $this->timeFactory = $this->createMock(ITimeFactory::class);

        $this->job = new SyncQueueJob(
            $this->timeFactory,
            $this->calendarSyncService,
            $this->contactsSyncService,
            $this->logger
        );
    }

    public function testRunProcessesQueue(): void {
        $this->calendarSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(3);

        $this->contactsSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(2);

        $this->logger->expects($this->once())
            ->method('info')
            ->with(
                'Sync queue processed',
                $this->callback(function (array $context) {
                    return $context['calendar'] === 3
                        && $context['contacts'] === 2
                        && $context['app'] === 'mitgliederverwaltung';
                })
            );

        // Use reflection to invoke the protected run method
        $reflection = new \ReflectionMethod($this->job, 'run');
        $reflection->setAccessible(true);
        $reflection->invoke($this->job, []);
    }

    public function testRunHandlesEmptyQueue(): void {
        $this->calendarSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(0);

        $this->contactsSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(0);

        // Should NOT log info when nothing was processed
        $this->logger->expects($this->never())->method('info');

        $reflection = new \ReflectionMethod($this->job, 'run');
        $reflection->setAccessible(true);
        $reflection->invoke($this->job, []);
    }

    public function testRunLogsOnCalendarOnlyProcessed(): void {
        $this->calendarSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(5);

        $this->contactsSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(0);

        $this->logger->expects($this->once())->method('info');

        $reflection = new \ReflectionMethod($this->job, 'run');
        $reflection->setAccessible(true);
        $reflection->invoke($this->job, []);
    }

    public function testRunLogsOnContactsOnlyProcessed(): void {
        $this->calendarSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(0);

        $this->contactsSyncService->expects($this->once())
            ->method('processQueue')
            ->willReturn(1);

        $this->logger->expects($this->once())->method('info');

        $reflection = new \ReflectionMethod($this->job, 'run');
        $reflection->setAccessible(true);
        $reflection->invoke($this->job, []);
    }

    public function testRunLogsErrorOnException(): void {
        $exception = new \RuntimeException('Queue failure');

        $this->calendarSyncService->expects($this->once())
            ->method('processQueue')
            ->willThrowException($exception);

        $this->logger->expects($this->once())
            ->method('error')
            ->with(
                'Sync queue processing failed',
                $this->callback(function (array $context) use ($exception) {
                    return $context['exception'] === $exception
                        && $context['app'] === 'mitgliederverwaltung';
                })
            );

        $reflection = new \ReflectionMethod($this->job, 'run');
        $reflection->setAccessible(true);
        $reflection->invoke($this->job, []);
    }
}
