<?php

namespace App\Service\OnlineRegistration;

use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Enum\OnlineRegistration\ValidationStateEnum;
use App\Service\Access\Utils as AccessUtils;
use App\Service\Utils\ArrayUtils;
use App\Service\Utils\DatesUtils;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;


/**
 * --- SERVICE INUTILISE POUR LE MOMENT ---
 * Service de lancement de l'inscription en ligne
 */
class RegistrationStartingService
{
    const EDUCATION_REREGISTER_RIGHT = "canReregisterEducations";
    const EDUCATION_ADD_RIGHT = "canAddEducations";
    const STUDENT_ADD_RIGHT = "canAddStudents";
    const ONLY_EDUCATION_ADD_RIGHT = "lockAllExeptAddNewEducation";


    public function __construct(
        private OnlineRegistrationService $onlineRegistrationService,
        private AccessUtils $accessUtils
    ) {}

    /**
     * Teste si un Access peut accéder à l'IEL
     * Sinon, lève une RuntimeException
     *
     * @param Access $access
     * @return bool
     */
    public function canStartOnlineRegistration(Access $access): bool {

        if ($this->accessAndFamilyWishAlreadyValidateCanAddEducation($access)) {
            return true;
        }

        $functions = $this->accessUtils->getActiveFunctions($access);

        $hasNoFunction = $functions === ["OTHER_FUNCTION"] && !$access->getAdminAccess();

        $isStudent = in_array('STUDENT', $functions);

        if (!$isStudent && $access->getAccessFamily()) {
            // On regarde si un membre de la famille est un élève actif
            $isStudent = ArrayUtils::any(
                $access->getAccessFamily()->getAccesses()->toArray(),
                function ($access) { return $this->accessUtils->hasActiveFunction($access, 'STUDENT'); }
            );
        }

        $reregistration = $isStudent && !$access->isNewAccess();

        // La structure dois posséder des periodes d'ouvertures
        if (!$this->hasOpeningHours($access->getOrganization(), $reregistration)) {
            throw new \RuntimeException('no_opening_hours_users');
        }

        // Aujourd'hui doit faire partie des périodes d'ouvertures
        if(!$this->isInOpeningPeriod($access->getOrganization(), $reregistration)) {
            throw new \RuntimeException('not_inside_period');
        }

        // Si l'Access a déjà répondu une fois cette année à l'IEL (ou un membre de la famille)
        if($this->checkIelValidationDateTimeFamily($access)) {
            throw new \RuntimeException('iel_allready_done');
        }
        else if (!$hasNoFunction && !$access->isIelEnabled()) {
            throw new \RuntimeException('no_access_granted');
        }

        return true;
    }

    /**
     * TODO: compléter la doc et trouver un nom plus court
     *
     * @param Access $access
     * @return bool
     * @throws \Exception
     */
    private function accessAndFamilyWishAlreadyValidateCanAddEducation(Access $access): bool {
        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);

        if (!$currentAccessWish) {
            return false;
        }

        $accessWishAlreadyValidateCanAddEducation =
            $this->accessWishAlreadyValidateCanAddEducation($access) ||
            (
                $access->getAccessFamily() &&
                !$access->getAccessFamily()->getAccesses()->isEmpty() &&
                ArrayUtils::any(
                    $access->getAccessFamily()->getAccesses()->toArray(),
                    function ($familyAccess) { return $this->accessWishAlreadyValidateCanAddEducation($familyAccess); }
                )
            );

        if ($accessWishAlreadyValidateCanAddEducation) {
            // La structure dois posséder des periodes d'ouvertures
            if (!$this->hasOpeningHours($access->getOrganization(),false)){
                throw new \Exception('no_opening_hours_users');
            }

            // Aujourd'hui doit faire partit des périodes d'ouvertures
            if (!$this->isInsidePeriod($access->getOrganization(),false)){
                throw new \Exception('not_inside_period');
            }
        }

