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

use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Common\Collections\Criteria;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

/**
 * Test the creation or deletion of accompanying periods.
 *
 * The person on which the test is done has a (current) period opened (and not
 * closed) starting the 2015-01-05.
 *
 * @internal
 *
 * @coversNothing
 */
final class AccompanyingPeriodControllerTest extends WebTestCase
{
    public const CLOSING_INPUT = 'chill_personbundle_accompanyingperiod[closingDate]';

    public const CLOSING_MOTIVE_INPUT = 'chill_personbundle_accompanyingperiod[closingMotive]';

    public const OPENING_INPUT = 'chill_personbundle_accompanyingperiod[openingDate]';

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

    /**
     * @var \Doctrine\ORM\EntityManagerInterface
     */
    protected static $em;

    /**
     * @var Person The person on which the form is applied
     */
    protected $person;

    /**
     * Setup before the first test of this class (see phpunit doc).
     */
    public static function setUpBeforeClass(): void
    {
        self::bootKernel();
        self::$em = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager');
    }

    /**
     * Setup before each test method (see phpunit doc).
     */
    protected function setUp(): void
    {
        $this->client = self::createClient([], [
            'PHP_AUTH_USER' => 'center a_social',
            'PHP_AUTH_PW' => 'password',
        ]);

        $center = self::$em->getRepository(\Chill\MainBundle\Entity\Center::class)
            ->findOneBy(['name' => 'Center A']);

        $this->person = (new Person(new \DateTime('2015-01-05')))
            ->setFirstName('Roland')
            ->setLastName('Gallorime')
            ->setCenter($center)
            ->setGender(Person::MALE_GENDER);

        self::$em->persist($this->person);
        self::$em->flush();
    }

    /**
     * TearDown after each test method (see phpunit doc).
     */
    protected function tearDown(): void
    {
        self::$em->refresh($this->person);
        self::$em->remove($this->person);

        self::$em->flush();
    }

    /**
     * Test the creation of a new period.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and we create a new period
     *      with : dateClosing: 2014-12-31
     *      with : dateOpening: 2014-01-01
     *      with : the last closing motive in list
     * Then the response should redirect to period view
     */
    public function testAddNewPeriodBeforeActual()
    {
        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton('Créer une période d\'accompagnement')->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('31-12-2014');
        $form->get(self::OPENING_INPUT)
            ->setValue('01-01-2014');

        $this->client->submit($form);

        $this->assertTrue(
            $this->client->getResponse()->isRedirect(
                '/fr/person/'.$this->person->getId().'/accompanying-period'
            ),
            'the server redirects to /accompanying-period page'
        );
        $this->assertGreaterThan(
            0,
            $this->client->followRedirect()
                ->filter('.alert-success')->count(),
            "a 'success' element is shown"
        );
    }

    /**
     * Test the closing of a periods.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and we fill the close form (at /fr/person/[id]/accompanying-period/close
     *      with : dateClosing: 2015-02-01
     *      with : the last closing motive in list
     * Then the response should redirect to period view
     * And the next page should have a `.alert-danger` element present in page
     *
     * @todo
     */
    public function testClosingCurrentPeriod()
    {
        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/close');

        $form = $crawler->selectButton('Clôre la période')->form();

        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue((new \DateTime('2015-02-01'))->format('d-m-Y'));

        $cr = $this->client->submit($form);

        $this->assertTrue(
            $this->client->getResponse()->isRedirect(
                '/fr/person/'.$this->person->getId().'/accompanying-period'
            ),
            'the server redirects to /accompanying-period page'
        );
        $this->assertGreaterThan(
            0,
            $this->client->followRedirect()
                ->filter('.alert-success')->count(),
            "a 'success' element is shown"
        );
    }

