<?php

namespace App\Tests\Unit\Service\Cron\Job;

use App\Service\Cron\Job\DolibarrSync;
use App\Service\Cron\UI\CronUIInterface;
use App\Service\Dolibarr\DolibarrSyncService;
use App\Service\Rest\Operation\UpdateOperation;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class DolibarrSyncTest extends TestCase
{
    private CronUIInterface|MockObject $ui;
    private MockObject|LoggerInterface $logger;
    private DolibarrSyncService|MockObject $dolibarrSyncService;

    public function setUp(): void {
        $this->ui = $this->getMockBuilder(CronUIInterface::class)->disableOriginalConstructor()->getMock();
        $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
        $this->dolibarrSyncService = $this->getMockBuilder(DolibarrSyncService::class)->disableOriginalConstructor()->getMock();
    }

    private function getMockFor(string $method) {
        $dolibarrSync = $this->getMockBuilder(DolibarrSync::class)
            ->setConstructorArgs([$this->dolibarrSyncService])
            ->setMethodsExcept([$method, 'setUI', 'setLoggerInterface'])
            ->getMock();
        $dolibarrSync->setUI($this->ui);
        $dolibarrSync->setLoggerInterface($this->logger);

        return $dolibarrSync;
    }

    public function testPreview(): void
    {
        $dolibarrSync = $this->getMockFor('preview');

        $operation1 = $this->getMockBuilder(UpdateOperation::class)->disableOriginalConstructor()->getMock();
        $operation1->method('getLabel')->willReturn('Op 1');
        $operation1->method('getChangeLog')->willReturn(['foo', 'bar']);

        $operation2 = $this->getMockBuilder(UpdateOperation::class)->disableOriginalConstructor()->getMock();
        $operation2->method('getLabel')->willReturn('Op 2');
        $operation2->method('getChangeLog')->willReturn([]);

        $operation3 = $this->getMockBuilder(UpdateOperation::class)->disableOriginalConstructor()->getMock();
        $operation3->method('getLabel')->willReturn('Op 3');
        $operation3->method('getChangeLog')->willReturn(['hello', 'world']);

        $this->dolibarrSyncService
            ->expects(self::once())
            ->method('scan')
            ->willReturn([1 => $operation1, 2 => $operation2, 3 => $operation3]);

        $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
            ["1. Op 1"],
            ["   foo"],
            ["   bar"],
            ["2. Op 2"],
            ["3. Op 3"],
            ["   hello"],
            ["   world"],
        );

        $dolibarrSync->preview();
    }

    public function testExecute(): void {
        $dolibarrSync = $this->getMockFor('execute');

        $operation1 = $this->getMockBuilder(UpdateOperation::class)->disableOriginalConstructor()->getMock();
        $operation1->method('getStatus')->willReturn(UpdateOperation::STATUS_DONE);

        $operation2 = $this->getMockBuilder(UpdateOperation::class)->disableOriginalConstructor()->getMock();
        $operation2->method('getStatus')->willReturn(UpdateOperation::STATUS_DONE);

        $operation3 = $this->getMockBuilder(UpdateOperation::class)->disableOriginalConstructor()->getMock();
        $operation3->method('getStatus')->willReturn(UpdateOperation::STATUS_ERROR);

        $operations = [1 => $operation1, 2 => $operation2, 3 => $operation3];

        $this->dolibarrSyncService
            ->expects(self::once())
            ->method('scan')
            ->willReturn($operations);

        $this->dolibarrSyncService
            ->expects(self::once())
            ->method('execute')
            ->with($operations, self::isInstanceOf(\Closure::class))
            ->willReturn($operations);

        $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
            ["Executing..."],
            ["2 operations successfully executed"],
            ["1 errors"],
        );

        $dolibarrSync->execute();
    }
}
