<?php
declare(strict_types=1);

namespace App\Service\Constraint;

use App\Entity\Access\Access;
use App\Service\Organization\Utils as OrganizationUtils;
use App\Tests\Service\Constraint\DateTimeConstraintTest;
use Doctrine\ORM\EntityManagerInterface;
use Exception;

/**
 * Classe DateTimeConstraint qui définie les dates de débuts et fin de périodes
 * par rapport au contraintes temporelles choisies par un utilisateur.
 */
class DateTimeConstraint extends AbstractTimeConstraintUtils
{
    public function __construct(
        readonly private EntityManagerInterface $entityManager,
        readonly private OrganizationUtils $organizationUtils
    )
    { }

    /**
     * Main méthod
     * @param int $accessID
     * @return array<string, array<string, list<int>>>
     * @throws Exception
     */
    public function invoke(int $accessID): array
    {
        $access = $this->entityManager->getRepository(Access::class)->find($accessID);
        $historical = $access->getHistorical();

        $contraints = [
            self::START_KEY => [],
            self::END_KEY => []
        ];

        if($this->hasCustomPeriods($historical)){
            $periods = $this->getCustomPeriods($historical['dateStart'], $historical['dateEnd']);
            //Une période "Custom" reviens à savoir quels sont les éléments actif durant le "présent" de la période custom, donc
            //on peut utiliser le presentConstraint avec les periods custom
            $contraints = $this->addConstraint($contraints, $this->presentConstraint($periods));
        }else{
            $periods = $this->getPeriods($access);
            if($historical['present']) $contraints = $this->addConstraint($contraints, $this->presentConstraint($periods));
            if($historical['past']) $contraints = $this->addConstraint($contraints, $this->pastConstraint($periods));
            if($historical['future']) $contraints = $this->addConstraint($contraints, $this->futureConstraint($periods));
        }
        return $this->cleanConstraints($contraints);
    }

    /**
     * Retourne le tableau des périodes custom
     * @param string $dateStart
     * @param string $dateEnd
     * @return string[]
     */
    protected function getCustomPeriods(string $dateStart, string $dateEnd): array
    {
        return [
            OrganizationUtils::START_DATE_KEY => $dateStart,
            OrganizationUtils::END_DATE_KEY => $dateEnd
        ];
    }

    /**
     * Fonction permettant de récupérer les périodes de début et de fin d'affichage
     *
     * @param Access $access
     * @return string[]
     * @throws Exception
     * @see DateTimeConstraintTest::testGetPeriodsToday()
     */
    protected function getPeriods(Access $access): array
    {
        $organization = $access->getOrganization();
        $activityYear = $access->getActivityYear();
        $currentActivityYear = $this->organizationUtils->getOrganizationCurrentActivityYear($organization);

        $periods = $this->organizationUtils->getActivityPeriodsSwitchYear($organization, $activityYear);

        // Si l'année courante est l'année d'affichage choisie par l'utilisateur, alors la date de début est aujourd'hui
        if($activityYear === $currentActivityYear){
            $today = new \DateTime('now');
            $periods[OrganizationUtils::START_DATE_KEY] = $today->format('Y-m-d');
        }

        return $periods;
    }

    /**
     * Une période est dans le présent si :
     *  - la date de début est plus petite ou égale (<=) à la date de fin de période à afficher
     * ET
     *  - la date de fin est plus grande ou égale (>=) à la date de fin de période à afficher OU que la date de fin n'est pas remplie (NULL)
     *
     * @param string[] $periods
     * @return array<string, int[]>
     *
     * @see DateTimeConstraintTest::testPresentConstrain()
     */
    protected function presentConstraint(array $periods): array
    {
        return [
          self::START_KEY => [
              $periods[OrganizationUtils::END_DATE_KEY] => self::INF + self::EQUAL
          ],
          self::END_KEY => [
              $periods[OrganizationUtils::START_DATE_KEY] => self::SUP + self::EQUAL,
              self::NULL_VALUE => self::NULL
          ]
        ];
    }

    /**
     * Une période est dans le passé si :
     * - la date de fin est plus petite (<) que la date de début de période à afficher
     * @param string[] $periods
     * @return array<string, int[]>
     * @see DateTimeConstraintTest::testPastConstrain()
     */
    protected function pastConstraint(array $periods): array
    {
        return [
            self::END_KEY => [
                $periods[OrganizationUtils::START_DATE_KEY] => self::INF
            ]
        ];
    }

    /**
     * Une période est dans le futur si :
     * - la date de début est plus grande (>) que la date de fin de période à afficher
     * @param string[] $periods
     * @return array<string, int[]>
     * @see DateTimeConstraintTest::testFuturConstrain()
     */
    protected function futureConstraint(array $periods): array
    {
        return [
            self::START_KEY => [
                $periods[OrganizationUtils::END_DATE_KEY] => self::SUP
            ]
        ];
    }
}