    /**
     * Test the closing of a periods.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and we fill the close form (at /fr/person/[id]/accompanying-period/close
     *      with : dateClosing: 2014-01-01
     *      with : the last closing motive in list
     * Then the response should redirect to period view
     * And the next page should have a `.alert-danger` element present in page
     *
     * @todo
     */
    public function testClosingCurrentPeriodWithDateClosingBeforeOpeningFails()
    {
        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/close');

        $form = $crawler->selectButton('Clôre la période')->form();

        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue((new \DateTime('2014-01-01'))->format('d-m-Y'));

        $crawlerResponse = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stays on the /close page'
        );
        $this->assertGreaterThan(
            0,
            $crawlerResponse
                ->filter('.alert-danger')->count(),
            "an '.alert-danger' element is shown"
        );
    }

    /**
     * create a period with date closing and date opening inside another period
     * fails.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and that this person has another accompanying period between 2014-01-01 and 2014-12-31
     * and we create a new period
     *      with : dateClosing: 2014-02-01
     *      with : dateOpening: 2014-03-01
     *      with : the last closing motive in list
     * Then the response should not redirect
     * and a error element is shown on the response page
     */
    public function testCreatePeriodAfterOpeningFails()
    {
        $this->generatePeriods([
            [
                'openingDate' => '2014-01-01',
                'closingDate' => '2014-12-31',
                'closingMotive' => $this->getRandomClosingMotive(),
            ],
        ]);

        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton('Créer une période d\'accompagnement')->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('2014-02-01');
        $form->get(self::OPENING_INPUT)
            ->setValue('01-03-2014');

        $crawlerResponse = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stay on form page'
        );
        $this->assertGreaterThan(
            0,
            $crawlerResponse->filter('.alert-danger')->count(),
            "an 'error' element is shown"
        );
    }

    /**
     * Create a period with closing after current fails.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and we create a new period
     *      with : dateClosing: 2015-02-01 (after 2015-01-05)
     *      with : dateOpening: 2014-12-31
     *      with : the last closing motive in list
     * Then the response should not redirect to any page
     * and an error element is shown
     */
    public function testCreatePeriodWithClosingAfterCurrentFails(): never
    {
        $this->markTestSkipped('Multiple period may now cover. This test is kept '.
            'in case of a configuration may add this feature again');

        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton("Créer une période d'accompagnement")->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('01-02-2015');
        $form->get(self::OPENING_INPUT)
            ->setValue('31-12-2014');

        $crawler = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stay on form page'
        );
        $this->assertGreaterThan(
            0,
            $crawler->filter('.alert-danger')->count(),
            "an 'error' element is shown"
        );
    }

    /**
     * create a period with date closing after opening fails.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and we create a new period
     *      with : dateClosing: 2014-01-01 (before opening)
     *      with : dateOpening: 2015-01-01
     *      with : the last closing motive in list
     * Then the response should redirect to period view
     */
    public function testCreatePeriodWithClosingBeforeOpeningFails()
    {
        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton('Créer une période d\'accompagnement')->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('01-01-2014');
        $form->get(self::OPENING_INPUT)
            ->setValue('01-01-2015');

        $crawler = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stay on form page'
        );
        $this->assertGreaterThan(
            0,
            $crawler->filter('.alert-danger')->count(),
            "an 'error' element is shown"
        );
    }

    /**
     * create a period with date end between another period must fails.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and that this person has another accompanying period between 2014-01-01 and 2014-12-31
     * and we create a new period
     *      with : dateClosing: 2014-16-01
     *      with : dateOpening: 2013-01-01
     *      with : the last closing motive in list
     * Then the response should not redirect
     * and a error element is shown on the response page
     */
    public function testCreatePeriodWithDateEndBetweenAnotherPeriodFails()
    {
        $this->generatePeriods([
            [
                'openingDate' => '2014-01-01',
                'closingDate' => '2014-12-31',
                'closingMotive' => $this->getRandomClosingMotive(),
            ],
        ]);

        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton('Créer une période d\'accompagnement')->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('31-12-2014');
        $form->get(self::OPENING_INPUT)
            ->setValue('01-02-2015');

        $crawlerResponse = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stay on form page'
        );
        $this->assertGreaterThan(
            0,
            $crawlerResponse->filter('.alert-danger')->count(),
            "an 'error' element is shown"
        );
    }

    /**
     * Create a period with dateOpening between another period must fails.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and that this person has another accompanying period between 2014-01-01 and 2014-12-31
     * and we create a new period
     *      with : dateClosing: 2015-01-01
     *      with : dateOpening: 2014-06-01
     *      with : the last closing motive in list
     * Then the response should not redirect
     * and a error element is shown on the response page
     */
    public function testCreatePeriodWithDateOpeningBetweenAnotherPeriodFails()
    {
        $this->generatePeriods([
            [
                'openingDate' => '2014-01-01',
                'closingDate' => '2014-12-31',
                'closingMotive' => $this->getRandomClosingMotive(),
            ],
        ]);

        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton('Créer une période d\'accompagnement')->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('2015-01-01');
        $form->get(self::OPENING_INPUT)
            ->setValue('01-06-2014');

        $crawlerResponse = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stay on form page'
        );
        $this->assertGreaterThan(
            0,
            $crawlerResponse->filter('.alert-danger')->count(),
            "an 'error' element is shown"
        );
    }

    /**
     * Create a period after a current opened period fails.
     *
     * Given that a person as an accompanying period opened since 2015-01-05
     * and we create a new period
     *      with : dateClosing: 2015-03-01
     *      with : dateOpening: 2015-02-01
     *      with : the last closing motive in list
     * Then the response should not redirect to any page
     * and an error element is shown
     */
    public function testCreatePeriodWithOpeningAndClosingAfterCurrentFails(): never
    {
        $this->markTestSkipped('Multiple period may now cover. This test is kept '.
            'in case of a configuration may add this feature again');

        $crawler = $this->client->request('GET', '/fr/person/'
              .$this->person->getId().'/accompanying-period/create');

        $form = $crawler->selectButton("Créer une période d'accompagnement")->form();
        $form->get(self::CLOSING_MOTIVE_INPUT)
            ->setValue($this->getLastValueOnClosingMotive($form));
        $form->get(self::CLOSING_INPUT)
            ->setValue('01-03-2015');
        $form->get(self::OPENING_INPUT)
            ->setValue('01-02-2015');

        $crawler = $this->client->submit($form);

        $this->assertFalse(
            $this->client->getResponse()->isRedirect(),
            'the server stay on form page'
        );
        $this->assertGreaterThan(
            0,
            $crawler->filter('.alert-danger')->count(),
            "an 'error' element is shown"
        );
    }

    /**
     * @group reopening
     */
    public function testReOpeningPeriod(): never
    {
        // test that re-opening a period which is opened does not work
        $this->client->request(
            'GET',
            sprintf(
                '/fr/person/%d/accompanying-period/%d/re-open',
                $this->person->getId(),
                $this->person->getOpenedAccompanyingPeriod()->getId()
            )
        );
        $this->assertEquals(
            400,
            $this->client->getResponse()->getStatusCode(),
            'Test an error is returned on a period which cannot be reopened'
        );

        // close the current period
        $period = $this->person->getOpenedAccompanyingPeriod();
        $period->setClosingDate(new \DateTime('2015-02-05'));
        $this->person->close($period);

        $this->generatePeriods([
            [
                'openingDate' => '2014-01-01',
                'closingDate' => '2014-12-31',
                'closingMotive' => $this->getRandomClosingMotive(),
            ],
        ]);

        $periods = $this->person->getAccompanyingPeriodsOrdered();
        /** @var Criteria $criteria */
        $criteria = Criteria::create();
        // $criteria->where(Criteria::expr()->eq('openingDate', \DateTime::createFromFormat()))
        $firstPeriod = reset($periods);
        $lastPeriod = end($periods);

        $this->markTestSkipped('From here, the test should be rewritten');
        // test that it is not possible to open the first period in the list
        $this->client->request(
            'GET',
            sprintf('/fr/person/%d/accompanying-period/%d/re-open', $this->person->getId(), reset($periods)->getId())
        );
        $this->assertEquals(
            400,
            $this->client->getResponse()->getStatusCode(),
            'Test an error is returned on the first period in the list'
        );

        // test that re-opening the last closed period works
        $crawler = $this->client->request(
            'GET',
            sprintf('/fr/person/%d/accompanying-period/%d/re-open', $this->person->getId(), end($periods)->getId())
        );

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

        $links = $crawler->selectLink('Confirmer');

        $this->assertEquals(1, $links->count(), "test the link 'confirmer' is present");

        $this->client->click($links->link());

        $this->assertTrue(
            $this->client->getResponse()->isRedirect(),
            'Test the response is a redirection => the period is re-opened'
        );
    }

    /**
     * Given an array of periods (key openingDate, closingDate (optioal),
     * closingMotive) generate the periods in the db.
     */
    protected function generatePeriods(array $periods)
    {
        foreach ($periods as $periodDef) {
            $period = new AccompanyingPeriod(new \DateTime($periodDef['openingDate']));

            if (\array_key_exists('closingDate', $periodDef)) {
                if (!\array_key_exists('closingMotive', $periodDef)) {
                    throw new \LogicalException('you must define a closing motive into your periods fixtures');
                }

                $period->setClosingDate(new \DateTime($periodDef['closingDate']))
                    ->setClosingMotive($periodDef['closingMotive']);
            }

            $this->person->addAccompanyingPeriod($period);
            self::$em->persist($period);
        }

        self::$em->flush();
    }

    /**
     * Get the last value of a closing motive.
     *
     * @var \Symfony\Component\DomCrawler\Form The form
     *
     * @return Chill\PersonBundle\Entity\AccompanyingPeriod The last value of closing
     *                                                      motive
     */
    protected function getLastValueOnClosingMotive(\Symfony\Component\DomCrawler\Form $form)
    {
        $values = $form->get(self::CLOSING_MOTIVE_INPUT)
            ->availableOptionValues();

        return end($values);
    }

    /**
     * Get a random closing motive.
     *
     * @return Chill\PersonBundle\Entity\AccompanyingPeriod The last closing motive
     */
    protected function getRandomClosingMotive()
    {
        $motives = self::$em
            ->getRepository('ChillPersonBundle:AccompanyingPeriod\ClosingMotive')
            ->findAll();

        return end($motives);
    }
}
