<?php
declare(strict_types=1);

namespace App\Repository\Access;

use App\DQL\DateConditions;
use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Enum\Access\FunctionEnum;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @method Access|null find($id, $lockMode = null, $lockVersion = null)
 * @method Access|null findOneBy(array $criteria, array $orderBy = null)
 * @method Access[]    findAll()
 * @method Access[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class AccessRepository extends ServiceEntityRepository implements UserLoaderInterface
{
    const ACCESS_NAME_HEADER = 'X-AccessId';
    const HTTP_X_SWITCH_USER = 'X-Switch-User';

    public function __construct(ManagerRegistry $registry, private RequestStack $requestStack)
    {
        parent::__construct($registry, Access::class);
    }

    /**
     * Méthode permettant de fournir un userProvider custom (voir config provider : access_provider)
     * @param string $identifier
     * @return UserInterface|null
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function loadUserByIdentifier($identifier): ?UserInterface
    {
        if($this->requestStack->getMainRequest()->headers->get(self::HTTP_X_SWITCH_USER) === $identifier)
            return $this->getEntityManager()->find(Access::class, $identifier);

        return $this->findAccessByUsernameAndAccessId($identifier, (int) $this->requestStack->getMainRequest()->headers->get(self::ACCESS_NAME_HEADER));
    }

    /**
     * Récupère un access grâce à son username et son ID
     * @param string $username
     * @param int $id
     * @return mixed
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    private function findAccessByUsernameAndAccessId(string $username, int $id): mixed
    {
        $entityManager = $this->getEntityManager();
        return $entityManager->createQuery(
            'SELECT a
                FROM App\Entity\Access\Access a
                INNER JOIN a.person p
                WHERE p.username = :query AND a.id = :id'
        )
            ->setParameter('query', $username)
            ->setParameter('id', $id)
            ->getOneOrNullResult();
    }

    /**
     * Retourne l'access administrateur de cette organisation
     *
     * @param Organization $organization
     * @return Access
     */
    public function findAdminAccess(Organization $organization): Access {
        return $this->findOneBy([
            'adminAccess' => 1,
            'organization' => $organization
        ]);
    }

    /**
     * Retourne tous les access qui appartiennent à une organisation qui est active dans un réseau
     *
     * @param Access $access
     * @return mixed
     * @throws \Exception
     */
    public function findAllValidAccesses(Access $access): mixed
    {
        $datetime = new \DateTime();
        $today = $datetime->format('Y-m-d');

        return $this->createQueryBuilder('access')
            ->innerJoin('access.organization', 'organization')
            ->innerJoin('organization.networkOrganizations', 'networkOrganizations')
            ->where('access.person = :person')
            ->andWhere('networkOrganizations.startDate <= :today')
            ->setParameter('person', $access->getPerson())
            ->setParameter('today', $today)
            ->getQuery()
            ->getResult()
      ;
    }

    /**
     * Retourne si oui ou non un access possède une fonction F active à une date précise
     * @param Access $access
     * @param FunctionEnum $function
     * @param DateTime $date
     * @return bool
     */
    public function hasGotFunctionAtThisDate(Access $access, FunctionEnum $function, \DateTime $date): bool
    {
        $this->_em->getFilters()->disable('date_time_filter');

        $qb = $this->createQueryBuilder('access');
        $qb
            ->innerJoin('access.organizationFunction', 'organization_function')
            ->innerJoin('organization_function.functionType', 'function_type')
            ->where('function_type.mission = :mission')
            ->andWhere('access.id = :id')
            ->setParameter('id', $access->getId())
            ->setParameter('mission', $function)
        ;
        DateConditions::addDateInPeriodCondition($qb, 'organization_function', $date->format('Y-m-d'));

        $result = count($qb->getQuery()->getResult()) > 0;

        $this->_em->getFilters()->enable('date_time_filter');

        return $result;
    }

    /**
     * Retourne tous les accesses de l'organization ayant la fonction donnée à la date donnée
     *
     * @param Organization $organization
     * @param string $function
     * @param DateTime|null $date
     * @return list<Access>
     */
    public function findByOrganizationAndMission(Organization $organization, string $function, \DateTime $date = null): array
    {
        if ($date === null)
            $date = new DateTime();

        $this->_em->getFilters()->disable('date_time_filter');

        $qb = $this->createQueryBuilder('access');
        $qb
            ->innerJoin('access.organizationFunction', 'organization_function')
            ->innerJoin('organization_function.functionType', 'function_type')
            ->where('function_type.mission = :mission')
            ->andWhere('access.organization = :id')
            ->setParameter('id', $organization->getId())
            ->setParameter('mission', $function)
        ;
        DateConditions::addDateInPeriodCondition($qb, 'organization_function', $date->format('Y-m-d'));
        $this->_em->getFilters()->enable('date_time_filter');

        return $qb->getQuery()->getResult();
    }

    /**
     * Get all the currently active accesses and return an array
     * of the form ['id' => $accessId, 'organization_id' => $organizationId, 'mission' => $mission]
     *
     * Used by App\Service\Dolibarr\DolibarrSync\DolibarrSyncService
     *
     * @return list<Access>
     */
    public function getAllActiveMembersAndMissions(): array
    {
        $qb = $this->createQueryBuilder('access');
        $qb
            ->select('access.id', 'organization.id as organization_id', 'function_type.mission')
            ->innerJoin('access.organization', 'organization')
            ->innerJoin('access.organizationFunction', 'organization_function')
            ->innerJoin('organization_function.functionType', 'function_type')
        ;
        DateConditions::addDateInPeriodCondition($qb, 'organization_function', date('Y-m-d'));

        return $qb->getQuery()->getArrayResult();
    }
}
