<?php

declare(strict_types=1);

namespace App\Service\Finance;

use App\Entity\AnneeScolaire;
use App\Entity\Depense;
use App\Entity\Ecole;
use App\Entity\MouvementCaisse;
use App\Entity\Paiement;
use App\Repository\MouvementCaisseRepository;
use Doctrine\ORM\EntityManagerInterface;

final class CaisseService
{
    public function __construct(
        private readonly MouvementCaisseRepository $mouvementRepository,
        private readonly EntityManagerInterface $em,
    ) {
    }

    public function createEntryFromPaiement(Paiement $paiement): MouvementCaisse
    {
        if ($paiement->getStatut() !== Paiement::STATUT_VALIDE) {
            throw new \LogicException('Seul un paiement valide peut créer une entrée de caisse.');
        }

        $existing = $this->mouvementRepository->findOneBy(['paiement' => $paiement, 'sourceType' => MouvementCaisse::SOURCE_PAIEMENT]);
        if ($existing instanceof MouvementCaisse) {
            return $existing;
        }

        $ecole = $paiement->getEcole();
        if (!$ecole) {
            throw new \LogicException('Impossible de créer une entrée de caisse sans école.');
        }

        $mouvement = (new MouvementCaisse())
            ->setEcole($ecole)
            ->setAnneeScolaire($paiement->getAnneeScolaire())
            ->setType(MouvementCaisse::TYPE_ENTREE)
            ->setSourceType(MouvementCaisse::SOURCE_PAIEMENT)
            ->setPaiement($paiement)
            ->setMontant($paiement->getMontantTotal() ?: 0)
            ->setDateMouvement($paiement->getDatePaiement() ?: new \DateTimeImmutable())
            ->setLibelle('Paiement '.$paiement->getNumeroRecu());

        $this->em->persist($mouvement);
        $this->em->flush();

        return $mouvement;
    }

    public function createExitFromDepense(Depense $depense): MouvementCaisse
    {
        if ($depense->getStatut() !== Depense::STATUT_VALIDEE) {
            throw new \LogicException('Seule une dépense validée peut créer une sortie de caisse.');
        }

        $existing = $this->mouvementRepository->findOneBy(['depense' => $depense, 'sourceType' => MouvementCaisse::SOURCE_DEPENSE]);
        if ($existing instanceof MouvementCaisse) {
            return $existing;
        }

        $ecole = $depense->getEcole();
        if (!$ecole) {
            throw new \LogicException('Impossible de créer une sortie de caisse sans école.');
        }

        $mouvement = (new MouvementCaisse())
            ->setEcole($ecole)
            ->setAnneeScolaire($depense->getAnneeScolaire())
            ->setType(MouvementCaisse::TYPE_SORTIE)
            ->setSourceType(MouvementCaisse::SOURCE_DEPENSE)
            ->setDepense($depense)
            ->setMontant($depense->getMontant() ?: 0)
            ->setDateMouvement($depense->getDateDepense() ?: new \DateTimeImmutable())
            ->setLibelle($depense->getDescription() ?: 'Dépense');

        $this->em->persist($mouvement);
        $this->em->flush();

        return $mouvement;
    }

    public function createCancellationEntryFromDepense(Depense $depense): MouvementCaisse
    {
        $existing = $this->mouvementRepository->findOneBy([
            'depense' => $depense,
            'sourceType' => MouvementCaisse::SOURCE_AJUSTEMENT,
            'type' => MouvementCaisse::TYPE_ENTREE,
        ]);
        if ($existing instanceof MouvementCaisse) {
            return $existing;
        }

        $ecole = $depense->getEcole();
        if (!$ecole) {
            throw new \LogicException('Impossible de créer une annulation de dépense sans école.');
        }

        $mouvement = (new MouvementCaisse())
            ->setEcole($ecole)
            ->setAnneeScolaire($depense->getAnneeScolaire())
            ->setType(MouvementCaisse::TYPE_ENTREE)
            ->setSourceType(MouvementCaisse::SOURCE_AJUSTEMENT)
            ->setDepense($depense)
            ->setMontant($depense->getMontant() ?: 0)
            ->setDateMouvement(new \DateTimeImmutable())
            ->setLibelle('Annulation dépense '.$depense->getId())
            ->setObservation('Mouvement inverse créé automatiquement lors de l’annulation de la dépense.');

        $this->em->persist($mouvement);
        $this->em->flush();

        return $mouvement;
    }

    public function getSolde(Ecole $ecole, ?AnneeScolaire $annee = null): float
    {
        return $this->mouvementRepository->sumByEcoleAndType($ecole, MouvementCaisse::TYPE_ENTREE, $annee)
            - $this->mouvementRepository->sumByEcoleAndType($ecole, MouvementCaisse::TYPE_SORTIE, $annee);
    }
}
