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

use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Repository\EventRepository;
use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use function count;

/**
 * Test the creation of participation controller.
 *
 * @internal
 *
 * @coversNothing
 */
final class ParticipationControllerTest extends WebTestCase
{
    use PersonRandomHelper;
    use PrepareClientTrait;

    private EntityManagerInterface $em;

    private EventRepository $eventRepository;

    /**
     * Keep a cache for each person id given by the function getRandomPerson.
     *
     * You may ask to ignore some people by adding their id to the array.
     *
     * This is reset by setUp().
     *
     * @var int[]
     */
    private array $personsIdsCache = [];

    protected function prepareDI(): void
    {
        $this->em = self::getContainer()->get(EntityManagerInterface::class);
        $this->eventRepository = self::getContainer()->get(EventRepository::class);

        $this->personsIdsCache = [];
    }

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

        self::ensureKernelShutdown();
    }

    /**
     * This method test participation creation with wrong parameters.
     *
     * Those request should fail before any processing.
     */
    public function testCreateActionWrongParameters()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();
        $event = $this->getRandomEvent();
        $person = $this->getRandomPerson($this->em);

        // missing person_id or persons_ids
        $client->request(
            'GET',
            '/fr/event/participation/create',
            [
                'event_id' => $event->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'Test that /fr/event/participation/create fail if '
                .'both person_id and persons_ids are missing'
        );

        // having both person_id and persons_ids
        $client->request(
            'GET',
            '/fr/event/participation/create',
            [
                'event_id' => $event->getId(),
                'persons_ids' => implode(',', [
                    $this->getRandomPerson($this->em)->getId(),
                    $this->getRandomPerson($this->em)->getId(),
                ]),
                'person_id' => $person->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'test that /fr/event/participation/create fail if both person_id and '
                .'persons_ids are set'
        );

        // missing event_id
        $client->request(
            'GET',
            '/fr/event/participation/create',
            [
                'person_id' => $person->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'Test that /fr/event/participation/create fails if event_id is missing'
        );

        // persons_ids with wrong content
        $client->request(
            'GET',
            '/fr/event/participation/create',
            [
                'persons_ids' => 'a,b,531',
                'event_id' => $event->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'Test that /fr/event/participation/create fails if persons_ids has wrong content'
        );
    }

    public function testEditMultipleAction()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();

        /** @var Event $event */
        $event = $this->getRandomEventWithMultipleParticipations();

        $crawler = $client->request('GET', '/fr/event/participation/'.$event->getId().
                '/edit_multiple');

        $this->assertEquals(200, $client->getResponse()->getStatusCode());

        $button = $crawler->selectButton('Mettre à jour');
        $this->assertEquals(1, $button->count(), "test the form with button 'mettre à jour' exists ");

        $client->submit($button->form(), [
            'form[participations][0][role]' => $event->getType()->getRoles()->first()->getId(),
            'form[participations][0][status]' => $event->getType()->getStatuses()->first()->getId(),
            'form[participations][1][role]' => $event->getType()->getRoles()->last()->getId(),
            'form[participations][1][status]' => $event->getType()->getStatuses()->last()->getId(),
        ]);

        $this->assertTrue($client->getResponse()
            ->isRedirect('/fr/event/event/'.$event->getId().'/show'));
    }

    public function testNewActionWrongParameters()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();
        $event = $this->getRandomEvent();
        $person = $this->getRandomPerson($this->em);

        // missing person_id or persons_ids
        $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'event_id' => $event->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'Test that /fr/event/participation/new fail if '
                .'both person_id and persons_ids are missing'
        );

        // having both person_id and persons_ids
        $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'event_id' => $event->getId(),
                'persons_ids' => implode(',', [
                    $this->getRandomPerson($this->em)->getId(),
                    $this->getRandomPerson($this->em)->getId(),
                ]),
                'person_id' => $person->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'test that /fr/event/participation/new fail if both person_id and '
                .'persons_ids are set'
        );

        // missing event_id
        $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'person_id' => $person->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'Test that /fr/event/participation/new fails if event_id is missing'
        );

        // persons_ids with wrong content
        $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'persons_ids' => 'a,b,531',
                'event_id' => $event->getId(),
            ]
        );
        $this->assertEquals(
            400,
            $client->getResponse()->getStatusCode(),
            'Test that /fr/event/participation/new fails if persons_ids has wrong content'
        );
    }

    public function testNewMultipleAction()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();
        $event = $this->getRandomEvent();
        // record the number of participation for the event (used later in this test)
        $nbParticipations = $event->getParticipations()->count();
        // make ignore the people already in the event from the function getRandomPerson
        $this->personsIdsCache = array_merge(
            $this->personsIdsCache,
            $event->getParticipations()->map(
                static fn ($p) => $p->getPerson()->getId()
            )
                ->toArray()
        );
        // get some random people
        $person1 = $this->getRandomPerson($this->em);
        $person2 = $this->getRandomPerson($this->em);

        $crawler = $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'persons_ids' => implode(',', [$person1->getId(), $person2->getId()]),
                'event_id' => $event->getId(),
            ]
        );

        $this->assertEquals(
            200,
            $client->getResponse()->getStatusCode(),
            'test that /fr/event/participation/new is successful'
        );

        $button = $crawler->selectButton('Créer');

        $this->assertNotNull($button, "test the form with button 'Créer' exists");

        $client->submit($button->form(), [
            'form' => [
                'participations' => [
                    0 => [
                        'role' => $event->getType()->getRoles()->first()->getId(),
                        'status' => $event->getType()->getStatuses()->first()->getId(),
                    ],
                    1 => [
                        'role' => $event->getType()->getRoles()->first()->getId(),
                        'status' => $event->getType()->getStatuses()->first()->getId(),
                    ],
                ],
            ],
        ]);

        $this->assertTrue($client->getResponse()->isRedirect());
        $crawler = $client->followRedirect();

        $span1 = $crawler->filter('table td span.entity-person a:contains("'
                .$person1->getFirstName().'"):contains("'.$person1->getLastname().'")');
        $this->assertGreaterThan(0, \count($span1));
        $span2 = $crawler->filter('table td span.entity-person a:contains("'
                .$person2->getFirstName().'"):contains("'.$person2->getLastname().'")');
        $this->assertGreaterThan(0, \count($span2));

        // as the container has reloaded, reload the event
        $event = $this->em->getRepository(Event::class)->find($event->getId());
        $this->em->refresh($event);

        $this->assertEquals($nbParticipations + 2, $event->getParticipations()->count());
    }

    public function testNewMultipleWithAllPeopleParticipating()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();
        $event = $this->getRandomEventWithMultipleParticipations();

        $persons_id = implode(',', $event->getParticipations()->map(
            static fn ($p) => $p->getPerson()->getId()
        )->toArray());

        $crawler = $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'persons_ids' => $persons_id,
                'event_id' => $event->getId(),
            ]
        );

        $this->assertEquals(
            302,
            $client->getResponse()->getStatusCode(),
            'test that /fr/event/participation/new is redirecting'
        );
    }

    public function testNewMultipleWithSomePeopleParticipating()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();
        $event = $this->getRandomEventWithMultipleParticipations();
        // record the number of participation for the event (used later in this test)
        $nbParticipations = $event->getParticipations()->count();
        // get the persons_id participating on this event
        $persons_id = $event->getParticipations()->map(
            static fn ($p) => $p->getPerson()->getId()
        )->toArray();
        // exclude the existing persons_ids from the new person
        $this->personsIdsCache = array_merge($this->personsIdsCache, $persons_id);

        // get a random person
        $newPerson = $this->getRandomPerson($this->em);

        // build the `persons_ids` parameter
        $persons_ids_string = implode(',', [...$persons_id, $newPerson->getId()]);

        $crawler = $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'persons_ids' => $persons_ids_string,
                'event_id' => $event->getId(),
            ]
        );

        $this->assertEquals(
            200,
            $client->getResponse()->getStatusCode(),
            'test that /fr/event/participation/new is successful'
        );

        // count that the one UL contains the new person string
        $firstPerson = $event->getParticipations()->first()->getPerson();
        $ul = $crawler->filter('ul:contains("'.$firstPerson->getLastName().'")'
                .':contains("'.$firstPerson->getFirstName().'")');

        $this->assertEquals(
            1,
            $ul->count(),
            'assert an ul containing the name of ignored people is present'
        );
        $this->assertEquals(
            $event->getParticipations()->count(),
            $ul->children()->count(),
            'assert the li listing ignored people has the correct number'
        );

        // test a form is present on the page
        $button = $crawler->selectButton('Créer');

        $this->assertNotNull($button, "test the form with button 'Créer' exists");

        // submit the form
        $client->submit($button->form(), [
            'participation[role]' => $event->getType()->getRoles()->first()->getId(),
            'participation[status]' => $event->getType()->getStatuses()->first()->getId(),
        ]);

        $this->assertTrue($client->getResponse()->isRedirect());

        // reload the event and test there is a new participation
        $event = $this->em->getRepository(Event::class)
            ->find($event->getId());
        $this->em->refresh($event);

        $this->assertEquals(
            $nbParticipations + 1,
            $event->getParticipations()->count(),
            'Test we have persisted a new participation associated to the test'
        );
    }

    public function testNewSingleAction()
    {
        $client = $this->getClientAuthenticated();
        $this->prepareDI();
        $event = $this->getRandomEvent();
        // record the number of participation for the event
        $nbParticipations = $event->getParticipations()->count();
        $person = $this->getRandomPerson($this->em);

        $crawler = $client->request(
            'GET',
            '/fr/event/participation/new',
            [
                'person_id' => $person->getId(),
                'event_id' => $event->getId(),
            ]
        );

        $this->assertEquals(
            200,
            $client->getResponse()->getStatusCode(),
            'test that /fr/event/participation/new is successful'
        );

        $button = $crawler->selectButton('Créer');

        $this->assertNotNull($button, "test the form with button 'Créer' exists");

        $client->submit($button->form(), [
            'participation[role]' => $event->getType()->getRoles()->first()->getId(),
            'participation[status]' => $event->getType()->getStatuses()->first()->getId(),
        ]);

        $this->assertTrue($client->getResponse()->isRedirect());
        $crawler = $client->followRedirect();

        $span = $crawler->filter('table td span.entity-person a:contains("'
                .$person->getFirstName().'"):contains("'.$person->getLastname().'")');

        $this->assertGreaterThan(0, \count($span));

        // as the container has reloaded, reload the event
        $event = $this->em->getRepository(Event::class)->find($event->getId());
        $this->em->refresh($event);

        $this->assertEquals($nbParticipations + 1, $event->getParticipations()->count());
    }

    private function getRandomEvent(string $centerName = 'Center A', string $circleName = 'social'): Event
    {
        $dql = 'FROM '.Event::class.' e JOIN e.center center JOIN e.circle scope WHERE center.name LIKE :cname AND JSON_EXTRACT(scope.name, \'fr\') LIKE :sname';

        $ids = $this->em->createQuery(
            'SELECT DISTINCT e.id '.$dql
        )
            ->setParameters(['cname' => $centerName, 'sname' => $circleName])
            ->getResult();

        return $this->eventRepository->find($ids[array_rand($ids)]['id']);
    }

    /**
     * Return a random event only if he has more than one participation.
     *
     * @param string $centerName
     * @param type   $circleName
     *
     * @return Event
     */
    protected function getRandomEventWithMultipleParticipations(
        $centerName = 'Center A',
        $circleName = 'social',
    ) {
        $event = $this->getRandomEvent($centerName, $circleName);

        return $event->getParticipations()->count() > 1 ?
                $event :
                $this->getRandomEventWithMultipleParticipations($centerName, $circleName);
    }
}
