<?php

namespace App\Tests\Application;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Symfony\Bundle\Test\Client;
use App\Entity\Access\Access;
use App\Enum\Booking\VisibilityEnum;
use App\Enum\Core\ContactPointTypeEnum;
use App\Enum\Core\TimeZoneEnum;
use App\Enum\Education\AdvancedEducationNotationTypeEnum;
use App\Enum\Education\CycleEnum;
use App\Enum\Education\PeriodicityEnum;
use App\Enum\Organization\BulletinOutputEnum;
use App\Enum\Organization\BulletinPeriodEnum;
use App\Enum\Organization\LegalEnum;
use App\Enum\Organization\PrincipalTypeEnum;
use App\Enum\Organization\SendToBulletinEnum;
use App\Enum\Organization\SettingsProductEnum;
use App\Tests\Fixture\Factory\Access\AccessFactory;
use App\Tests\Fixture\Factory\Billing\BillingSettingFactory;
use App\Tests\Fixture\Factory\Billing\ResidenceAreaFactory;
use App\Tests\Fixture\Factory\Booking\EventFactory;
use App\Tests\Fixture\Factory\Core\ContactPointFactory;
use App\Tests\Fixture\Factory\Education\CycleFactory;
use App\Tests\Fixture\Factory\Education\EducationTimingFactory;
use App\Tests\Fixture\Factory\Mobyt\MobytUserStatusFactory;
use App\Tests\Fixture\Factory\Network\NetworkFactory;
use App\Tests\Fixture\Factory\Network\NetworkOrganizationFactory;
use App\Tests\Fixture\Factory\Organization\OrganizationFactory;
use App\Tests\Fixture\Factory\Organization\ParametersFactory;
use App\Tests\Fixture\Factory\Organization\SettingsFactory;
use App\Tests\Fixture\Factory\Organization\SubdomainFactory;
use App\Tests\Fixture\Factory\Person\PersonFactory;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Zenstruck\Foundry\Proxy;

/**
 * Base class for applicative tests.
 */
abstract class OtWebTestCase extends ApiTestCase
{
    protected EntityManagerInterface $em;
    protected Client $client;
    protected Access|Proxy|null $user = null;
    protected ?string $securityToken = null;
    protected Logger $logger;

    /**
     * Executed before each test.
     *
     * @throws \Exception
     */
    protected function setUp(): void
    {
        // Initialisation du logger
        $this->logger = new Logger('Test applicatif');
        $handler = new StreamHandler('php://stdout', Logger::DEBUG);
        $formatter = new LineFormatter(
            "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
            'Y-m-d H:i:s'
        );
        $handler->setFormatter($formatter);
        $this->logger->pushHandler($handler);

        // Boot le kernel symfony et récupère l'entity manager
        // @see https://symfony.com/doc/current/testing.html#retrieving-services-in-the-test
        self::bootKernel();
        $this->em = static::getContainer()->get(EntityManagerInterface::class);

        // Purge DB before populating new fixtures
        $this->purgeDb();
        $this->logger->info('Database purged');

        // Définit les fixtures et flush
        $this->loadFixtures();
        $this->em->flush();

        // Instancie le client qui exécutera les requêtes à l'api
        // @see https://symfony.com/doc/current/testing.html#making-requests
        $this->client = static::createClient();
    }

    protected function purgeDb()
    {
        if (!preg_match('/.*test.*/', $this->em->getConnection()->getDatabase())) {
            throw new \RuntimeException("The DB name shall contain 'test' in its name to be purge");
        }

        $this->em->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 0;');
        $purger = new ORMPurger($this->em);
        $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
        $purger->purge();

        $this->resetAutoIncrement();

        $this->em->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 1;');
    }

    protected function resetAutoIncrement()
    {
        $connection = $this->em->getConnection();
        $schemaManager = $connection->getSchemaManager();

        foreach ($schemaManager->listTableNames() as $tableName) {
            $connection->executeStatement("ALTER TABLE $tableName AUTO_INCREMENT = 1;");
        }
    }

