<?php

declare(strict_types=1);

namespace App\Controller\School;

use App\Service\School\SchoolContext;
use App\Entity\AnneeScolaire;
use App\Entity\EcheancePaiement;
use App\Entity\Ecole;
use App\Entity\Etudiant;
use App\Entity\LignePaiement;
use App\Entity\MouvementCaisse;
use App\Entity\Paiement;
use App\Entity\RecuPaiement;
use App\Form\Finance\PaiementType;
use App\Repository\AnneeScolaireRepository;
use App\Repository\EcheancePaiementRepository;
use App\Repository\MouvementCaisseRepository;
use App\Repository\PaiementRepository;
use App\Repository\RecuPaiementRepository;
use App\Service\Finance\CaisseService;
use App\Service\Finance\FinanceCalculator;
use App\Service\Finance\RecuNumberGenerator;
use App\Service\Pdf\PaiementPdfGenerator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_ECOLE_ADMIN')]
final class PaiementController extends AbstractController
{
    public function __construct(private readonly SchoolContext $schoolContext)
    {
    }

    #[Route('/school/paiements', name: 'school_paiement_index', methods: ['GET'])]
    public function index(Request $request, PaiementRepository $repository, AnneeScolaireRepository $anneeRepository): Response
    {
        $ecole = $this->schoolContext->getEcole();
        $filters = $this->buildFilters($request, $anneeRepository, $ecole);

        return $this->render('school/paiement/index.html.twig', [
            'ecole' => $ecole,
            'currentAnnee' => $anneeRepository->findCurrentForEcole($ecole),
            'paiements' => $repository->searchForEcole($ecole, $filters),
            'annees' => $anneeRepository->findBy(['ecole' => $ecole], ['dateDebut' => 'DESC', 'id' => 'DESC']),
            'filters' => $filters,
            'stats' => [
                'totalEncaisse' => $repository->sumValidatedForEcole($ecole),
                'nombrePaiements' => $repository->countForEcole($ecole),
                'paiementsMois' => $repository->sumValidatedForCurrentMonth($ecole),
                'annules' => $repository->countForEcole($ecole, Paiement::STATUT_ANNULE),
            ],
        ]);
    }

    #[Route('/school/paiements/new', name: 'school_paiement_new', methods: ['GET', 'POST'])]
    public function new(
        Request $request,
        AnneeScolaireRepository $anneeRepository,
        EcheancePaiementRepository $echeanceRepository,
        FinanceCalculator $calculator,
        CaisseService $caisseService,
        RecuNumberGenerator $numberGenerator,
        EntityManagerInterface $em,
    ): Response {
        return $this->handlePaymentForm($request, null, $anneeRepository, $echeanceRepository, $calculator, $caisseService, $numberGenerator, $em);
    }

    #[Route('/school/etudiants/{id}/paiements/new', name: 'school_etudiant_paiement_new', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
    public function newForStudent(
        Request $request,
        Etudiant $etudiant,
        AnneeScolaireRepository $anneeRepository,
        EcheancePaiementRepository $echeanceRepository,
        FinanceCalculator $calculator,
        CaisseService $caisseService,
        RecuNumberGenerator $numberGenerator,
        EntityManagerInterface $em,
    ): Response {
        $this->schoolContext->assertSameSchool($etudiant->getEcole());

        return $this->handlePaymentForm($request, $etudiant, $anneeRepository, $echeanceRepository, $calculator, $caisseService, $numberGenerator, $em);
    }

    #[Route('/school/paiements/{id}', name: 'school_paiement_show', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function show(Paiement $paiement, AnneeScolaireRepository $anneeRepository, MouvementCaisseRepository $mouvementRepository, RecuPaiementRepository $recuRepository): Response
    {
        $this->schoolContext->assertSameSchool($paiement->getEcole());
        $ecole = $this->schoolContext->getEcole();

        return $this->render('school/paiement/show.html.twig', [
            'ecole' => $ecole,
            'currentAnnee' => $anneeRepository->findCurrentForEcole($ecole),
            'paiement' => $paiement,
            'mouvement' => $mouvementRepository->findOneBy(['paiement' => $paiement, 'sourceType' => MouvementCaisse::SOURCE_PAIEMENT]),
            'recu' => $recuRepository->findOneBy(['paiement' => $paiement]),
        ]);
    }