        return $accessWishAlreadyValidateCanAddEducation;
    }


    /**
     * TODO: compléter la doc et trouver un nom plus court
     *
     * @param Access $access
     * @return bool
     */
    private function accessWishAlreadyValidateCanAddEducation(Access $access): bool
    {
        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);

        if (!$currentAccessWish) {
            return false;
        }

        $accessFamilyWish = $currentAccessWish->getAccessFamilyWish();

        if ($accessFamilyWish?->isCloseRegistration()) {
            return false;
        }

        if ($currentAccessWish->getValidationState() !== ValidationStateEnum::OPEN_FOR_EDUCATIONS) {
            return false;
        }

        $studentsAndEducationsAuthorization = $this->buildStudentAuthorizations($access);

        return $studentsAndEducationsAuthorization[self::EDUCATION_ADD_RIGHT] === true;
    }

    /**
     * Détermine les autorisations d'un étudiant
     *
     * @param Access $access
     * @return array<string, bool>
     */
    private function buildStudentAuthorizations(Access $access): array {

        $rights = [
            self::EDUCATION_REREGISTER_RIGHT => false,
            self::EDUCATION_ADD_RIGHT => true,
            self::STUDENT_ADD_RIGHT => true,
            self::ONLY_EDUCATION_ADD_RIGHT => false
        ];

        $isNewStudent = $access->isNewAccess();
        if ($isNewStudent) {
            return $rights;
        }

        $organization = $access->getOrganization();
        if ($organization === null) {
            throw new \RuntimeException('Missing organization');
        }

        $organizationSettings = $organization->getOnlineRegistrationSettings();
        $addNewStudentsSetting = $organizationSettings->canAddNewStudents();
        $addNewEducationsSetting = $organizationSettings->canAddNewEducations();

        $reRegistrationIsOpen = $this->isRegistrationOpen($organization, true);
        // TODO: je comprend pas bien cette condition?
        $rights[self::EDUCATION_REREGISTER_RIGHT] = $reRegistrationIsOpen || (!$addNewStudentsSetting && !$addNewEducationsSetting);

        $newRegistrationIsOpen = $this->isRegistrationOpen($organization, false);
        $rights[self::EDUCATION_ADD_RIGHT] =  $newRegistrationIsOpen || $addNewEducationsSetting;
        $rights[self::STUDENT_ADD_RIGHT] =  $newRegistrationIsOpen || $addNewStudentsSetting;

        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);

        $rights[self::STUDENT_ADD_RIGHT] = $currentAccessWish &&
            $currentAccessWish->getValidationState() === ValidationStateEnum::OPEN_FOR_EDUCATIONS ||
            (
                $currentAccessWish->getAccessFamilyWish() &&
                ArrayUtils::any(
                    $currentAccessWish->getAccessFamilyWish()->getAccessWishes()->toArray(),
                    function ($wish) { return $wish->getValidationState() === ValidationStateEnum::OPEN_FOR_EDUCATIONS; }
                )
            );

        return $rights;
    }

    /**
     * Retourne true si l'IEL est ouvert pour l'organisation donnée
     *
     * @param Organization $organization
     * @param bool $reregistration
     * @return bool
     */
    private function isRegistrationOpen(Organization $organization, bool $reregistration = true) {
        return $this->hasOpeningHours($organization, $reregistration) &&
               $this->isInOpeningPeriod($organization, $reregistration);
    }

    /**
     * Retourne les périodes d'ouverture de l'IEL d'une organization, selon qu'il s'agisse de réinscription ou non
     *
     * @param Organization $organization
     * @param bool $reregistration
     * @return Collection
     */
    private function getOrganizationPeriods(Organization $organization, bool $reregistration = true): Collection {
        $organizationSettings = $organization->getOnlineRegistrationSettings();
        return $reregistration ?
            $organizationSettings->getOpeningPeriods() :
            $organizationSettings->getOpeningPeriodsNewEnrolments();
    }

    /**
     * TODO: completer doc
     *
     * @param Organization $organization
     * @param bool $reregistration
     * @return bool
     */
    private function hasOpeningHours(Organization $organization, bool $reregistration = true): bool {
        $openingPeriods = $this->getOrganizationPeriods($organization, $reregistration);
        return count($openingPeriods) > 0;
    }

    /**
     * Vérifie que nous sommes actuellement dans au moins une des périodes d'ouverture de l'IEL de l'organisation
     *
     * @param Organization $organization
     * @param $reregistration
     * @return bool
     */
    public function isInOpeningPeriod(Organization $organization, bool $reregistration = true): bool {
        return ArrayUtils::any(
            $this->getOrganizationPeriods($organization, $reregistration)->toArray(),
            function ($period) { return DatesUtils::isNowInInterval($period->getStartDate(), $period->getEndDate()); }
        );
    }

    /**
     * TODO: completer doc et revoir le nom
     *
     * @param Access $access
     * @return bool
     */
    private function checkIelValidationDateTimeFamily(Access $access): bool {
        $today = DatesUtils::new();

        if(
            $access->getIelValidationDatetime() &&
            ($access->getIelValidationDatetime()->format('Y') === $today->format('Y'))
        ) {
            return true;
        }

        if (
            $access->getAccessFamily() &&
            !$access->getAccessFamily()->getAccesses()->isEmpty()
        ) {
            return ArrayUtils::any(
                $access->getAccessFamily()->getAccesses()->toArray(),
                function ($access) use ($today) {
                    return $access->getIelValidationDatetime() && $access->getIelValidationDatetime()->format('Y') === $today->format('Y');
                }
            );
        }

        return false;
    }
}
