<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\Ecole;
use App\Entity\Etudiant;
use App\Entity\AnneeScolaire;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/** @extends ServiceEntityRepository<Etudiant> */
final class EtudiantRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Etudiant::class);
    }

    /**
     * @return list<Etudiant>
     *
     * $scope:
     * - current  : étudiants actifs de l’année en cours/sélectionnée
     * - archives : étudiants d’anciennes années ou désactivés
     * - all      : tous les étudiants, avec filtre année optionnel
     */
    public function findForEcole(
        Ecole $ecole,
        ?string $search = null,
        ?AnneeScolaire $annee = null,
        string $scope = 'current',
        ?AnneeScolaire $currentAnnee = null,
    ): array {
        $qb = $this->createQueryBuilder('etudiant')
            ->leftJoin('etudiant.anneeScolaire', 'annee')->addSelect('annee')
            ->leftJoin('etudiant.section', 'section')->addSelect('section')
            ->leftJoin('etudiant.niveau', 'niveau')->addSelect('niveau')
            ->leftJoin('etudiant.classe', 'classe')->addSelect('classe')
            ->andWhere('etudiant.ecole = :ecole')
            ->setParameter('ecole', $ecole)
            ->orderBy('etudiant.createdAt', 'DESC');

        if ($search) {
            $qb
                ->andWhere('LOWER(etudiant.nom) LIKE :search OR LOWER(etudiant.prenom) LIKE :search OR etudiant.telephonePrincipal LIKE :search OR etudiant.telephoneWhatsapp LIKE :search OR etudiant.matricule LIKE :search')
                ->setParameter('search', '%'.mb_strtolower(trim($search)).'%');
        }

        if ($scope === 'archives') {
            if ($annee) {
                $qb->andWhere('etudiant.anneeScolaire = :annee')->setParameter('annee', $annee);
            } elseif ($currentAnnee) {
                $qb
                    ->andWhere('etudiant.anneeScolaire IS NULL OR etudiant.anneeScolaire != :currentAnnee OR etudiant.actif = :inactive')
                    ->setParameter('currentAnnee', $currentAnnee)
                    ->setParameter('inactive', false);
            } else {
                $qb->andWhere('etudiant.actif = :inactive')->setParameter('inactive', false);
            }
        } elseif ($scope === 'all') {
            if ($annee) {
                $qb->andWhere('etudiant.anneeScolaire = :annee')->setParameter('annee', $annee);
            }
        } else {
            if ($annee) {
                $qb->andWhere('etudiant.anneeScolaire = :annee')->setParameter('annee', $annee);
            }
            $qb->andWhere('etudiant.actif = :active')->setParameter('active', true);
        }

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

    public function countForEcole(Ecole $ecole, ?AnneeScolaire $annee = null, bool $activeOnly = false): int
    {
        $qb = $this->createQueryBuilder('etudiant')
            ->select('COUNT(etudiant.id)')
            ->andWhere('etudiant.ecole = :ecole')
            ->setParameter('ecole', $ecole);

        if ($annee) {
            $qb->andWhere('etudiant.anneeScolaire = :annee')->setParameter('annee', $annee);
        }

        if ($activeOnly) {
            $qb->andWhere('etudiant.actif = :active')->setParameter('active', true);
        }

        return (int) $qb->getQuery()->getSingleScalarResult();
    }

    /** @return list<Etudiant> */
    public function searchOldCandidates(Ecole $ecole, string $query, int $limit = 8): array
    {
        $search = '%'.mb_strtolower(trim($query)).'%';

        return $this->createQueryBuilder('etudiant')
            ->andWhere('etudiant.ecole = :ecole')
            ->andWhere('etudiant.actif = :active')
            ->andWhere('LOWER(etudiant.nom) LIKE :search OR LOWER(etudiant.prenom) LIKE :search OR etudiant.matricule LIKE :search OR etudiant.telephonePrincipal LIKE :search OR etudiant.telephoneWhatsapp LIKE :search')
            ->setParameter('ecole', $ecole)
            ->setParameter('active', true)
            ->setParameter('search', $search)
            ->orderBy('etudiant.nom', 'ASC')
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }

    public function findOneMatchingContactForEcole(
        Ecole $ecole,
        ?string $nom,
        ?string $prenom,
        ?string $telephonePrincipal,
        ?string $telephoneWhatsapp,
    ): ?Etudiant {
        $phoneKeys = $this->phoneKeys($telephonePrincipal, $telephoneWhatsapp);
        if ($phoneKeys === []) {
            return null;
        }

        $expectedNames = array_filter([
            $this->normalizeName($nom),
            $this->normalizeName(trim(($prenom ?? '').' '.($nom ?? ''))),
        ]);

        $candidates = $this->createQueryBuilder('etudiant')
            ->andWhere('etudiant.ecole = :ecole')
            ->andWhere('etudiant.actif = :active')
            ->andWhere('etudiant.telephonePrincipal IS NOT NULL OR etudiant.telephoneWhatsapp IS NOT NULL')
            ->setParameter('ecole', $ecole)
            ->setParameter('active', true)
            ->getQuery()
            ->getResult();

        $matches = [];
        foreach ($candidates as $candidate) {
            if (!$candidate instanceof Etudiant || !$this->hasMatchingPhone($candidate, $phoneKeys)) {
                continue;
            }

            $candidateNames = array_filter([
                $this->normalizeName($candidate->getNom()),
                $this->normalizeName($candidate->getNomComplet()),
            ]);

            if ($expectedNames !== [] && $candidateNames !== [] && array_intersect($expectedNames, $candidateNames) === []) {
                continue;
            }

            $id = $candidate->getId();
            if ($id !== null) {
                $matches[$id] = $candidate;
            }
        }

        return count($matches) === 1 ? reset($matches) : null;
    }

    /** @return list<Etudiant> */
    public function findLatestForEcole(Ecole $ecole, int $limit = 5, ?AnneeScolaire $annee = null): array
    {
        $qb = $this->createQueryBuilder('etudiant')
            ->leftJoin('etudiant.classe', 'classe')->addSelect('classe')
            ->andWhere('etudiant.ecole = :ecole')
            ->andWhere('etudiant.actif = :active')
            ->setParameter('ecole', $ecole)
            ->setParameter('active', true)
            ->orderBy('etudiant.createdAt', 'DESC')
            ->setMaxResults($limit);

        if ($annee) {
            $qb->andWhere('etudiant.anneeScolaire = :annee')->setParameter('annee', $annee);
        }

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

    /** @return list<Etudiant> */
    public function findByClasseForEcole(Ecole $ecole, int $classeId): array
    {
        return $this->createQueryBuilder('etudiant')
            ->select('DISTINCT etudiant')
            ->leftJoin('etudiant.anneeScolaire', 'annee')->addSelect('annee')
            ->leftJoin('etudiant.section', 'section')->addSelect('section')
            ->leftJoin('etudiant.niveau', 'niveau')->addSelect('niveau')
            ->leftJoin('etudiant.classe', 'classe')->addSelect('classe')
            ->leftJoin('etudiant.parcours', 'parcours')->addSelect('parcours')
            ->leftJoin('parcours.classe', 'parcoursClasse')->addSelect('parcoursClasse')
            ->andWhere('etudiant.ecole = :ecole')
            ->andWhere('etudiant.actif = :active')
            ->andWhere('classe.id = :classeId OR parcoursClasse.id = :classeId')
            ->setParameter('ecole', $ecole)
            ->setParameter('active', true)
            ->setParameter('classeId', $classeId)
            ->orderBy('etudiant.nom', 'ASC')
            ->addOrderBy('etudiant.prenom', 'ASC')
            ->getQuery()
            ->getResult();
    }

    /** @return list<string> */
    private function phoneKeys(?string ...$phones): array
    {
        $keys = [];
        foreach ($phones as $phone) {
            $digits = $this->normalizePhone($phone);
            if ($digits === null) {
                continue;
            }

            $keys[] = $digits;
            if (str_starts_with($digits, '237') && strlen($digits) === 12) {
                $keys[] = substr($digits, 3);
            } elseif (strlen($digits) === 9) {
                $keys[] = '237'.$digits;
            }
        }

        return array_values(array_unique($keys));
    }

    private function hasMatchingPhone(Etudiant $etudiant, array $phoneKeys): bool
    {
        $studentKeys = $this->phoneKeys($etudiant->getTelephonePrincipal(), $etudiant->getTelephoneWhatsapp());

        return array_intersect($phoneKeys, $studentKeys) !== [];
    }

    private function normalizePhone(?string $phone): ?string
    {
        $digits = preg_replace('/\D+/', '', (string) $phone);

        return $digits !== '' ? $digits : null;
    }

    private function normalizeName(?string $name): ?string
    {
        $normalized = preg_replace('/\s+/', ' ', mb_strtolower(trim((string) $name)));

        return $normalized !== '' ? $normalized : null;
    }
}