    /**
     * Create and persist the fixtures (do not flush).
     *
     * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#same-entities-used-in-these-docs
     */
    protected function loadFixtures(): void
    {
        $adminEbag = PersonFactory::createOne(
            [
                'username' => 'ebag',
                'password' => 'password',
            ]
        );

        $personOfOtherOrganization = PersonFactory::createOne(
            [
                'username' => 'intruOfRoot',
                'password' => 'password',
            ]
        );

        $student = PersonFactory::createOne([
            'username' => 'studentEbag',
            'password' => 'password',
        ]);

        $contactPoint = ContactPointFactory::createOne([
            'contactType' => ContactPointTypeEnum::PRINCIPAL,
        ]);

        $parameters = ParametersFactory::createOne([
            'educationPeriodicity' => PeriodicityEnum::MONTHLY,
            'financialDate' => new \DateTime(),
            'musicalDate' => new \DateTime(),
            'startCourseDate' => new \DateTime(),
            'endCourseDate' => new \DateTime(),
            'average' => 20,
            'editCriteriaNotationByAdminOnly' => true,
            'smsSenderName' => 'MySender',
            'logoDonorsMove' => false,
            'subDomain' => 'subdomain',
            'website' => 'https://www.example.com',
            'otherWebsite' => 'https://www.otherwebsite.com',
            'customDomain' => 'https://www.customdomain.com',
            'desactivateOpentalentSiteWeb' => false,
            'bulletinPeriod' => BulletinPeriodEnum::YEAR,
            'bulletinWithTeacher' => false,
            'bulletinPrintAddress' => false,
            'bulletinSignatureDirector' => true,
            'bulletinDisplayLevelAcquired' => true,
            'bulletinShowEducationWithoutEvaluation' => false,
            'bulletinViewTestResults' => false,
            'bulletinShowAbsences' => false,
            'bulletinShowAverages' => true,
            'bulletinOutput' => BulletinOutputEnum::SEND_BY_EMAIL,
            'bulletinEditWithoutEvaluation' => true,
            'bulletinReceiver' => SendToBulletinEnum::STUDENTS_AND_THEIR_GUARDIANS,
            'usernameSMS' => '2iosinterne',
            'passwordSMS' => '2iosot74',
            'showAdherentList' => true,
            'studentsAreAdherents' => false,
            'timezone' => TimeZoneEnum::EUROPE_PARIS,
            'advancedEducationNotationType' => AdvancedEducationNotationTypeEnum::BY_EDUCATION,
            'sendAttendanceEmail' => true,
            'sendAttendanceSms' => true,
            'generateAttendanceReport' => true,
            'consultPedagogicResult' => true,
            'consultTeacherListing' => true,
            'periodValidation' => true,
        ]);

        $parameters2 = ParametersFactory::createOne([
            'educationPeriodicity' => PeriodicityEnum::MONTHLY,
            'financialDate' => new \DateTime(),
            'musicalDate' => new \DateTime(),
            'startCourseDate' => new \DateTime(),
            'endCourseDate' => new \DateTime(),
            'average' => 20,
            'editCriteriaNotationByAdminOnly' => true,
            'smsSenderName' => 'toto',
            'logoDonorsMove' => false,
            'subDomain' => 'subdomain',
            'website' => 'https://www.toto.com',
            'otherWebsite' => 'https://www.toto.com',
            'customDomain' => 'https://www.toto.com',
            'desactivateOpentalentSiteWeb' => false,
            'bulletinPeriod' => BulletinPeriodEnum::YEAR,
            'bulletinWithTeacher' => false,
            'bulletinPrintAddress' => false,
            'bulletinSignatureDirector' => true,
            'bulletinDisplayLevelAcquired' => true,
            'bulletinShowEducationWithoutEvaluation' => false,
            'bulletinViewTestResults' => false,
            'bulletinShowAbsences' => false,
            'bulletinShowAverages' => true,
            'bulletinOutput' => BulletinOutputEnum::SEND_BY_EMAIL,
            'bulletinEditWithoutEvaluation' => true,
            'bulletinReceiver' => SendToBulletinEnum::STUDENTS_AND_THEIR_GUARDIANS,
            'usernameSMS' => 'toto',
            'passwordSMS' => 'toto',
            'showAdherentList' => true,
            'studentsAreAdherents' => false,
            'timezone' => TimeZoneEnum::EUROPE_PARIS,
            'advancedEducationNotationType' => AdvancedEducationNotationTypeEnum::BY_EDUCATION,
            'sendAttendanceEmail' => true,
            'sendAttendanceSms' => true,
            'generateAttendanceReport' => true,
            'consultPedagogicResult' => true,
            'consultTeacherListing' => true,
            'periodValidation' => true,
        ]);

        $organization = OrganizationFactory::createOne([
            'legalStatus' => LegalEnum::ASSOCIATION_LAW_1901,
            'principalType' => PrincipalTypeEnum::NATIONAL_FEDERATION,
            'name' => 'Root',
            'parameters' => $parameters,
            'siretNumber' => '34919841600035',
            'identifier' => 'FR042100000050',
        ]);

        $network1 = NetworkFactory::createOne([
            'name' => 'Network 1',
            'logo' => 'logo',
            'url' => 'https://www.network1.com',
        ]);

        $network2 = NetworkFactory::createOne([
            'name' => 'Network 2',
            'logo' => 'logo',
            'url' => 'https://www.network2.com',
        ]);

        $cmfNetwork = NetworkFactory::createOne([
            'name' => 'CMF',
            'logo' => 'logo',
            'url' => 'https://www.cmf.com',
        ]);

        $networkOrganization = NetworkOrganizationFactory::createOne([
            'network' => $cmfNetwork,
            'organization' => $organization,
            'startDate' => new \DateTime('2001-01-01'),
            'endDate' => new \DateTime('2031-12-31'),
            'leadingCause' => 'leadingCause',
        ]);

        $billingSetting = BillingSettingFactory::createOne([
            'organization' => $organization,
        ]);

        $residenceArea = ResidenceAreaFactory::createOne([
            'label' => 'Résidence 1',
            'billingSetting' => $billingSetting,
        ]);

        $organization2 = OrganizationFactory::createOne([
            'legalStatus' => LegalEnum::ASSOCIATION_LAW_1901,
            'principalType' => PrincipalTypeEnum::NATIONAL_FEDERATION,
            'name' => 'Other Organization',
            'parameters' => $parameters2,
        ]);

        $settings2 = SettingsFactory::createOne([
            'product' => SettingsProductEnum::SCHOOL_PREMIUM,
            'organization' => $organization2,
            'modules' => ['BillingAdministration'],
        ]);
        $mobyteUserStatus = MobytUserStatusFactory::createOne([
            'organizationId' => $organization->getId(),
            'active' => true,
            'amount' => 100,
            'money' => 100,
        ]);

        $cycle = CycleFactory::createOne([
            'organization' => $organization,
            'label' => 'Cycle 1',
            'cycleEnum' => CycleEnum::CYCLE_1,
        ]);

        $settings = SettingsFactory::createOne([
            'product' => SettingsProductEnum::SCHOOL_PREMIUM,
            'organization' => $organization,
            'modules' => [
                'Sms' => true,
                // 'BillingAdministration' => true,
            ],
        ]);

        $educationTimings = EducationTimingFactory::createOne([
            'organization' => $organization,
            'timing' => 45,
        ]);

        $educationTimings2 = EducationTimingFactory::createOne([
            'organization' => $organization2,
            'timing' => 60,
        ]);

        $subdomain = SubdomainFactory::createOne([
            'organization' => $organization,
            'subdomain' => 'subdomain',
            'active' => true,
        ]);

        $event = EventFactory::createOne([
            'organization' => $organization,
            'name' => 'My event',
            'datetimeStart' => new \DateTime(),
            'datetimeEnd' => new \DateTime(),
            'visibility' => VisibilityEnum::PUBLIC_VISIBILITY,
        ]);

        $this->user = AccessFactory::createOne([
            'person' => $adminEbag,
            'organization' => $organization,
            'roles' => ['ROLE_ADMIN', 'ROLE_ADMIN_CORE', 'ROLE_SUPER_ADMIN', 'ROLE_ORGANIZATION_VIEW', 'ROLE_ORGANIZATION'],
            'adminAccess' => true,
            'activityYear' => 2021,
        ]);

        $accessWithNoRole = AccessFactory::createOne([
            'person' => $student,
            'organization' => $organization,
            'roles' => ['ROLE_STUDENT'],
            'adminAccess' => false,
        ]);

        $acccesFromOtherOrganization = AccessFactory::createOne([
            'person' => $personOfOtherOrganization,
            'organization' => $organization2,
            'roles' => ['ROLE_ADMIN', 'ROLE_ADMIN_CORE', 'ROLE_SUPER_ADMIN', 'ROLE_ORGANIZATION_VIEW', 'ROLE_ORGANIZATION'],
            'adminAccess' => true,
        ]);

        $this->logger->info('User created in loadFixture: ', [
            'id' => $this->user->getId(),
            'username' => $this->user->getPerson()->getUsername(),
            'roles' => $this->user->getRoles(),
            'organization' => $this->user->getOrganization()->getName(),
            'billingSetting' => $this->user->getOrganization()->getBillingSetting()->getId(),
        ]);
    }

