<?php

/**
 * 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.
 */

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\CalendarBundle\Tests\Form;

use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarRange;
use Chill\CalendarBundle\Form\CalendarType;
use Chill\CalendarBundle\Form\DataTransformer\IdToCalendarRangeDataTransformer;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\PrivateCommentDataMapper;
use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToUsersDataTransformer;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PrivateCommentType;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\DataTransformer\PersonsToIdDataTransformer;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Form\DataTransformer\ThirdPartiesToIdDataTransformer;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Doctrine\Common\Collections\Collection;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;

/**
 * @internal
 *
 * @coversNothing
 */
final class CalendarTypeTest extends TypeTestCase
{
    use ProphecyTrait;

    private IdToCalendarRangeDataTransformer $calendarRangeDataTransformer;

    private IdToLocationDataTransformer $idToLocationDataTransformer;

    private IdToUserDataTransformer $idToUserDataTransformer;

    private IdToUsersDataTransformer $idToUsersDataTransformer;

    private ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer;

    private PersonsToIdDataTransformer $personsToIdDataTransformer;

    private TokenStorageInterface $tokenStorage;

    protected function setUp(): void
    {
        $this->personsToIdDataTransformer = $this->buildMultiToIdDataTransformer(PersonsToIdDataTransformer::class, Person::class);
        $this->idToUserDataTransformer = $this->buildSingleToIdDataTransformer(IdToUserDataTransformer::class, User::class);
        $this->idToUsersDataTransformer = $this->buildMultiToIdDataTransformer(IdToUsersDataTransformer::class, User::class);
        $this->idToLocationDataTransformer = $this->buildSingleToIdDataTransformer(IdToLocationDataTransformer::class, Location::class);
        $this->partiesToIdDataTransformer = $this->buildMultiToIdDataTransformerThirdParties();
        $this->calendarRangeDataTransformer = $this->buildSingleToIdDataTransformer(IdToCalendarRangeDataTransformer::class, CalendarRange::class);
        $tokenStorage = $this->prophesize(TokenStorageInterface::class);
        $token = $this->prophesize(TokenInterface::class);
        $token->getUser()->willReturn(new User());
        $tokenStorage->getToken()->willReturn($token->reveal());
        $this->tokenStorage = $tokenStorage->reveal();

        parent::setUp();
    }

    public function testSubmitValidData()
    {
        $formData = [
            'mainUser' => '1',
            'users' => '2,3',
            'professionnals' => '646',
            'startDate' => '2022-05-05T14:00:00+0200',
            'endDate' => '2022-05-05T14:30:00+0200',
            'persons' => '7',
            'calendarRange' => '8',
            'location' => '9',
            'sendSMS' => '1',
        ];

        $calendar = new Calendar();
        $calendar->setMainUser(new class () extends User {
            public function getId(): ?int
            {
                return 1;
            }
        });

        $form = $this->factory->create(CalendarType::class, $calendar);

        $form->submit($formData);

        $this->assertTrue($form->isSynchronized());
        $this->assertEquals(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:00:00'), $calendar->getStartDate());
        $this->assertEquals(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:30:00'), $calendar->getEndDate());
        $this->assertEquals(7, $calendar->getPersons()->first()->getId());
        $this->assertEquals(8, $calendar->getCalendarRange()->getId());
        $this->assertEquals(9, $calendar->getLocation()->getId());
        $this->assertEquals(true, $calendar->getSendSMS());
        $this->assertContains(2, $calendar->getUsers()->map(static fn (User $u) => $u->getId()));
        $this->assertContains(3, $calendar->getUsers()->map(static fn (User $u) => $u->getId()));
    }

    protected function getExtensions()
    {
        $calendarType = new CalendarType(
            $this->personsToIdDataTransformer,
            $this->idToUserDataTransformer,
            $this->idToUsersDataTransformer,
            $this->idToLocationDataTransformer,
            $this->partiesToIdDataTransformer,
            $this->calendarRangeDataTransformer
        );
        $commentType = new CommentType($this->tokenStorage);

        $user = $this->prophesize(User::class);
        $user->getId()->willReturn(1);
        $security = $this->prophesize(Security::class);
        $security->getUser()->willReturn($user->reveal());
        $privateCommentDataMapper = new PrivateCommentDataMapper($security->reveal());
        $privateCommentType = new PrivateCommentType($privateCommentDataMapper);

        return array_merge(
            parent::getExtensions(),
            [new PreloadedExtension([$calendarType, $commentType, $privateCommentType], [])]
        );
    }

    private function buildMultiToIdDataTransformerThirdParties(): ThirdPartiesToIdDataTransformer
    {
        $repository = $this->prophesize(ThirdPartyRepository::class);
        $repository->findOneBy(Argument::type('array'))->willReturn(new ThirdParty());

        return new ThirdPartiesToIdDataTransformer($repository->reveal());
    }

    private function buildMultiToIdDataTransformer(
        string $classTransformer,
        string $objClass,
    ) {
        $transformer = $this->prophesize($classTransformer);
        $transformer->transform(Argument::type('array'))
            ->will(static fn ($args) => implode(
                ',',
                array_map(static fn ($p) => $p->getId(), $args[0])
            ));
        $transformer->transform(Argument::exact(null))
            ->willReturn([]);
        $transformer->transform(Argument::type(Collection::class))
            ->will(static fn ($args) => implode(
                ',',
                array_map(static fn ($p) => $p->getId(), $args[0]->toArray())
            ));
        $transformer->reverseTransform(Argument::type('string'))
            ->will(static function ($args) use ($objClass) {
                if (null === $args[0]) {
                    return [];
                }

                return array_map(
                    static function ($id) use ($objClass) {
                        $obj = new $objClass();
                        $reflectionProperty = new \ReflectionProperty($objClass, 'id');
                        $reflectionProperty->setAccessible(true);
                        $reflectionProperty->setValue($obj, (int) $id);

                        return $obj;
                    },
                    explode(',', (string) $args[0])
                );
            });

        return $transformer->reveal();
    }

    private function buildSingleToIdDataTransformer(
        string $classTransformer,
        string $class,
    ) {
        $transformer = $this->prophesize($classTransformer);
        $transformer->transform(Argument::type('object'))
            ->will(static fn ($args) => (string) $args[0]->getId());
        $transformer->transform(Argument::exact(null))
            ->willReturn('');
        $transformer->reverseTransform(Argument::type('string'))
            ->will(static function ($args) use ($class) {
                if (null === $args[0]) {
                    return null;
                }
                $obj = new $class();
                $reflectionProperty = new \ReflectionProperty($class, 'id');
                $reflectionProperty->setAccessible(true);
                $reflectionProperty->setValue($obj, (int) $args[0]);

                return $obj;
            });

        return $transformer->reveal();
    }
}
