<?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\Controller;

use Chill\MainBundle\Controller\WorkflowViewSendPublicController;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSendView;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
use Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage;
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Workflow\Registry;
use Twig\Environment;

/**
 * @internal
 *
 * @coversNothing
 */
class WorkflowViewSendPublicControllerTest extends TestCase
{
    use ProphecyTrait;

    public function testTooMuchTrials(): void
    {
        $environment = $this->prophesize(Environment::class);
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldNotBeCalled();
        $messageBus = $this->prophesize(MessageBusInterface::class);
        $messageBus->dispatch(Argument::type(PostPublicViewMessage::class))->shouldNotBeCalled();

        $controller = new WorkflowViewSendPublicController($entityManager->reveal(), new NullLogger(), new EntityWorkflowManager([], new Registry()), new MockClock(), $environment->reveal(), $messageBus->reveal());

        self::expectException(AccessDeniedHttpException::class);

        $send = $this->buildEntityWorkflowSend();

        for ($i = 0; $i < 51; ++$i) {
            $send->increaseErrorTrials();
        }

        $controller($send, $send->getPrivateToken(), new Request());
    }

    public function testInvalidVerificationKey(): void
    {
        $environment = $this->prophesize(Environment::class);
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldBeCalled();
        $messageBus = $this->prophesize(MessageBusInterface::class);
        $messageBus->dispatch(Argument::type(PostPublicViewMessage::class))->shouldNotBeCalled();

        $controller = new WorkflowViewSendPublicController($entityManager->reveal(), new NullLogger(), new EntityWorkflowManager([], new Registry()), new MockClock(), $environment->reveal(), $messageBus->reveal());

        self::expectException(AccessDeniedHttpException::class);

        $send = $this->buildEntityWorkflowSend();

        try {
            $controller($send, 'some-token', new Request());
        } catch (AccessDeniedHttpException $e) {
            self::assertEquals(1, $send->getNumberOfErrorTrials());

            throw $e;
        }
    }

    public function testExpiredLink(): void
    {
        $environment = $this->prophesize(Environment::class);
        $environment->render('@ChillMain/Workflow/workflow_view_send_public_expired.html.twig')->willReturn('test');

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldNotBeCalled();
        $messageBus = $this->prophesize(MessageBusInterface::class);
        $messageBus->dispatch(Argument::type(PostPublicViewMessage::class))->shouldNotBeCalled();

        $controller = new WorkflowViewSendPublicController($entityManager->reveal(), new NullLogger(), new EntityWorkflowManager([], new Registry()), new MockClock('next year'), $environment->reveal(), $messageBus->reveal());

        $send = $this->buildEntityWorkflowSend();

        $response = $controller($send, $send->getPrivateToken(), new Request());

        self::assertEquals('test', $response->getContent());
        self::assertEquals(409, $response->getStatusCode());
    }

    public function testNoHandlerFound(): void
    {
        $environment = $this->prophesize(Environment::class);
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldNotBeCalled();
        $messageBus = $this->prophesize(MessageBusInterface::class);
        $messageBus->dispatch(Argument::type(PostPublicViewMessage::class))->shouldNotBeCalled();

        $controller = new WorkflowViewSendPublicController(
            $entityManager->reveal(),
            new NullLogger(),
            new EntityWorkflowManager([], new Registry()),
            new MockClock(),
            $environment->reveal(),
            $messageBus->reveal(),
        );

        self::expectException(\RuntimeException::class);

        $send = $this->buildEntityWorkflowSend();
        $controller($send, $send->getPrivateToken(), new Request());
    }

    public function testHappyScenario(): void
    {
        $send = $this->buildEntityWorkflowSend();
        $environment = $this->prophesize(Environment::class);
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(function (EntityWorkflowSendView $view) use ($send) {
            $reflection = new \ReflectionClass($view);
            $idProperty = $reflection->getProperty('id');
            $idProperty->setAccessible(true);
            $idProperty->setValue($view, 5);

            return $send === $view->getSend();
        }))->shouldBeCalled();
        $entityManager->flush()->shouldBeCalled();
        $messageBus = $this->prophesize(MessageBusInterface::class);
        $messageBus->dispatch(Argument::type(PostPublicViewMessage::class))->shouldBeCalled()
            ->will(fn ($args) => new Envelope($args[0]));


        $controller = new WorkflowViewSendPublicController(
            $entityManager->reveal(),
            new NullLogger(),
            new EntityWorkflowManager([
                $this->buildFakeHandler(),
            ], new Registry()),
            new MockClock(),
            $environment->reveal(),
            $messageBus->reveal(),
        );

        $response = $controller($send, $send->getPrivateToken(), $this->buildRequest());

        self::assertEquals(200, $response->getStatusCode());
        self::assertEquals('content', $response->getContent());
    }

    private function buildFakeHandler(): EntityWorkflowHandlerInterface&EntityWorkflowWithPublicViewInterface
    {
        return new class () implements EntityWorkflowWithPublicViewInterface, EntityWorkflowHandlerInterface {
            public function getDeletionRoles(): array
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getRelatedObjects(object $object): array
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function isObjectSupported(object $object): bool
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
            {
                return true;
            }

            public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool
            {
                return false;
            }

            public function findByRelatedEntity(object $object): array
            {
                throw new \BadMethodCallException('not implemented');
            }

            public function renderPublicView(EntityWorkflowSend $entityWorkflowSend, EntityWorkflowViewMetadataDTO $metadata): string
            {
                return 'content';
            }

            public function getSuggestedPersons(EntityWorkflow $entityWorkflow): array
            {
                return [];
            }

            public function getSuggestedThirdParties(EntityWorkflow $entityWorkflow): array
            {
                return [];
            }
        };
    }

    private function buildRequest(): Request
    {
        return Request::create('/test', server: ['REMOTE_ADDR' => '10.0.0.10']);
    }

    private function buildEntityWorkflowSend(): EntityWorkflowSend
    {
        $entityWorkflow = new EntityWorkflow();

        $step = $entityWorkflow->getCurrentStep();

        return new EntityWorkflowSend($step, new ThirdParty(), new \DateTimeImmutable('next month'));
    }
}
