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

use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Search\EventSearch;
use DateTime;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

/**
 * Test the EventSearch class.
 *
 * @internal
 *
 * @coversNothing
 */
final class EventSearchTest extends WebTestCase
{
    /**
     * The center A.
     *
     * @var \Chill\MainBundle\Entity\Center
     */
    protected $centerA;

    /**
     * @var \Symfony\Component\BrowserKit\AbstractBrowser
     */
    protected $client;

    /**
     * @var \Doctrine\ORM\EntityManagerInterface
     */
    protected $entityManager;

    /**
     * Events created during this test.
     *
     * @var Event[]
     */
    protected $events = [];

    /**
     * The eventSearch service, which is used to search events.
     *
     * @var EventSearch
     */
    protected $eventSearch;

    /**
     * a random event type.
     *
     * @var \Chill\EventBundle\Entity\EventType
     */
    protected $eventType;

    /**
     * @var \Prophecy\Prophet
     */
    protected $prophet;

    protected function setUp(): void
    {
        self::bootKernel();
        /** @var \Symfony\Component\HttpKernel\KernelInterface $kernel */
        $kernel = self::$kernel;

        $this->client = self::createClient([], [
            'PHP_AUTH_USER' => 'center a_social',
            'PHP_AUTH_PW' => 'password',
            'HTTP_ACCEPT_LANGUAGE' => 'fr_FR',
        ]);

        $this->prophet = new \Prophecy\Prophet();

        $this->entityManager = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager');

        $this->centerA = $this->entityManager
            ->getRepository(\Chill\MainBundle\Entity\Center::class)
            ->findOneBy(['name' => 'Center A']);

        $this->eventType = $this->entityManager
            ->getRepository(\Chill\EventBundle\Entity\EventType::class)
            ->findAll()[0];

        $this->createEvents();
    }

    protected function tearDown(): void
    {
        foreach ($this->events as $event) {
            $this->entityManager->createQuery('DELETE FROM ChillEventBundle:Event e WHERE e.id = :event_id')
                ->setParameter('event_id', $event->getId())
                ->execute();
        }

        $this->events = [];
    }

    public function testDisplayAll()
    {
        $crawler = $this->client->request('GET', '/fr/search', [
            'q' => '@events',
        ]);

        $this->assertGreaterThanOrEqual(
            2,
            $crawler->filter('table.events tr')->count(),
            'assert than more than 2 tr are present'
        );
    }

    /**
     * Test that a user connected with an user with the wrong center does not
     * see the events.
     */
    public function testDisplayAllWrongUser()
    {
        $client = self::createClient([], [
            'PHP_AUTH_USER' => 'center b_social',
            'PHP_AUTH_PW' => 'password',
            'HTTP_ACCEPT_LANGUAGE' => 'fr_FR',
        ]);

        $crawler = $client->request('GET', '/fr/search', [
            'q' => '@events printemps',
        ]);

        $this->assertEquals(
            0,
            $crawler->filter('tr:contains("Printemps")')->count(),
            'assert that the word "printemps" is present'
        );
    }

    public function testSearchByDateDateBetween()
    {
        // serach with date from **and** date-to
        $crawler = $this->client->request('GET', '/fr/search', [
            'q' => '@events date-from:2016-05-30 date-to:2016-06-20',
        ]);

        /** @var \DateTime $dateFrom the date from in DateTime */
        $dateFrom = \DateTime::createFromFormat('Y-m-d', '2016-05-30');
        $dateTo = \DateTime::createFromFormat('Y-m-d', '2016-06-20');

        $dates = $this->iterateOnRowsToFindDate($crawler->filter('tr'));

        foreach ($dates as $date) {
            $this->assertGreaterThanOrEqual($dateFrom, $date);
            $this->assertLessThanOrEqual($dateTo, $date);
        }

        // there should not have any other results, but if any other bundle
        // add some other event, we go on next pages

        if (0 === $crawler->selectLink('Voir tous les résultats')->count()) {
            return;
        }

        // click on link "Voir tous les résultats"
        $crawlerAllResults = $this->client->click($crawler
            ->selectLink('Voir tous les résultats')->link());
        $dates = $this->iterateOnRowsToFindDate($crawlerAllResults->filter('tr'));

        foreach ($dates as $date) {
            $this->assertGreaterThanOrEqual($dateFrom, $date);
            $this->assertLessThanOrEqual($dateTo, $date);
        }

        // iterate on pagination
        $crawlerAllResults->filter('.pagination a')->each(function ($a, $i) use ($dateFrom) {
            $page = $this->client->click($a->link());
            $dates = $this->iterateOnRowsToFindDate($page->filter('tr'));

            foreach ($dates as $date) {
                $this->assertGreaterThanOrEqual($dateFrom, $date);
                $this->assertLessThanOrEqual($dateTo, $date);
            }
        });
    }

