<?php

declare(strict_types=1);

namespace App\Service\Pdf;

use App\Entity\EcheancePaiement;
use App\Entity\Ecole;
use App\Entity\Etudiant;
use App\Entity\LignePaiement;
use App\Entity\Paiement;
use App\Entity\ParcoursEtudiant;
use App\Entity\RecuPaiement;
use App\Service\Finance\AmountToWords;
use App\Service\Finance\FinanceCalculator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

final class PaiementPdfGenerator
{
    private const PAGE_W = 220.0;
    private const PAGE_H = 110.0;

    public function __construct(
        #[Autowire('%kernel.project_dir%')]
        private readonly string $projectDir,
        private readonly PdfDocumentBranding $branding,
        private readonly FinanceCalculator $calculator,
        private readonly AmountToWords $amountToWords,
        private readonly EntityManagerInterface $em,
    ) {
    }

    public function generate(Paiement $paiement): RecuPaiement
    {
        $ecole = $paiement->getEcole();
        $etudiant = $paiement->getEtudiant();
        if (!$ecole instanceof Ecole || !$etudiant instanceof Etudiant) {
            throw new \LogicException('Impossible de générer un reçu sans école et étudiant.');
        }

        $numero = $paiement->getNumeroRecu() ?: sprintf('PAY-%06d', $paiement->getId() ?? 0);
        $relativePath = 'uploads/recus-paiement/recu-paiement-'.$this->safeFilename($numero).'.pdf';
        $absoluteDir = $this->projectDir.'/public/uploads/recus-paiement';
        $absolutePath = $this->projectDir.'/public/'.$relativePath;

        if (!is_dir($absoluteDir) && !mkdir($absoluteDir, 0775, true) && !is_dir($absoluteDir)) {
            throw new \RuntimeException('Impossible de créer le dossier des reçus de paiement.');
        }

        $generatedAt = new \DateTimeImmutable();
        $qrPayload = $this->buildQrPayload($paiement, $ecole, $etudiant);
        $summary = $this->calculator->getStudentSummary($etudiant, $paiement->getAnneeScolaire());
        $this->renderPdf($paiement, $ecole, $etudiant, $summary, $qrPayload, $absolutePath);

        $recu = $this->em->getRepository(RecuPaiement::class)->findOneBy(['paiement' => $paiement]);
        if (!$recu instanceof RecuPaiement) {
            $recu = (new RecuPaiement())
                ->setEcole($ecole)
                ->setPaiement($paiement)
                ->setNumero($numero);
            $this->em->persist($recu);
        }

        $recu
            ->setEcole($ecole)
            ->setNumero($numero)
            ->setPdfPath($relativePath)
            ->setQrPayload($qrPayload)
            ->setGeneratedAt($generatedAt);

        $this->em->flush();

        return $recu;
    }

    /** @param array{totalDu: float, totalPaye: float, reste: float, statut: string, echeances: list<EcheancePaiement>} $summary */
    private function renderPdf(Paiement $paiement, Ecole $ecole, Etudiant $etudiant, array $summary, string $qrPayload, string $absolutePath): void
    {
        $pdf = new SalsabilTcpdf('P', 'mm', [self::PAGE_W, self::PAGE_H], true, 'UTF-8', false);
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $pdf->SetCreator('SALSABIL Madrassas');
        $pdf->SetAuthor($ecole->getNom() ?: 'SALSABIL Madrassas');
        $pdf->SetTitle('Reçu de paiement '.$paiement->getNumeroRecu());
        $pdf->SetMargins(0, 0, 0);
        $pdf->SetAutoPageBreak(false, 0);
        $pdf->setCellPaddings(0, 0, 0, 0);
        $pdf->AddPage();

        $primary = $this->hexToRgb($ecole->getCouleurPrincipale() ?: '#073B63');
        $accent = $this->hexToRgb($ecole->getCouleurAccent() ?: '#BCD7EE');
        $light = [232, 243, 252];
        $ink = [8, 45, 76];

        $pdf->SetFillColor(244, 248, 251);
        $pdf->Rect(0, 0, self::PAGE_W, self::PAGE_H, 'F');
        $pdf->SetFillColor(196, 219, 239);
        $pdf->Rect(4, 4, self::PAGE_W - 8, self::PAGE_H - 8, 'F');
        $pdf->SetDrawColor($primary[0], $primary[1], $primary[2]);
        $pdf->SetLineWidth(0.35);
        $pdf->Rect(6, 6, self::PAGE_W - 12, self::PAGE_H - 12, 'D');

        $this->renderLeftPanel($pdf, $ecole, $etudiant, $summary, $primary, $light);
        $this->renderMainHeader($pdf, $ecole, $paiement, $primary, $light, $ink);
        $this->renderLinesTable($pdf, $paiement, $primary, $light, $ink);
        $this->renderPaymentFooter($pdf, $paiement, $ecole, $summary, $qrPayload, $primary, $accent, $ink);

        $pdf->Output($absolutePath, 'F');
    }

