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

use Chill\ActivityBundle\Entity\ActivityType;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Role\Role;

/**
 * @internal
 *
 * @coversNothing
 */
final class ActivityControllerTest extends WebTestCase
{
    /**
     * @dataProvider getSecuredPagesUnauthenticated
     */
    public function testAccessIsDeniedForUnauthenticated(mixed $url)
    {
        $client = $this->createClient();

        $client->request('GET', $url);

        $this->assertEquals(302, $client->getResponse()->getStatusCode());
        $this->assertTrue(
            $client->getResponse()->isRedirect('http://localhost/login'),
            sprintf('the page "%s" does not redirect to http://localhost/login', $url)
        );
    }

    /**
     * Provide a client unauthenticated and.
     */
    public function getSecuredPagesUnauthenticated()
    {
        self::bootKernel();
        $person = $this->getPersonFromFixtures();
        $activities = $this->getActivitiesForPerson($person);

        return [
            [sprintf('fr/person/%d/activity/', $person->getId())],
            [sprintf('fr/person/%d/activity/new', $person->getId())],
            [sprintf('fr/person/%d/activity/%d/show', $person->getId(), $activities[0]->getId())],
            [sprintf('fr/person/%d/activity/%d/edit', $person->getId(), $activities[0]->getId())],
        ];
    }

    /**
     * @dataProvider getSecuredPagesAuthenticated
     *
     * @param type $client
     * @param type $url
     */
    public function testAccessIsDeniedForUnauthorized($client, $url)
    {
        $client->request('GET', $url);

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

    public function getSecuredPagesAuthenticated()
    {
        self::bootKernel();

        $person = $this->getPersonFromFixtures();
        $activities = $this->getActivitiesForPerson($person);

        $user = $this->createFakeUser();

        return [
            [
                $this->getAuthenticatedClient('center b_social'),
                sprintf('fr/person/%d/activity/', $person->getId()),
            ],
            [
                $this->getAuthenticatedClient('center b_social'),
                sprintf('fr/person/%d/activity/new', $person->getId()),
            ],
            [
                $this->getAuthenticatedClient('center b_social'),
                sprintf('fr/person/%d/activity/%d/show', $person->getId(), $activities[0]->getId()),
            ],
            [
                $this->getAuthenticatedClient('center b_social'),
                sprintf('fr/person/%d/activity/%d/edit', $person->getId(), $activities[0]->getId()),
            ],
            [
                $this->getAuthenticatedClient($user->getUsername()),
                sprintf('fr/person/%d/activity/new', $person->getId()),
            ],
        ];
    }

    public function testCompleteScenario()
    {
        // Create a new client to browse the application
        $client = $this->getAuthenticatedClient();
        $person = $this->getPersonFromFixtures();

        // Create a new entry in the database
        $crawler = $client->request('GET', sprintf(
            'en/person/%d/activity/',
            $person->getId()
        ));
        $this->assertEquals(
            200,
            $client->getResponse()->getStatusCode(),
            'Unexpected HTTP status code for GET /activity/'
        );
        $crawler = $client->click($crawler->selectLink('Ajouter un nouvel échange')
            ->link());

        $reason1 = $this->getRandomActivityReason();
        $reason2 = $this->getRandomActivityReason([$reason1->getId()]);

        // Fill in the form and submit it
        $form = $crawler->selectButton('Ajouter un nouvel échange')->form([
            'chill_activitybundle_activity' => [
                'date' => '15-01-2015',
                'durationTime' => 600,
                //   'remark' => 'blabla',
                'scope' => $this->getRandomScope('center a_social', 'Center A')->getId(),
                'type' => $this->getRandomActivityType()->getId(),
            ],
        ]);
        $form['chill_activitybundle_activity[reasons]']->select([$reason1->getId(), $reason2->getId()]);

        $client->submit($form);

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

        // Check data in the show view
        $this->assertGreaterThan(
            0,
            $crawler->filter('dd:contains("January 15, 2015")')->count(),
            'Missing element dd:contains("January 15, 2015")'
        );

        // Edit the entity
        $crawler = $client->click($crawler->selectLink("Modifier l'échange")->link());

        $form = $crawler->selectButton("Sauver l'échange")->form([
            'chill_activitybundle_activity' => [
                'date' => '25-01-2015',
                //  'remark' => 'Foo'
            ],
        ]);

        $client->submit($form);

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

        $crawler = $client->followRedirect();

        // check that new data are present
        $this->assertGreaterThan(
            0,
            $crawler->filter('dd:contains("January 25, 2015")')->count(),
            'Missing element dd:contains("January 25, 2015")'
        );
        $this->assertGreaterThan(
            0,
            $crawler->filter('dd:contains("Foo")')->count(),
            'Missing element dd:contains("Foo")'
        );

        // delete the actvity
        $crawler = $client->click($crawler->selectLink('Supprimer')->link());

        $button = $crawler->selectButton('Supprimer');

        $form = $button->form();

        $client->submit($form);
        $this->assertTrue($client->getResponse()->isRedirect(sprintf(
            '/en/person/%d/activity/',
            $person->getId()
        )));

        $crawler = $client->followRedirect();

        $this->assertStringNotContainsString('January 25, 2015', $crawler->text());
    }

    /**
     * create a user without any permissions on CHILL_ACTIVITY_* but with
     * permissions on center.
     *
     * @return \Chill\MainBundle\Entity\User a fake user within a group without activity
     */
    private function createFakeUser()
    {
        $container = self::$kernel->getContainer();
        $em = $container->get('doctrine.orm.entity_manager');

        // get the social PermissionGroup, and remove CHILL_ACTIVITY_*
        $socialPermissionGroup = $em
            ->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)
            ->findOneByName('social');
        $withoutActivityPermissionGroup = (new \Chill\MainBundle\Entity\PermissionsGroup())
            ->setName('social without activity');
        // copy role scopes where ACTIVITY is not present
        foreach ($socialPermissionGroup->getRoleScopes() as $roleScope) {
            if (!strpos((string) $roleScope->getRole(), 'ACTIVITY')) {
                $withoutActivityPermissionGroup->addRoleScope($roleScope);
            }
        }
        // create groupCenter
        $groupCenter = new \Chill\MainBundle\Entity\GroupCenter();
        $groupCenter->setCenter($em->getRepository(\Chill\MainBundle\Entity\Center::class)
            ->findOneBy(['name' => 'Center A']))
            ->setPermissionsGroup($withoutActivityPermissionGroup);
        $em->persist($withoutActivityPermissionGroup);
        $em->persist($groupCenter);

        // create user
        $faker = \Faker\Factory::create();
        $username = $faker->name;
        $user = new \Chill\MainBundle\Entity\User();
        $user
            ->setPassword($container->get('security.password_encoder')
                ->encodePassword($user, 'password'))
            ->setUsername($username)
            ->addGroupCenter($groupCenter);

        $em->persist($user);

        $em->flush();

        return $user;
    }