    /**
     * Send a requests, parse the hydra response and return an object or a Collection.
     *
     * @param array<mixed> $data
     * @param array<mixed> $headers
     */
    protected function request(string $method, string $route, ?array $data = null, array $headers = []): ResponseInterface
    {
        if ($this->user) {
            $headers = array_merge(
                ['x-accessid' => $this->user->getId(), 'authorization' => 'BEARER '.$this->securityToken],
                $headers
            );
        }

        $parameters = ['headers' => $headers];
        if ($data) {
            $parameters['json'] = $data;
        }

        return $this->client->request(
            $method,
            $route,
            $parameters
        );
    }

    /**
     * Send a GET request and return the response parsed content.
     *
     * @param array<mixed> $headers
     */
    protected function get(string $route, array $headers = []): ResponseInterface
    {
        return $this->request(
            Request::METHOD_GET,
            $route,
            null,
            $headers
        );
    }

    /**
     * Send a PUT request and return the response parsed content.
     *
     * @param array<mixed> $data
     * @param array<mixed> $headers
     */
    protected function put(string $route, array $data, array $headers = []): ResponseInterface
    {
        return $this->request(
            Request::METHOD_PUT,
            $route,
            $data,
            $headers
        );
    }

    /**
     * Send a POST request and return the response parsed content.
     *
     * @param array<mixed> $data
     * @param array<mixed> $headers
     */
    protected function post(string $route, array $data, array $headers = []): ResponseInterface
    {
        return $this->request(
            Request::METHOD_POST,
            $route,
            $data,
            $headers
        );
    }

