<?php

namespace App\Controller\Admin;

use App\Entity\AdvertLine;
use App\Entity\Customer;
use App\Entity\Devis;
use App\Entity\DevisLine;
use App\Entity\DocusealEvent;
use App\Service\AdvertService;
use App\Service\DevisService;
use App\Service\DocuSealService;
use App\Service\MailerService;
use App\Service\MeilisearchService;
use App\Service\OrderNumberService;
use App\Service\Pdf\DevisPdf;
use App\Service\TarificationService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Twig\Environment;

#[Route('/admin/devis', name: 'app_admin_devis_')]
#[IsGranted('ROLE_EMPLOYE')]
class DevisController extends AbstractController
{
    private const MSG_NOT_FOUND = 'Devis introuvable';

    public function __construct(
        private EntityManagerInterface $em,
        private OrderNumberService $orderNumberService,
        private DevisService $devisService,
        private MeilisearchService $meilisearch,
    ) {
    }

    #[Route('/services/{customerId}/{type}', name: 'services', requirements: ['customerId' => '\d+'], methods: ['GET'])]
    public function services(int $customerId, string $type): Response
    {
        $customer = $this->em->getRepository(Customer::class)->find($customerId);
        if (null === $customer) {
            return $this->json([]);
        }

        $items = match ($type) {
            'ndd' => array_map(
                fn ($d) => ['id' => $d->getId(), 'label' => $d->getFqdn()],
                $this->em->getRepository(\App\Entity\Domain::class)->findBy(['customer' => $customer])
            ),
            'website' => array_map(
                fn ($w) => ['id' => $w->getId(), 'label' => $w->getName().' ('.$w->getType().')'],
                $this->em->getRepository(\App\Entity\Website::class)->findBy(['customer' => $customer])
            ),
            'esymail' => array_map(
                function ($domain) {
                    $emails = $this->em->getRepository(\App\Entity\DomainEmail::class)->findBy(['domain' => $domain]);

                    return array_map(fn ($e) => ['id' => $e->getId(), 'label' => $e->getFullEmail()], $emails);
                },
                $this->em->getRepository(\App\Entity\Domain::class)->findBy(['customer' => $customer])
            ),
            default => [],
        };

        // Flatten esymail (array of arrays)
        if ('esymail' === $type) {
            $items = array_merge(...$items);
        }

        return $this->json($items);
    }

    #[Route('/search/{customerId}', name: 'search', requirements: ['customerId' => '\d+'], methods: ['GET'])]
    public function search(int $customerId, Request $request): Response
    {
        $query = trim($request->query->getString('q'));
        if ('' === $query) {
            return $this->json([]);
        }

        $results = $this->meilisearch->searchDevis($query, 20, $customerId);

        return $this->json($results);
    }

    #[Route('/create/{customerId}', name: 'create', requirements: ['customerId' => '\d+'])]
    /** @codeCoverageIgnore */
    public function create(int $customerId, Request $request): Response
    {
        $customer = $this->em->getRepository(Customer::class)->find($customerId);
        if (null === $customer) {
            throw $this->createNotFoundException('Client introuvable');
        }

        if ($request->isMethod('POST')) {
            return $this->handleSave($customer, $request);
        }

        return $this->render('admin/devis/create.html.twig', [
            'customer' => $customer,
            'customerId' => $customerId,
            'numOrder' => $this->orderNumberService->preview(),
            'today' => new \DateTimeImmutable(),
            'quickPrices' => TarificationService::getDefaultTypes(),
        ]);
    }

    #[Route('/{id}/edit', name: 'edit', requirements: ['id' => '\d+'])]
    /** @codeCoverageIgnore */
    public function edit(int $id, Request $request): Response
    {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        if (Devis::STATE_CANCEL === $devis->getState()) {
            $this->addFlash('error', 'Impossible de modifier un devis annule.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $devis->getCustomer()?->getId() ?? 0,
                'tab' => 'devis',
            ]);
        }

        $customer = $devis->getCustomer();
        if (null === $customer) {
            throw $this->createNotFoundException('Client introuvable');
        }

        if ($request->isMethod('POST')) {
            return $this->handleSave($customer, $request, $devis);
        }

