<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Tests\Workflow;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Workflow\DefinitionBuilder;
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class SignatureStepStateChangerTest extends TestCase
{
    public function testMarkSignatureAsSignedScenarioWhichExpectsTransitionSignatureWithPerson()
    {
        $entityWorkflow = new EntityWorkflow();
        $entityWorkflow->setWorkflowName('dummy');
        $registry = $this->buildRegistry();
        $workflow = $registry->get($entityWorkflow, 'dummy');
        $clock = new MockClock();
        $user = new User();

        $messengerBus = new MessageBus([]);
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $entityManager->expects($this->exactly(4))->method('refresh')->with(
            $this->isInstanceOf(EntityWorkflowStepSignature::class),
            $this->logicalOr(LockMode::PESSIMISTIC_WRITE, LockMode::PESSIMISTIC_READ)
        );
        $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger(), $messengerBus, $entityManager);

        // move it to signature
        $dto = new WorkflowTransitionContextDTO($entityWorkflow);
        $dto->futurePersonSignatures = [new Person(), new Person()];
        $workflow->apply($entityWorkflow, 'to_signature', ['context' => $dto, 'transitionAt' => $clock->now(),
            'byUser' => $user, 'transition' => 'to_signature']);

        // get the signature created
        $signatures = $entityWorkflow->getCurrentStep()->getSignatures();

        if (2 !== count($signatures)) {
            throw new \LogicException('there should have 2 signatures at this step');
        }

        // we mark the first signature as signed
        $changer->markSignatureAsSigned($signatures[0], 1);
        // the next step should be done by handling an async message
        $changer->onPostMark($signatures[0]);

        self::assertEquals('signature', $entityWorkflow->getStep(), 'there should have any change in the entity workflow step');
        self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState());
        self::assertEquals(1, $signatures[0]->getZoneSignatureIndex());
        self::assertNotNull($signatures[0]->getStateDate());


        // we mark the second signature as signed
        $changer->markSignatureAsSigned($signatures[1], 2);
        // the next step should be done by handling an async message
        $changer->onPostMark($signatures[1]);
        self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[1]->getState());
        self::assertEquals('post-signature', $entityWorkflow->getStep(), 'the entity workflow step should be post-signature');
        self::assertContains($user, $entityWorkflow->getCurrentStep()->getAllDestUser());
        self::assertEquals(2, $signatures[1]->getZoneSignatureIndex());
        self::assertNotNull($signatures[1]->getStateDate());
    }

    public function testMarkSignatureAsSignedScenarioWhichExpectsTransitionSignatureWithUser()
    {
        $entityWorkflow = new EntityWorkflow();
        $entityWorkflow->setWorkflowName('dummy');
        $registry = $this->buildRegistry();
        $workflow = $registry->get($entityWorkflow, 'dummy');
        $clock = new MockClock();
        $user = new User();

        $messengerBus = new MessageBus([]);
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $entityManager->expects($this->exactly(2))->method('refresh')->with(
            $this->isInstanceOf(EntityWorkflowStepSignature::class),
            $this->logicalOr(LockMode::PESSIMISTIC_WRITE, LockMode::PESSIMISTIC_READ)
        );
        $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger(), $messengerBus, $entityManager);

        // move it to signature
        $dto = new WorkflowTransitionContextDTO($entityWorkflow);
        $dto->futureUserSignature = $signer = new User();
        $workflow->apply($entityWorkflow, 'to_signature', ['context' => $dto, 'transitionAt' => $clock->now(),
            'byUser' => $user, 'transition' => 'to_signature']);

        // get the signature created
        $signatures = $entityWorkflow->getCurrentStep()->getSignatures();

        // we mark the first signature as signed
        $changer->markSignatureAsSigned($signatures[0], 1);
        // the next step should be done by handling an async message
        $changer->onPostMark($signatures[0]);

        self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState());
        self::assertEquals('post-signature', $entityWorkflow->getStep(), 'the entity workflow step should be post-signature');
        self::assertContains($signer, $entityWorkflow->getCurrentStep()->getAllDestUser());
        self::assertCount(1, $entityWorkflow->getCurrentStep()->getAllDestUser());
        self::assertEquals(1, $signatures[0]->getZoneSignatureIndex());
        self::assertNotNull($signatures[0]->getStateDate());
    }

    public function testMarkSignatureAsSignedScenarioWithoutRequiredMetadata()
    {
        $entityWorkflow = new EntityWorkflow();
        $entityWorkflow->setWorkflowName('dummy');
        $registry = $this->buildRegistry();
        $workflow = $registry->get($entityWorkflow, 'dummy');
        $clock = new MockClock();
        $user = new User();
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $entityManager->expects($this->exactly(2))->method('refresh')->with(
            $this->isInstanceOf(EntityWorkflowStepSignature::class),
            $this->logicalOr(LockMode::PESSIMISTIC_WRITE, LockMode::PESSIMISTIC_READ)
        );
        $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger(), new MessageBus([]), $entityManager);

        // move it to signature
        $dto = new WorkflowTransitionContextDTO($entityWorkflow);
        $dto->futurePersonSignatures = [new Person()];
        $workflow->apply($entityWorkflow, 'to_signature-without-metadata', ['context' => $dto, 'transitionAt' => $clock->now(),
            'byUser' => $user, 'transition' => 'to_signature-without-metadata']);

        // get the signature created
        $signatures = $entityWorkflow->getCurrentStep()->getSignatures();

        if (1 !== count($signatures)) {
            throw new \LogicException('there should have 2 signatures at this step');
        }

        // we mark the first signature as signed
        $changer->markSignatureAsSigned($signatures[0], 1);
        // the next step should be done by handling an async message
        $changer->onPostMark($signatures[0]);

        self::assertEquals('signature-without-metadata', $entityWorkflow->getStep(), 'there should have any change in the entity workflow step');
        self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState());
        self::assertEquals(1, $signatures[0]->getZoneSignatureIndex());
        self::assertNotNull($signatures[0]->getStateDate());
    }

    private function buildRegistry(): Registry
    {
        $builder = new DefinitionBuilder();
        $builder
            ->setInitialPlaces('initial')
            ->addPlaces(['initial', 'signature', 'signature-without-metadata', 'post-signature'])
            ->addTransition(new Transition('to_signature', 'initial', 'signature'))
            ->addTransition(new Transition('to_signature-without-metadata', 'initial', 'signature-without-metadata'))
            ->addTransition(new Transition('to_post-signature', 'signature', 'post-signature'))
            ->addTransition(new Transition('to_post-signature_2', 'signature-without-metadata', 'post-signature'))
        ;

        $metadata = new InMemoryMetadataStore(
            [],
            [
                'signature' => ['onSignatureCompleted' => ['transitionName' => 'to_post-signature']],
            ]
        );
        $builder->setMetadataStore($metadata);

        $workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), name: 'dummy');
        $registry = new Registry();
        $registry->addWorkflow(
            $workflow,
            new class () implements WorkflowSupportStrategyInterface {
                public function supports(WorkflowInterface $workflow, object $subject): bool
                {
                    return true;
                }
            }
        );

        return $registry;
    }
}