    #[Route('/school/paiements/{id}/generer-recu', name: 'school_paiement_generate_receipt', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function generateReceipt(Request $request, Paiement $paiement, PaiementPdfGenerator $generator): Response
    {
        $this->schoolContext->assertSameSchool($paiement->getEcole());

        if (!$this->isCsrfTokenValid('generer_recu_paiement_'.$paiement->getId(), (string) $request->request->get('_token'))) {
            $this->addFlash('danger', 'Jeton de sécurité invalide.');

            return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
        }

        $generator->generate($paiement);
        $this->addFlash('success', 'Le reçu PDF a été généré.');

        return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
    }

    #[Route('/school/paiements/{id}/recu', name: 'school_paiement_receipt', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function receipt(Paiement $paiement, RecuPaiementRepository $recuRepository, PaiementPdfGenerator $generator): Response
    {
        $this->schoolContext->assertSameSchool($paiement->getEcole());

        $recu = $recuRepository->findOneBy(['paiement' => $paiement]) ?? $generator->generate($paiement);
        $path = $recu->getPdfPath();
        $absolutePath = (string) $this->getParameter('kernel.project_dir').'/public/'.ltrim((string) $path, '/');
        if (!$path || !is_file($absolutePath)) {
            $recu = $generator->generate($paiement);
            $absolutePath = (string) $this->getParameter('kernel.project_dir').'/public/'.ltrim((string) $recu->getPdfPath(), '/');
        }

        $response = new BinaryFileResponse($absolutePath);
        $response->setContentDisposition(
            ResponseHeaderBag::DISPOSITION_INLINE,
            'recu-paiement-'.($paiement->getNumeroRecu() ?: $paiement->getId()).'.pdf'
        );

        return $response;
    }