        return $this->render('admin/devis/create.html.twig', [
            'customer' => $customer,
            'customerId' => $customer->getId(),
            'numOrder' => $devis->getOrderNumber()->getNumOrder(),
            'today' => $devis->getCreatedAt(),
            'quickPrices' => TarificationService::getDefaultTypes(),
            'devis' => $devis,
            'isEdit' => true,
        ]);
    }

    /** @codeCoverageIgnore */
    private function handleSave(Customer $customer, Request $request, ?Devis $devis = null): Response
    {
        $isEdit = null !== $devis;

        /** @var array<int, array{title?: string, description?: string, priceHt?: string, pos?: string|int, type?: string, serviceId?: string|int}> $lines */
        $lines = $request->request->all('lines');

        if ([] === $lines) {
            $this->addFlash('error', 'Le devis doit contenir au moins une ligne.');

            return $isEdit
                ? $this->redirectToRoute('app_admin_devis_edit', ['id' => $devis->getId()])
                : $this->redirectToRoute('app_admin_devis_create', ['customerId' => $customer->getId()]);
        }

        if (!$isEdit) {
            // Creation du devis (OrderNumber genere ou reutilise)
            $devis = $this->devisService->create();
            $devis->setCustomer($customer);
            /** @var \App\Entity\User|null $currentUser */
            $currentUser = $this->getUser();
            $devis->setSubmitterSiteconseilId($currentUser?->getId());
        } else {
            // Edition : supprimer les lignes existantes (orphanRemoval gere la suppression DB)
            foreach ($devis->getLines()->toArray() as $oldLine) {
                $devis->removeLine($oldLine);
            }
        }

        // Calcul total HT + tri par pos front
        $totalHt = 0.0;
        uasort($lines, static fn ($a, $b) => ((int) ($a['pos'] ?? 0)) <=> ((int) ($b['pos'] ?? 0)));

        $pos = 0;
        foreach ($lines as $data) {
            $title = trim((string) ($data['title'] ?? ''));
            if ('' === $title) {
                continue;
            }

            $priceHt = (float) str_replace(',', '.', (string) ($data['priceHt'] ?? '0'));
            $line = $this->createDevisLine($devis, $data, $title, $priceHt, $pos);
            $this->em->persist($line);
            $devis->addLine($line);

            $totalHt += $priceHt;
            ++$pos;
        }

        if (0 === $pos) {
            $this->addFlash('error', 'Aucune ligne valide.');

            return $isEdit
                ? $this->redirectToRoute('app_admin_devis_edit', ['id' => $devis->getId()])
                : $this->redirectToRoute('app_admin_devis_create', ['customerId' => $customer->getId()]);
        }

        $totals = $this->devisService->computeTotals(number_format($totalHt, 2, '.', ''));

        $devis->setTotalHt($totals['totalHt']);
        $devis->setTotalTva($totals['totalTva']);
        $devis->setTotalTtc($totals['totalTtc']);

        $this->em->flush();

        $this->meilisearch->indexDevis($devis);

        $this->addFlash('success', 'Devis '.$devis->getOrderNumber()->getNumOrder().($isEdit ? ' modifie' : ' cree').' avec succes.');

        return $this->redirectToRoute('app_admin_clients_show', [
            'id' => $customer->getId(),
            'tab' => 'devis',
        ]);
    }

    /**
     * @codeCoverageIgnore
     *
     * @param array{title?: string, description?: string, priceHt?: string, pos?: string|int, type?: string, serviceId?: string|int} $data
     */
    private function createDevisLine(Devis $devis, array $data, string $title, float $priceHt, int $pos): DevisLine
    {
        $line = new DevisLine($devis, $title, number_format($priceHt, 2, '.', ''), $pos);
        $description = trim((string) ($data['description'] ?? ''));
        if ('' !== $description) {
            $line->setDescription($description);
        }
        $lineType = trim((string) ($data['type'] ?? ''));
        if ('' !== $lineType) {
            $line->setType($lineType);
        }
        $lineServiceId = (int) ($data['serviceId'] ?? 0);
        if ($lineServiceId > 0) {
            $line->setServiceId($lineServiceId);
        }

        return $line;
    }

    #[Route('/{id}/generate-pdf', name: 'generate_pdf', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function generatePdf(int $id, KernelInterface $kernel, Environment $twig): Response
    {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        // Generation du PDF (devis + CGV fusionnees via FPDI)
        $pdf = new DevisPdf($kernel, $devis, $twig);
        $pdf->generate();

        // Ecriture dans un fichier temporaire
        $tmpPath = tempnam(sys_get_temp_dir(), 'devis_').'.pdf';
        $pdf->Output('F', $tmpPath);

        $hadOld = null !== $devis->getUnsignedPdf();
        $uploadDir = $kernel->getProjectDir().'/public/uploads/devis';

        // Regeneration : supprime l'ancien fichier
        if ($hadOld) {
            $oldPath = $uploadDir.'/'.$devis->getUnsignedPdf();
            if (file_exists($oldPath)) {
                @unlink($oldPath);
            }
            $devis->setUnsignedPdf(null);
        }

        // UploadedFile avec test=true : contourne la verification "is_uploaded_file"
        // qui rejetterait un fichier genere serveur-side
        $uploadedFile = new UploadedFile(
            $tmpPath,
            'devis-'.str_replace('/', '-', $devis->getOrderNumber()->getNumOrder()).'.pdf',
            'application/pdf',
            null,
            true // test mode
        );

        $devis->setUnsignedPdfFile($uploadedFile);
        $devis->setUpdatedAt(new \DateTimeImmutable());

        $this->em->flush();

        $this->addFlash('success', 'PDF devis '.$devis->getOrderNumber()->getNumOrder().' '.($hadOld ? 'regenere' : 'genere').'.');

        return $this->redirectToRoute('app_admin_clients_show', [
            'id' => $devis->getCustomer()?->getId() ?? 0,
            'tab' => 'devis',
        ]);
    }

    #[Route('/{id}/events', name: 'events', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function events(int $id): Response
    {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        $submitterId = (int) ($devis->getSubmissionId() ?? '0');
        $events = [];
        if ($submitterId > 0) {
            $events = $this->em->getRepository(DocusealEvent::class)
                ->createQueryBuilder('e')
                ->where('e.type = :type')
                ->andWhere('e.submitterId = :sid')
                ->setParameter('type', 'devis')
                ->setParameter('sid', $submitterId)
                ->orderBy('e.createdAt', 'DESC')
                ->getQuery()
                ->getResult();
        }

        return $this->render('admin/devis/events.html.twig', [
            'devis' => $devis,
            'events' => $events,
        ]);
    }

    #[Route('/{id}/send', name: 'send', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function send(
        int $id,
        DocuSealService $docuSeal,
        MailerService $mailer,
        Environment $twig,
        UrlGeneratorInterface $urlGenerator,
    ): Response {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        $customer = $devis->getCustomer();
        if (null === $devis->getUnsignedPdf() || null === $customer || null === $customer->getEmail()) {
            $this->addFlash('error', null === $devis->getUnsignedPdf() ? 'Le PDF du devis doit etre genere avant l\'envoi.' : 'Client ou email introuvable.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $customer?->getId() ?? ($devis->getCustomer()?->getId() ?? 0),
                'tab' => 'devis',
            ]);
        }

        // URL de redirection post-signature DocuSeal
        $signedRedirectUrl = $urlGenerator->generate('app_devis_process_signed', [
            'id' => $devis->getId(),
            'hmac' => $devis->getHmac(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        // Envoi a DocuSeal et recuperation de l'id submitter
        $submitterId = $docuSeal->sendDevisForSignature($devis, $signedRedirectUrl);
        if (null === $submitterId) {
            $this->addFlash('error', 'Echec de l\'envoi a DocuSeal.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $customer->getId(),
                'tab' => 'devis',
            ]);
        }

        $devis->setSubmissionId((string) $submitterId);
        $devis->setState(Devis::STATE_SEND);
        $this->em->flush();

        $this->sendDevisSignEmail($devis, $customer, $mailer, $twig, $urlGenerator, 'Votre devis '.$devis->getOrderNumber()->getNumOrder().' est pret a signer');
        $this->meilisearch->indexDevis($devis);

        $this->addFlash('success', 'Devis envoye a '.$customer->getEmail().' pour signature.');

        return $this->redirectToRoute('app_admin_clients_show', [
            'id' => $customer->getId(),
            'tab' => 'devis',
        ]);
    }

    #[Route('/{id}/resend', name: 'resend', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function resend(
        int $id,
        DocuSealService $docuSeal,
        MailerService $mailer,
        Environment $twig,
        UrlGeneratorInterface $urlGenerator,
    ): Response {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        $customer = $devis->getCustomer();
        if (null === $customer || null === $customer->getEmail() || null === $devis->getUnsignedPdf()) {
            $this->addFlash('error', null === $devis->getUnsignedPdf() ? 'Le PDF doit exister pour renvoyer le lien.' : 'Client ou email introuvable.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $customer?->getId() ?? 0,
                'tab' => 'devis',
            ]);
        }

        $signedRedirectUrl = $urlGenerator->generate('app_devis_process_signed', [
            'id' => $devis->getId(),
            'hmac' => $devis->getHmac(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        $submitterId = $docuSeal->resendDevisSignature($devis, $signedRedirectUrl);
        if (null === $submitterId) {
            $this->addFlash('error', 'Echec du renvoi a DocuSeal.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $customer->getId(),
                'tab' => 'devis',
            ]);
        }

        $devis->setSubmissionId((string) $submitterId);
        $devis->setState(Devis::STATE_SEND);
        $this->em->flush();

        $this->sendDevisSignEmail($devis, $customer, $mailer, $twig, $urlGenerator, 'Nouveau lien de signature - Devis '.$devis->getOrderNumber()->getNumOrder());

        $this->addFlash('success', 'Nouveau lien de signature envoye a '.$customer->getEmail().'.');

        return $this->redirectToRoute('app_admin_clients_show', [
            'id' => $customer->getId(),
            'tab' => 'devis',
        ]);
    }

    /** @codeCoverageIgnore */
    private function sendDevisSignEmail(Devis $devis, \App\Entity\Customer $customer, MailerService $mailer, Environment $twig, UrlGeneratorInterface $urlGenerator, string $subject): void
    {
        $processUrl = $urlGenerator->generate('app_devis_process', [
            'id' => $devis->getId(),
            'hmac' => $devis->getHmac(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        $mailer->sendEmail(
            $customer->getEmail(),
            $subject,
            $twig->render('emails/devis_to_sign.html.twig', [
                'customer' => $customer,
                'devis' => $devis,
                'processUrl' => $processUrl,
            ]),
        );
    }

    #[Route('/{id}/create-advert', name: 'create_advert', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function createAdvert(int $id, AdvertService $advertService): Response
    {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        if (Devis::STATE_ACCEPTED !== $devis->getState()) {
            $this->addFlash('error', 'Le devis doit etre signe (accepted) pour creer un avis de paiement.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $devis->getCustomer()?->getId() ?? 0,
                'tab' => 'devis',
            ]);
        }

        if (null !== $devis->getAdvert()) {
            $this->addFlash('error', 'Un avis de paiement existe deja pour ce devis.');

            return $this->redirectToRoute('app_admin_clients_show', [
                'id' => $devis->getCustomer()?->getId() ?? 0,
                'tab' => 'devis',
            ]);
        }

        // Creation de l'avis de paiement avec le meme OrderNumber
        $advert = $advertService->createFromDevis($devis);

        // Copie du client
        $advert->setCustomer($devis->getCustomer());

        // Copie des totaux
        $advert->setTotalHt($devis->getTotalHt());
        $advert->setTotalTva($devis->getTotalTva());
        $advert->setTotalTtc($devis->getTotalTtc());

        // Copie des lignes
        foreach ($devis->getLines() as $devisLine) {
            $advertLine = new AdvertLine(
                $advert,
                $devisLine->getTitle(),
                $devisLine->getPriceHt(),
                $devisLine->getPos(),
            );
            if (null !== $devisLine->getDescription()) {
                $advertLine->setDescription($devisLine->getDescription());
            }
            if (null !== $devisLine->getType()) {
                $advertLine->setType($devisLine->getType());
            }
            if (null !== $devisLine->getServiceId()) {
                $advertLine->setServiceId($devisLine->getServiceId());
            }
            $this->em->persist($advertLine);
            $advert->addLine($advertLine);
        }

        $this->em->flush();

        $this->meilisearch->indexAdvert($advert);
        $this->meilisearch->indexDevis($devis);

        $this->addFlash('success', 'Avis de paiement '.$advert->getOrderNumber()->getNumOrder().' cree a partir du devis.');

        return $this->redirectToRoute('app_admin_clients_show', [
            'id' => $devis->getCustomer()?->getId() ?? 0,
            'tab' => 'devis',
        ]);
    }

    #[Route('/{id}/cancel', name: 'cancel', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function cancel(int $id): Response
    {
        $devis = $this->em->getRepository(Devis::class)->find($id);
        if (null === $devis) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        if (Devis::STATE_CANCEL === $devis->getState()) {
            $this->addFlash('error', 'Devis deja annule.');
        } else {
            $devis->setState(Devis::STATE_CANCEL);
            // Libere le numero pour qu'il soit reutilise
            $devis->getOrderNumber()->markAsUnused();
            $this->em->flush();

            $this->meilisearch->indexDevis($devis);

            $this->addFlash('success', 'Devis '.$devis->getOrderNumber()->getNumOrder().' annule. Le numero est libere.');
        }

        $customerId = $devis->getCustomer()?->getId();

        return $customerId
            ? $this->redirectToRoute('app_admin_clients_show', ['id' => $customerId, 'tab' => 'devis'])
            : $this->redirectToRoute('app_admin_clients_index');
    }
}