    /**
     * Send a DELETE request and return the response parsed content.
     *
     * @param array<mixed> $headers
     */
    protected function delete(string $route, array $headers = []): ResponseInterface
    {
        return $this->request(
            Request::METHOD_DELETE,
            $route,
            null,
            $headers
        );
    }

    /**
     * Login as the given Access user.
     *
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
     */
    public function loginAs()
    {
        $access = $this->em->getRepository(Access::class)->find(1);
        $person = $access->getPerson();

        $response = $this->post(
            '/login_check',
            ['username' => $person->getUsername(), 'password' => $person->getPassword()]
        );

        $content = $response->getContent();

        $decodedContent = json_decode($content);

        // Vérifier que le token est présent
        if (!isset($decodedContent->token)) {
            throw new \Exception('Token not found in response');
        }

        $this->securityToken = $decodedContent->token;
        $this->user = $access;
    }

    /**
     * Login as the given Access user.
     *
     * @return void
     *
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
     */
    public function loginAsStudent()
    {
        // on récupère l'access qui a l'id 641003 dans l'entity manager
        $access = $this->em->getRepository(Access::class)->find(2);
        $person = $access->getPerson();

        $response = $this->post(
            '/login_check',
            ['username' => $person->getUsername(), 'password' => $person->getPassword()]
        );

        $content = $response->getContent();

        $this->securityToken = json_decode($content)->token;
        $this->user = $access;
    }

    /**
     * Login as the given Access user.
     *
     * @return void
     *
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
     */
    public function loginAsintruOfRoot()
    {
        $access = $this->em->getRepository(Access::class)->find(3);
        $person = $access->getPerson();

        $response = $this->post(
            '/login_check',
            ['username' => $person->getUsername(), 'password' => $person->getPassword()]
        );

        $content = $response->getContent();

        $this->securityToken = json_decode($content)->token;
        $this->user = $access;
    }

    /**
     * Assert that the response has the expected status code and is well formated.
     */
    protected function validateCollectionSchema(string $resourceClass, int $expectedStatus = 200): void
    {
        $this->assertResponseStatusCodeSame($expectedStatus);

        if ($expectedStatus == 200) {
            $this->assertResponseIsSuccessful();
        }

        // Asserts that the returned content type is JSON-LD (the default)
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');

        // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform
        // >>> Issue with the json typed PublicStructure::addresses properties
        // $this->assertMatchesResourceCollectionJsonSchema($resourceClass);
    }
}
