<?php

declare(strict_types=1);

namespace App\Service\Finance;

use App\Entity\AnneeScolaire;
use App\Entity\EcheancePaiement;
use App\Entity\Etudiant;
use App\Entity\ParcoursEtudiant;
use App\Entity\RegleFrais;
use App\Entity\TypeFrais;
use App\Repository\EcheancePaiementRepository;
use App\Repository\RegleFraisRepository;
use Doctrine\ORM\EntityManagerInterface;

final class EcheanceGenerator
{
    public function __construct(
        private readonly RegleFraisRepository $regleFraisRepository,
        private readonly EcheancePaiementRepository $echeanceRepository,
        private readonly EntityManagerInterface $em,
    ) {
    }

    /** @return list<EcheancePaiement> */
    public function generateForStudent(Etudiant $etudiant, ?AnneeScolaire $annee = null): array
    {
        $created = [];
        foreach ($etudiant->getParcours() as $parcours) {
            if (!$parcours->isActif()) {
                continue;
            }
            if ($annee instanceof AnneeScolaire && $parcours->getAnneeScolaire() !== $annee) {
                continue;
            }

            $created = array_merge($created, $this->generateForParcoursInternal($parcours));
        }

        $this->em->flush();

        return $created;
    }

    /** @return list<EcheancePaiement> */
    public function generateForParcours(ParcoursEtudiant $parcours): array
    {
        $created = $this->generateForParcoursInternal($parcours);
        $this->em->flush();

        return $created;
    }

    /** @return list<EcheancePaiement> */
    public function generateFromRegle(ParcoursEtudiant $parcours, RegleFrais $regle): array
    {
        $created = $this->generateFromRegleInternal($parcours, $regle);
        $this->em->flush();

        return $created;
    }

    /** @return list<EcheancePaiement> */
    private function generateForParcoursInternal(ParcoursEtudiant $parcours): array
    {
        $created = [];
        foreach ($this->regleFraisRepository->findApplicableForParcours($parcours) as $regle) {
            $created = array_merge($created, $this->generateFromRegleInternal($parcours, $regle));
        }

        return $created;
    }

    /** @return list<EcheancePaiement> */
    private function generateFromRegleInternal(ParcoursEtudiant $parcours, RegleFrais $regle): array
    {
        $typeFrais = $regle->getTypeFrais();
        $etudiant = $parcours->getEtudiant();
        $ecole = $parcours->getEcole();
        if (!$typeFrais || !$etudiant || !$ecole) {
            throw new \LogicException('Impossible de générer une échéance sans école, étudiant et type de frais.');
        }

        $periodicite = $regle->getPeriodicite() ?: $typeFrais->getPeriodicite() ?: TypeFrais::PERIODICITE_UNIQUE;

        if ($periodicite === TypeFrais::PERIODICITE_MENSUELLE) {
            return $this->generateMonthly($parcours, $regle, $typeFrais);
        }

        return $this->createUniqueEcheance($parcours, $regle, $typeFrais);
    }

    /** @return list<EcheancePaiement> */
    private function generateMonthly(ParcoursEtudiant $parcours, RegleFrais $regle, TypeFrais $typeFrais): array
    {
        $annee = $regle->getAnneeScolaire() ?: $parcours->getAnneeScolaire();
        $baseYear = (int) (($annee?->getDateDebut()?->format('Y')) ?: (new \DateTimeImmutable())->format('Y'));
        $startMonth = $regle->getMoisDebut() ?: (int) (($annee?->getDateDebut()?->format('m')) ?: (new \DateTimeImmutable())->format('m'));
        $count = max(1, $regle->getNombreEcheances() ?: 1);
        $created = [];

        for ($offset = 0; $offset < $count; ++$offset) {
            $date = (new \DateTimeImmutable(sprintf('%04d-%02d-01', $baseYear, max(1, min(12, $startMonth)))))->modify(sprintf('+%d month', $offset));
            $periode = $date->format('Y-m');
            $label = sprintf('%s - %s', $typeFrais->getLibelle() ?: 'Mensualité', $periode);
            $echeance = $this->createIfMissing($parcours, $regle, $typeFrais, $label, $regle->getMontant(), $periode, $date);
            if ($echeance instanceof EcheancePaiement) {
                $created[] = $echeance;
            }
        }

        return $created;
    }

    /** @return list<EcheancePaiement> */
    private function createUniqueEcheance(ParcoursEtudiant $parcours, RegleFrais $regle, TypeFrais $typeFrais): array
    {
        $annee = $regle->getAnneeScolaire() ?: $parcours->getAnneeScolaire();
        $periode = $annee?->getLibelle();
        $date = $annee?->getDateDebut() ? \DateTimeImmutable::createFromInterface($annee->getDateDebut()) : $parcours->getDateDebut();
        $label = $typeFrais->getLibelle() ?: 'Frais';
        $echeance = $this->createIfMissing($parcours, $regle, $typeFrais, $label, $regle->getMontant(), $periode, $date);

        return $echeance instanceof EcheancePaiement ? [$echeance] : [];
    }

    private function createIfMissing(
        ParcoursEtudiant $parcours,
        RegleFrais $regle,
        TypeFrais $typeFrais,
        string $label,
        string $amount,
        ?string $periode,
        ?\DateTimeInterface $date,
    ): ?EcheancePaiement {
        $ecole = $parcours->getEcole();
        $etudiant = $parcours->getEtudiant();
        if (!$ecole || !$etudiant) {
            throw new \LogicException('Impossible de générer une échéance sans école ou étudiant.');
        }

        $existing = $this->echeanceRepository->findExisting($ecole, $etudiant, $parcours, $typeFrais, $periode);
        if ($existing instanceof EcheancePaiement) {
            return null;
        }

        $montant = (float) $amount;
        $echeance = (new EcheancePaiement())
            ->setEcole($ecole)
            ->setEtudiant($etudiant)
            ->setParcoursEtudiant($parcours)
            ->setAnneeScolaire($regle->getAnneeScolaire() ?: $parcours->getAnneeScolaire())
            ->setTypeFrais($typeFrais)
            ->setRegleFrais($regle)
            ->setLibelle($label)
            ->setMontantDu($montant)
            ->setMontantRemise(0)
            ->setMontantNet($montant)
            ->setMontantPaye(0)
            ->setDateEcheance($date)
            ->setPeriode($periode)
            ->setStatut(EcheancePaiement::STATUT_NON_PAYE)
            ->setActif(true);

        $this->em->persist($echeance);

        return $echeance;
    }
}