    private function getActivitiesForPerson(\Chill\PersonBundle\Entity\Person $person)
    {
        $em = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager');

        $activities = $em->getRepository(\Chill\ActivityBundle\Entity\Activity::class)
            ->findBy(['person' => $person]);

        if (0 === \count($activities)) {
            throw new \RuntimeException('We need activities associated with this person. Did you forget to add fixtures ?');
        }

        return $activities;
    }

    /**
     * @return \Symfony\Component\BrowserKit\AbstractBrowser
     */
    private function getAuthenticatedClient(mixed $username = 'center a_social')
    {
        return self::createClient([], [
            'PHP_AUTH_USER' => $username,
            'PHP_AUTH_PW' => 'password',
        ]);
    }

    /**
     * @return \Chill\PersonBundle\Entity\Person
     */
    private function getPersonFromFixtures()
    {
        $em = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager');

        $person = $em->getRepository(\Chill\PersonBundle\Entity\Person::class)
            ->findOneBy([
                'firstName' => 'Depardieu',
                'lastName' => 'Gérard',
            ]);

        if (null === $person) {
            throw new \RuntimeException('We need a person with firstname Gérard and lastname Depardieu. Did you add fixtures ?');
        }

        return $person;
    }

    /**
     * @param int[] $excludeIds An array of id to exclude
     *
     * @return \Chill\ActivityBundle\Entity\ActivityReason
     */
    private function getRandomActivityReason(array $excludeIds = [])
    {
        $reasons = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager')
            ->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)
            ->findAll();

        $reason = $reasons[array_rand($reasons)];

        if (\in_array($reason->getId(), $excludeIds, true)) {
            return $this->getRandomActivityReason($excludeIds);
        }

        return $reason;
    }

    /**
     * @return ActivityType
     */
    private function getRandomActivityType()
    {
        $types = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager')
            ->getRepository(ActivityType::class)
            ->findAll();

        return $types[array_rand($types)];
    }

    /**
     * @param string $username
     * @param string $centerName
     *
     * @return \Chill\MainBundle\Entity\Scope
     */
    private function getRandomScope($username, $centerName)
    {
        $user = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager')
            ->getRepository(\Chill\MainBundle\Entity\User::class)
            ->findOneByUsername($username);

        if (null === $user) {
            throw new \RuntimeException("The user with username {$username} ".'does not exists in database. Did you add fixtures ?');
        }

        $center = self::$kernel->getContainer()
            ->get('doctrine.orm.entity_manager')
            ->getRepository(\Chill\MainBundle\Entity\Center::class)
            ->findOneByName($centerName);

        // get scope reachable by both role UPDATE and DELETE
        $reachableScopesUpdate = self::$kernel->getContainer()
            ->get('chill.main.security.authorization.helper')
            ->getReachableScopes(
                $user,
                'CHILL_ACTIVITY_UPDATE',
                $center
            );
        $reachableScopesDelete = self::$kernel->getContainer()
            ->get('chill.main.security.authorization.helper')
            ->getReachableScopes(
                $user,
                'CHILL_ACTIVITY_DELETE',
                $center
            );
        $reachableScopesId = array_intersect(
            array_map(static fn ($s) => $s->getId(), $reachableScopesDelete),
            array_map(static fn ($s) => $s->getId(), $reachableScopesUpdate)
        );

        if (0 === \count($reachableScopesId)) {
            throw new \RuntimeException('there are not scope reachable for both CHILL_ACTIVITY_UPDATE and CHILL_ACTIVITY_DELETE');
        }

        foreach ($reachableScopesUpdate as $scope) {
            if (\in_array($scope->getId(), $reachableScopesId, true)) {
                $reachableScopes[] = $scope;
            }
        }

        return $reachableScopes[array_rand($reachableScopes)];
    }
}