    /** @param array{totalDu: float, totalPaye: float, reste: float, statut: string, echeances: list<EcheancePaiement>} $summary @param array{0:int,1:int,2:int} $primary @param array{0:int,1:int,2:int} $light */
    private function renderLeftPanel(SalsabilTcpdf $pdf, Ecole $ecole, Etudiant $etudiant, array $summary, array $primary, array $light): void
    {
        $x = 8.0;
        $w = 46.0;
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, 8, $w, 34, 'F');
        $logo = $this->branding->publicImagePath($ecole->getLogo());
        if ($logo) {
            $pdf->Image($logo, $x + 5, 10, $w - 10, 24, '', '', '', true, 300, '', false, false, 0, true);
        } else {
            $pdf->SetTextColor($primary[0], $primary[1], $primary[2]);
            $pdf->SetFont('dejavusans', 'B', 10);
            $pdf->SetXY($x + 3, 19);
            $pdf->MultiCell($w - 6, 6, mb_strtoupper($ecole->getNom() ?: 'SALSABIL Madrassas'), 0, 'C');
        }

        $pdf->SetTextColor($primary[0], $primary[1], $primary[2]);
        $pdf->SetFont('dejavusans', 'B', 8.6);
        $pdf->SetXY($x, 44);
        $pdf->Cell($w, 5, 'APPRENANT', 0, 1, 'C');

        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, 51, $w, 26, 'F');
        $pdf->SetTextColor(20, 30, 42);
        $pdf->SetFont('dejavusans', 'B', 7.5);
        $pdf->SetXY($x + 2, 54);
        $pdf->Cell(13, 4, 'Code', 0, 0, 'L');
        $pdf->SetFont('dejavusansmono', 'B', 7.5);
        $pdf->Cell($w - 17, 4, $etudiant->getMatricule() ?: $etudiant->getMatriculeTemporaire() ?: '-', 0, 1, 'L');
        $pdf->SetFont('dejavusans', '', 7.0);
        $pdf->SetXY($x + 2, 61);
        $pdf->MultiCell($w - 4, 4, $this->short($etudiant->getNomComplet(), 52), 0, 'L');
        $pdf->SetXY($x + 2, 70);
        $pdf->Cell($w - 4, 4, 'Tél : '.($etudiant->getTelephonePrincipal() ?: $etudiant->getTelephoneWhatsapp() ?: '-'), 0, 0, 'L');

        $pdf->SetTextColor($primary[0], $primary[1], $primary[2]);
        $pdf->SetFont('dejavusans', 'B', 8.4);
        $pdf->SetXY($x, 80);
        $pdf->Cell($w, 5, 'ÉTAT FINANCIER', 0, 1, 'C');
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, 86, $w, 16, 'F');
        $pdf->SetTextColor(20, 30, 42);
        $pdf->SetFont('dejavusans', '', 7);
        $pdf->SetXY($x + 2, 88);
        $pdf->Cell(18, 4, 'Total payé', 0, 0, 'L');
        $pdf->SetFont('dejavusansmono', 'B', 7.5);
        $pdf->Cell($w - 22, 4, $this->money($summary['totalPaye']), 0, 1, 'R');
        $pdf->SetFont('dejavusans', '', 7);
        $pdf->SetX($x + 2);
        $pdf->Cell(18, 4, 'Reste', 0, 0, 'L');
        $pdf->SetFont('dejavusansmono', 'B', 7.5);
        $pdf->Cell($w - 22, 4, $this->money($summary['reste']), 0, 1, 'R');
    }

    /** @param array{0:int,1:int,2:int} $primary @param array{0:int,1:int,2:int} $light @param array{0:int,1:int,2:int} $ink */
    private function renderMainHeader(SalsabilTcpdf $pdf, Ecole $ecole, Paiement $paiement, array $primary, array $light, array $ink): void
    {
        $x = 61.0;
        $pdf->SetTextColor($ink[0], $ink[1], $ink[2]);
        $pdf->SetFont('dejavusans', 'B', 14.5);
        $pdf->SetXY($x, 9);
        $pdf->Cell(84, 7, 'REÇU DE PAIEMENT', 0, 1, 'L');
        $pdf->SetFont('dejavusans', 'B', 8.6);
        $pdf->SetX($x);
        $pdf->Cell(84, 5, mb_strtoupper($ecole->getNom() ?: 'MADRASSA'), 0, 1, 'L');

        $pdf->SetFillColor($light[0], $light[1], $light[2]);
        $pdf->Rect($x, 25, 78, 23, 'F');
        $pdf->SetTextColor($ink[0], $ink[1], $ink[2]);
        $pdf->SetFont('dejavusans', 'B', 7.2);
        $pdf->SetXY($x + 2, 27);
        $pdf->MultiCell(74, 4, $this->schoolContacts($ecole), 0, 'L');

        $boxX = 145.0;
        $pdf->SetFillColor($ink[0], $ink[1], $ink[2]);
        $pdf->Rect($boxX, 10, 67, 38, 'F');
        $rows = [
            ['N°', $paiement->getNumeroRecu() ?: '-'],
            ['Date', $paiement->getDatePaiement()?->format('d/m/Y') ?: '-'],
            ['Montant FCFA', $this->money($paiement->getMontantTotal())],
        ];
        $y = 14.5;
        foreach ($rows as $row) {
            $pdf->SetTextColor(255, 255, 255);
            $pdf->SetFont('dejavusans', 'B', 7.1);
            $pdf->SetXY($boxX + 4, $y + 1.2);
            $pdf->Cell(24, 5, $row[0], 0, 0, 'L');
            $pdf->SetFillColor(255, 255, 255);
            $pdf->Rect($boxX + 31, $y, 32, 7.6, 'F');
            $pdf->SetTextColor($row[0] === 'N°' ? 190 : 20, $row[0] === 'N°' ? 20 : 30, $row[0] === 'N°' ? 20 : 42);
            $pdf->SetFont('dejavusansmono', 'B', $row[0] === 'Montant FCFA' ? 8.3 : 7.2);
            $pdf->SetXY($boxX + 32, $y + 1.2);
            $pdf->Cell(30, 5, (string) $row[1], 0, 0, 'C');
            $y += 11.2;
        }
    }

    /** @param array{0:int,1:int,2:int} $primary @param array{0:int,1:int,2:int} $light @param array{0:int,1:int,2:int} $ink */
    private function renderLinesTable(SalsabilTcpdf $pdf, Paiement $paiement, array $primary, array $light, array $ink): void
    {
        $x = 61.0;
        $y = 51.0;
        $widths = [53.0, 29.0, 43.0, 26.0];
        $headers = ['TYPE DE FRAIS', 'PÉRIODE', 'PARCOURS / CLASSE', 'MONTANT'];

        $pdf->SetDrawColor($primary[0], $primary[1], $primary[2]);
        $pdf->SetLineWidth(0.18);
        $pdf->SetFillColor($light[0], $light[1], $light[2]);
        $pdf->SetTextColor($ink[0], $ink[1], $ink[2]);
        $pdf->SetFont('dejavusans', 'B', 6.8);
        $currentX = $x;
        foreach ($headers as $index => $header) {
            $pdf->Rect($currentX, $y, $widths[$index], 7.2, 'DF');
            $pdf->SetXY($currentX + 1, $y + 1.6);
            $pdf->Cell($widths[$index] - 2, 4, $header, 0, 0, $index === 3 ? 'R' : 'L');
            $currentX += $widths[$index];
        }

        $lines = array_values($paiement->getLignes()->toArray());
        $maxRows = 6;
        $rowY = $y + 7.2;
        $pdf->SetFont('dejavusans', '', 6.3);
        for ($i = 0; $i < $maxRows; ++$i) {
            $line = $lines[$i] ?? null;
            $currentX = $x;
            foreach ($widths as $index => $w) {
                $pdf->Rect($currentX, $rowY, $w, 6.0, 'D');
                $pdf->SetXY($currentX + 1, $rowY + 1.2);
                $text = $line instanceof LignePaiement ? $this->lineCell($line, $index) : '';
                $pdf->Cell($w - 2, 3.6, $this->short($text, $index === 0 ? 34 : 24), 0, 0, $index === 3 ? 'R' : 'L');
                $currentX += $w;
            }
            $rowY += 6.0;
        }

        if (count($lines) > $maxRows) {
            $pdf->SetTextColor(95, 110, 125);
            $pdf->SetFont('dejavusans', '', 5.6);
            $pdf->SetXY($x, $rowY + 0.7);
            $pdf->Cell(150, 3, '+ '.(count($lines) - $maxRows).' ligne(s) conservée(s) dans le détail du paiement.', 0, 0, 'L');
        }
    }

    /** @param array{totalDu: float, totalPaye: float, reste: float, statut: string, echeances: list<EcheancePaiement>} $summary @param array{0:int,1:int,2:int} $primary @param array{0:int,1:int,2:int} $accent @param array{0:int,1:int,2:int} $ink */
    private function renderPaymentFooter(SalsabilTcpdf $pdf, Paiement $paiement, Ecole $ecole, array $summary, string $qrPayload, array $primary, array $accent, array $ink): void
    {
        $x = 61.0;
        $y = 92.0;
        $pdf->SetTextColor($ink[0], $ink[1], $ink[2]);
        $pdf->SetFont('dejavusans', 'B', 6.8);
        $pdf->SetXY($x, $y - 7);
        $pdf->Cell(25, 4, 'MODE :', 0, 0, 'L');
        $pdf->SetFont('dejavusans', '', 6.8);
        $pdf->Cell(42, 4, $this->modeLabel($paiement->getModePaiement()), 0, 0, 'L');
        $pdf->SetFont('dejavusans', 'B', 6.8);
        $pdf->Cell(24, 4, 'RÉF. :', 0, 0, 'L');
        $pdf->SetFont('dejavusans', '', 6.8);
        $pdf->Cell(39, 4, $paiement->getReference() ?: '-', 0, 0, 'L');

        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, 117, 11, 'F');
        $pdf->SetTextColor($ink[0], $ink[1], $ink[2]);
        $pdf->SetFont('dejavusans', 'B', 9.2);
        $pdf->SetXY($x + 2, $y + 2);
        $pdf->Cell(42, 5, 'TOTAL PAYÉ :', 0, 0, 'L');
        $pdf->SetFont('dejavusansmono', 'B', 10.5);
        $pdf->Cell(45, 5, $this->money($paiement->getMontantTotal()), 0, 0, 'R');
        $pdf->SetFont('dejavusans', '', 6.2);
        $pdf->Cell(15, 5, 'FCFA', 0, 0, 'L');
        $pdf->SetFont('dejavuserif', 'I', 7.7);
        $pdf->SetXY($x + 2, $y + 7);
        $pdf->Cell(112, 3.5, $this->amountToWords->toFcfa($paiement->getMontantTotal()), 0, 0, 'C');

        $pdf->SetFont('dejavusans', '', 5.6);
        $pdf->SetTextColor(70, 82, 96);
        $pdf->SetXY($x, 104);
        $pdf->Cell(58, 3, 'Dû : '.$this->money($summary['totalDu']).' FCFA', 0, 0, 'L');
        $pdf->Cell(50, 3, 'Payé : '.$this->money($summary['totalPaye']).' FCFA', 0, 0, 'L');
        $pdf->Cell(44, 3, 'Reste : '.$this->money($summary['reste']).' FCFA', 0, 0, 'L');

        $pdf->SetFillColor(255, 255, 255);
        $pdf->SetDrawColor($primary[0], $primary[1], $primary[2]);
        $pdf->Rect(180, 85, 28, 17, 'DF');
        $this->branding->renderAdministrativeMarks($pdf, $ecole, 181, 86, 26, 15);
        $pdf->SetTextColor(70, 82, 96);
        $pdf->SetFont('dejavusans', '', 5.3);
        $pdf->SetXY(180, 102.5);
        $pdf->Cell(28, 3, 'Administration', 0, 0, 'C');

        $pdf->SetFillColor(255, 255, 255);
        $pdf->SetDrawColor($primary[0], $primary[1], $primary[2]);
        $pdf->Rect(203, 82, 13, 13, 'DF');
        $pdf->write2DBarcode($qrPayload, 'QRCODE,H', 204.2, 83.2, 10.6, 10.6, [
            'border' => false,
            'padding' => 0,
            'fgcolor' => [20, 30, 42],
            'bgcolor' => false,
        ], 'N');

        $pdf->SetTextColor($primary[0], $primary[1], $primary[2]);
        $pdf->SetFont('dejavusans', '', 5.2);
        $pdf->SetXY(150, 104);
        $pdf->Cell(66, 3, 'Généré via SALSABIL Madrassas', 0, 0, 'R');
    }

    private function lineCell(LignePaiement $line, int $index): string
    {
        return match ($index) {
            0 => $line->getTypeFrais()?->getLibelle() ?: $line->getLibelle() ?: '-',
            1 => $line->getEcheancePaiement()?->getPeriode() ?: $line->getEcheancePaiement()?->getDateEcheance()?->format('m/Y') ?: 'Libre',
            2 => $this->parcoursLabel($line->getParcoursEtudiant()),
            3 => $this->money($line->getMontant()),
            default => '-',
        };
    }

    private function parcoursLabel(?ParcoursEtudiant $parcours): string
    {
        if (!$parcours instanceof ParcoursEtudiant) {
            return '-';
        }

        $parts = array_filter([
            $parcours->getClasse()?->getNom(),
            $parcours->getNiveau()?->getNom(),
            $parcours->getSection()?->getNom(),
        ]);

        return $parts ? implode(' · ', $parts) : 'Parcours';
    }

    private function buildQrPayload(Paiement $paiement, Ecole $ecole, Etudiant $etudiant): string
    {
        return implode("\n", [
            'Reçu : '.($paiement->getNumeroRecu() ?: '-'),
            'École : '.($ecole->getNom() ?: '-'),
            'Étudiant : '.$etudiant->getNomComplet(),
            'Matricule : '.($etudiant->getMatricule() ?: $etudiant->getMatriculeTemporaire() ?: '-'),
            'Montant : '.$this->money($paiement->getMontantTotal()).' FCFA',
            'Date : '.($paiement->getDatePaiement()?->format('d/m/Y') ?: '-'),
        ]);
    }

    private function schoolContacts(Ecole $ecole): string
    {
        $lines = [];
        if ($ecole->getAdresse() || $ecole->getVille()) {
            $lines[] = trim(($ecole->getAdresse() ?: '').' '.($ecole->getVille() ?: ''));
        }
        $phones = array_filter([$ecole->getTelephone1(), $ecole->getTelephone2()]);
        if ($phones) {
            $lines[] = 'Tél : '.implode(' / ', $phones);
        }
        if ($ecole->getEmail()) {
            $lines[] = $ecole->getEmail();
        }

        return $lines ? implode("\n", $lines) : 'SALSABIL Madrassas';
    }

    private function modeLabel(?string $mode): string
    {
        return [
            Paiement::MODE_ESPECES => 'Espèces',
            Paiement::MODE_ORANGE_MONEY => 'Orange Money',
            Paiement::MODE_MTN_MOMO => 'MTN MoMo',
            Paiement::MODE_MOBILE_MONEY => 'Mobile Money',
            Paiement::MODE_VIREMENT => 'Virement',
            Paiement::MODE_AUTRE => 'Autre',
        ][$mode ?? ''] ?? '-';
    }

    private function money(string|int|float|null $amount): string
    {
        return number_format((float) ($amount ?? 0), 0, ',', ' ');
    }

    private function safeFilename(string $value): string
    {
        return trim((string) preg_replace('/[^A-Za-z0-9._-]+/', '-', $value), '-') ?: 'recu';
    }

    private function short(?string $value, int $length): string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return '-';
        }

        return mb_strlen($value) > $length ? mb_substr($value, 0, $length - 1).'…' : $value;
    }

    /** @return array{0:int,1:int,2:int} */
    private function hexToRgb(string $hex): array
    {
        $hex = ltrim($hex, '#');
        if (strlen($hex) !== 6) {
            return [7, 59, 99];
        }

        return [
            hexdec(substr($hex, 0, 2)),
            hexdec(substr($hex, 2, 2)),
            hexdec(substr($hex, 4, 2)),
        ];
    }
}