    #[Route('/school/paiements/{id}/annuler', name: 'school_paiement_cancel', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function cancel(
        Request $request,
        Paiement $paiement,
        FinanceCalculator $calculator,
        MouvementCaisseRepository $mouvementRepository,
        EntityManagerInterface $em,
    ): Response {
        $this->schoolContext->assertSameSchool($paiement->getEcole());

        if (!$this->isCsrfTokenValid('annuler_paiement_'.$paiement->getId(), (string) $request->request->get('_token'))) {
            $this->addFlash('danger', 'Jeton de sécurité invalide.');

            return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
        }

        if ($paiement->getStatut() === Paiement::STATUT_ANNULE) {
            $this->addFlash('warning', 'Ce paiement est déjà annulé.');

            return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
        }

        $touchedEcheances = [];
        foreach ($paiement->getLignes() as $ligne) {
            if ($ligne->getEcheancePaiement() instanceof EcheancePaiement) {
                $touchedEcheances[$ligne->getEcheancePaiement()->getId()] = $ligne->getEcheancePaiement();
            }
        }

        $paiement->setStatut(Paiement::STATUT_ANNULE);
        $mouvement = $mouvementRepository->findOneBy(['paiement' => $paiement, 'sourceType' => MouvementCaisse::SOURCE_PAIEMENT]);
        if ($mouvement instanceof MouvementCaisse) {
            $mouvement->setObservation(trim(($mouvement->getObservation() ? $mouvement->getObservation()."\n" : '').'Paiement annulé administrativement.'));
        }
        $em->flush();

        foreach ($touchedEcheances as $echeance) {
            $calculator->updateEcheanceStatus($echeance);
        }
        $em->flush();

        $this->addFlash('success', 'Le paiement a été annulé. La trace de caisse est conservée avec une observation.');

        return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
    }

    #[Route('/school/paiements/{id}/delete', name: 'school_paiement_delete', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function delete(
        Request $request,
        Paiement $paiement,
        FinanceCalculator $calculator,
        MouvementCaisseRepository $mouvementRepository,
        RecuPaiementRepository $recuRepository,
        EntityManagerInterface $em,
    ): Response {
        $this->schoolContext->assertSameSchool($paiement->getEcole());

        if (!$this->isCsrfTokenValid('delete_paiement_'.$paiement->getId(), (string) $request->request->get('_token'))) {
            $this->addFlash('danger', 'Jeton de sécurité invalide.');

            return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
        }

        $touchedEcheances = [];
        foreach ($paiement->getLignes() as $ligne) {
            if ($ligne->getEcheancePaiement() instanceof EcheancePaiement) {
                $touchedEcheances[$ligne->getEcheancePaiement()->getId()] = $ligne->getEcheancePaiement();
            }
            $em->remove($ligne);
        }

        foreach ($mouvementRepository->findBy(['paiement' => $paiement]) as $mouvement) {
            $em->remove($mouvement);
        }

        $recu = $recuRepository->findOneBy(['paiement' => $paiement]);
        if ($recu instanceof RecuPaiement) {
            $this->deleteStoredReceipt($recu->getPdfPath());
            $em->remove($recu);
        }

        $em->remove($paiement);
        $em->flush();

        foreach ($touchedEcheances as $echeance) {
            $calculator->updateEcheanceStatus($echeance);
        }
        $em->flush();

        $this->addFlash('success', 'Le paiement a été supprimé définitivement, sans trace de caisse ni reçu conservé.');

        return $this->redirectToRoute('school_paiement_index');
    }

    private function handlePaymentForm(
        Request $request,
        ?Etudiant $lockedStudent,
        AnneeScolaireRepository $anneeRepository,
        EcheancePaiementRepository $echeanceRepository,
        FinanceCalculator $calculator,
        CaisseService $caisseService,
        RecuNumberGenerator $numberGenerator,
        EntityManagerInterface $em,
    ): Response {
        $ecole = $this->schoolContext->getEcole();
        $selectedAnnee = $this->selectedPaymentYear($request, $anneeRepository, $ecole, $lockedStudent);
        $paiement = (new Paiement())
            ->setEcole($ecole)
            ->setDatePaiement(new \DateTimeImmutable())
            ->setStatut(Paiement::STATUT_VALIDE)
            ->setAnneeScolaire($selectedAnnee);

        if ($lockedStudent instanceof Etudiant) {
            $paiement->setEtudiant($lockedStudent);
        }

        $form = $this->createForm(PaiementType::class, $paiement, [
            'ecole' => $ecole,
            'locked_student' => $lockedStudent,
            'current_annee' => $selectedAnnee,
        ]);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            if ($lockedStudent instanceof Etudiant) {
                $paiement->setEtudiant($lockedStudent);
            }

            $etudiant = $paiement->getEtudiant();
            if (!$etudiant instanceof Etudiant || $etudiant->getEcole()?->getId() !== $ecole->getId()) {
                throw $this->createAccessDeniedException('Étudiant non disponible pour cette madrassa.');
            }

            $montant = (float) $paiement->getMontantTotal();
            if ($montant <= 0) {
                $this->addFlash('danger', 'Le montant reçu doit être supérieur à 0.');
            } else {
                $paiement->setNumeroRecu($numberGenerator->generate($ecole, $paiement->getAnneeScolaire()));
                $em->persist($paiement);
                $touchedEcheances = $this->createPaymentLines($paiement, $echeanceRepository, $em);
                $em->flush();

                foreach ($touchedEcheances as $echeance) {
                    $calculator->updateEcheanceStatus($echeance);
                }
                $em->flush();

                $caisseService->createEntryFromPaiement($paiement);
                $this->addFlash('success', 'Le paiement a été enregistré et réparti sur les échéances ouvertes.');

                return $this->redirectToRoute('school_paiement_show', ['id' => $paiement->getId()]);
            }
        }

        return $this->render('school/paiement/form.html.twig', [
            'ecole' => $ecole,
            'currentAnnee' => $selectedAnnee,
            'form' => $form,
            'lockedStudent' => $lockedStudent,
            'title' => $lockedStudent instanceof Etudiant ? 'Paiement de '.$lockedStudent->getNomComplet() : 'Nouveau paiement',
            'paymentYear' => $selectedAnnee,
        ]);
    }

    /** @return array<int, EcheancePaiement> */
    private function createPaymentLines(Paiement $paiement, EcheancePaiementRepository $echeanceRepository, EntityManagerInterface $em): array
    {
        $remaining = (float) $paiement->getMontantTotal();
        $touchedEcheances = [];
        $createdLine = false;
        $etudiant = $paiement->getEtudiant();

        if ($etudiant instanceof Etudiant) {
            $echeances = $echeanceRepository->findActiveByStudent($etudiant, $paiement->getAnneeScolaire());
            usort($echeances, static function (EcheancePaiement $a, EcheancePaiement $b): int {
                $dateA = $a->getDateEcheance()?->getTimestamp() ?? PHP_INT_MAX;
                $dateB = $b->getDateEcheance()?->getTimestamp() ?? PHP_INT_MAX;

                return $dateA <=> $dateB ?: (($a->getId() ?? 0) <=> ($b->getId() ?? 0));
            });

            foreach ($echeances as $echeance) {
                if ($remaining <= 0) {
                    break;
                }
                if (in_array($echeance->getStatut(), [EcheancePaiement::STATUT_PAYE, EcheancePaiement::STATUT_ANNULE], true)) {
                    continue;
                }

                $reste = max(0.0, (float) $echeance->getMontantNet() - (float) $echeance->getMontantPaye());
                if ($reste <= 0) {
                    continue;
                }

                $amount = min($remaining, $reste);
                $ligne = (new LignePaiement())
                    ->setEcole($paiement->getEcole())
                    ->setEcheancePaiement($echeance)
                    ->setParcoursEtudiant($echeance->getParcoursEtudiant())
                    ->setTypeFrais($echeance->getTypeFrais())
                    ->setLibelle($echeance->getLibelle() ?: ($echeance->getTypeFrais()?->getLibelle() ?? 'Échéance'))
                    ->setMontant(number_format($amount, 2, '.', ''));
                $paiement->addLigne($ligne);
                $em->persist($ligne);

                $remaining = round($remaining - $amount, 2);
                $touchedEcheances[$echeance->getId()] = $echeance;
                $createdLine = true;
            }
        }

        if ($remaining > 0) {
            $ligne = (new LignePaiement())
                ->setEcole($paiement->getEcole())
                ->setLibelle($createdLine ? 'Avance sur frais à venir' : 'Paiement libre')
                ->setMontant(number_format($remaining, 2, '.', ''));
            $paiement->addLigne($ligne);
            $em->persist($ligne);
        }

        return $touchedEcheances;
    }

    private function buildFilters(Request $request, AnneeScolaireRepository $anneeRepository, Ecole $ecole): array
    {
        $filters = [
            'q' => trim((string) $request->query->get('q', '')),
            'statut' => trim((string) $request->query->get('statut', '')),
            'modePaiement' => trim((string) $request->query->get('modePaiement', '')),
            'anneeScolaire' => null,
            'anneeId' => (int) $request->query->get('annee', 0),
            'dateFromRaw' => trim((string) $request->query->get('dateFrom', '')),
            'dateToRaw' => trim((string) $request->query->get('dateTo', '')),
            'dateFrom' => null,
            'dateTo' => null,
        ];

        if ($filters['anneeId'] > 0) {
            $annee = $anneeRepository->find($filters['anneeId']);
            if ($annee && $annee->getEcole()?->getId() === $ecole->getId()) {
                $filters['anneeScolaire'] = $annee;
            }
        }

        try {
            if ($filters['dateFromRaw'] !== '') {
                $filters['dateFrom'] = new \DateTimeImmutable($filters['dateFromRaw']);
            }
            if ($filters['dateToRaw'] !== '') {
                $filters['dateTo'] = new \DateTimeImmutable($filters['dateToRaw']);
            }
        } catch (\Exception) {
            $filters['dateFrom'] = null;
            $filters['dateTo'] = null;
        }

        return $filters;
    }

    private function selectedPaymentYear(Request $request, AnneeScolaireRepository $anneeRepository, Ecole $ecole, ?Etudiant $lockedStudent): ?AnneeScolaire
    {
        $requestedId = $request->query->getInt('annee');
        if ($requestedId > 0) {
            $annee = $anneeRepository->find($requestedId);
            if ($annee && $annee->getEcole()?->getId() === $ecole->getId()) {
                return $annee;
            }
        }

        $currentAnnee = $anneeRepository->findCurrentForEcole($ecole);
        if ($currentAnnee instanceof AnneeScolaire) {
            return $currentAnnee;
        }

        if ($lockedStudent instanceof Etudiant && $lockedStudent->getAnneeScolaire()?->getEcole()?->getId() === $ecole->getId()) {
            return $lockedStudent->getAnneeScolaire();
        }

        return null;
    }

    private function deleteStoredReceipt(?string $path): void
    {
        if (!$path) {
            return;
        }

        $normalizedPath = ltrim($path, '/');
        if (!str_starts_with($normalizedPath, 'uploads/recus-paiement/')) {
            return;
        }

        $absolutePath = rtrim((string) $this->getParameter('kernel.project_dir'), '/').'/public/'.$normalizedPath;
        if (is_file($absolutePath)) {
            @unlink($absolutePath);
        }
    }

}