    public function testSearchByDateDateFromOnly()
    {
        // search with date from
        $crawler = $this->client->request('GET', '/fr/search', [
            'q' => '@events date-from:2016-05-30',
        ]);
        /** @var \DateTime $dateFrom the date from in DateTime */
        $dateFrom = \DateTime::createFromFormat('Y-m-d', '2016-05-30');

        $dates = $this->iterateOnRowsToFindDate($crawler->filter('tr'));

        foreach ($dates as $date) {
            $this->assertGreaterThanOrEqual($dateFrom, $date);
        }

        // click on link "Voir tous les résultats"
        $crawlerAllResults = $this->client->click($crawler
            ->selectLink('Voir tous les résultats')->link());
        $dates = $this->iterateOnRowsToFindDate($crawlerAllResults->filter('tr'));

        foreach ($dates as $date) {
            $this->assertGreaterThanOrEqual($dateFrom, $date);
        }

        // iterate on pagination
        $crawlerAllResults->filter('.pagination a')->each(function ($a, $i) use ($dateFrom) {
            $page = $this->client->click($a->link());
            $dates = $this->iterateOnRowsToFindDate($page->filter('tr'));

            foreach ($dates as $date) {
                $this->assertGreaterThanOrEqual($dateFrom, $date);
            }
        });
    }

    public function testSearchByDateDateTo()
    {
        // serach with date from **and** date-to
        $crawler = $this->client->request('GET', '/fr/search', [
            'q' => '@events date:2016-05-30',
        ]);

        /** @var \DateTime $dateFrom the date from in DateTime */
        $dateTo = \DateTime::createFromFormat('Y-m-d', '2016-05-30');

        $dates = $this->iterateOnRowsToFindDate($crawler->filter('tr'));

        foreach ($dates as $date) {
            $this->assertLessThanOrEqual($dateTo, $date);
        }

        if (0 === $crawler->selectLink('Voir tous les résultats')->count()) {
            return;
        }

        // click on link "Voir tous les résultats"
        $crawlerAllResults = $this->client->click($crawler
            ->selectLink('Voir tous les résultats')->link());
        $dates = $this->iterateOnRowsToFindDate($crawlerAllResults->filter('tr'));

        foreach ($dates as $date) {
            $this->assertLessThanOrEqual($dateTo, $date);
        }

        // iterate on pagination
        $crawlerAllResults->filter('.pagination a')->each(function ($a, $i) {
            $page = $this->client->click($a->link());
            $dates = $this->iterateOnRowsToFindDate($page->filter('tr'));

            foreach ($dates as $date) {
                $this->assertLessThanOrEqual($dateTo, $date);
            }
        });
    }

    public function testSearchByDefault()
    {
        $crawler = $this->client->request('GET', '/fr/search', [
            'q' => '@events printemps',
        ]);

        $this->assertEquals(
            1,
            $crawler->filter('table.events tr')->count() - 1 /* as the header is a th */ ,
            'assert than more than 2 tr are present'
        );

        $this->assertEquals(
            1,
            $crawler->filter('tr:contains("Printemps")')->count(),
            'assert that the word "printemps" is present'
        );
    }

    public function testSearchByName()
    {
        $crawler = $this->client->request('GET', '/fr/search', [
            'q' => '@events name:printemps',
        ]);

        $this->assertEquals(
            1,
            $crawler->filter('table.events tr')->count() - 1 /* as the header is a th */ ,
            'assert than more than 2 tr are present'
        );

        $this->assertEquals(
            1,
            $crawler->filter('tr:contains("Printemps")')->count(),
            'assert that the word "printemps" is present'
        );
    }

    protected function createEvents()
    {
        $event1 = (new Event())
            ->setCenter($this->centerA)
            ->setDate(new \DateTime('2016-05-30'))
            ->setName('Printemps européen')
            ->setType($this->eventType)
            ->setCircle($this->getCircle());
        $this->entityManager->persist($event1);
        $this->events[] = $event1;

        $event2 = (new Event())
            ->setCenter($this->centerA)
            ->setDate(new \DateTime('2016-06-24'))
            ->setName('Hiver de la droite')
            ->setType($this->eventType)
            ->setCircle($this->getCircle());
        $this->entityManager->persist($event2);
        $this->events[] = $event2;

        $this->entityManager->flush();
    }

    /**
     * @param string $name the name of the circle
     *
     * @return \Chill\MainBundle\Entity\Scope
     */
    protected function getCircle($name = 'social')
    {
        $circles = $this->entityManager->getRepository(\Chill\MainBundle\Entity\Scope::class)
            ->findAll();

        /** @var \Chill\MainBundle\Entity\Scope $circle */
        foreach ($circles as $circle) {
            if (\in_array($name, $circle->getName(), true)) {
                return $circle;
            }
        }
    }

    /**
     * this function iterate on row from results of events and return the content
     * of the second column (which should contains the date) in DateTime objects.
     *
     * @return \DateTime[]
     */
    private function iterateOnRowsToFindDate(\Symfony\Component\DomCrawler\Crawler $trs)
    {
        $months = [
            'janvier' => 1,
            'février' => 2,
            'mars' => 3,
            'avril' => 4,
            'mai' => 5,
            'juin' => 6,
            'juillet' => 7,
            'août' => 8,
            'septembre' => 9,
            'octobre' => 10,
            'novembre' => 11,
            'décembre' => 12,
        ];

        $results = $trs->each(static function ($tr, $i) use ($months) {
            // we skip the first row
            if (0 < $i) {
                // get the second node, which should contains a date
                $tdDate = $tr->filter('td')->eq(1);
                // transform the date, which should be in french, into a DateTime object
                $parts = explode(' ', (string) $tdDate->text());

                return \DateTime::createFromFormat('Y-m-d', $parts[2].
                        '-'.$months[$parts[1]].'-'.$parts[0]);
            }
        });

        // remove the first row
        unset($results[0]);

        return $results;
    }
}
