<?php

namespace App\Controller\Admin;

use App\Entity\Advert;
use App\Entity\AdvertEvent;
use App\Service\FactureService;
use App\Service\MailerService;
use App\Service\MeilisearchService;
use App\Service\Pdf\AdvertPdf;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\File\UploadedFile;
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/advert', name: 'app_admin_advert_')]
#[IsGranted('ROLE_EMPLOYE')]
class AdvertController extends AbstractController
{
    private const MSG_NOT_FOUND = 'Avis de paiement introuvable';

    public function __construct(
        private EntityManagerInterface $em,
        private MeilisearchService $meilisearch,
    ) {
    }

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

        $events = $this->em->getRepository(AdvertEvent::class)->findBy(
            ['advert' => $advert],
            ['createdAt' => 'DESC']
        );

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

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

        $pdf = new AdvertPdf($kernel, $advert, $urlGenerator, $twig);
        $pdf->generate();

        $tmpPath = tempnam(sys_get_temp_dir(), 'advert_').'.pdf';
        $pdf->Output('F', $tmpPath);

        $hadOld = null !== $advert->getAdvertFile();
        $uploadDir = $kernel->getProjectDir().'/public/uploads/adverts';

        if ($hadOld) {
            $oldPath = $uploadDir.'/'.$advert->getAdvertFile();
            if (file_exists($oldPath)) {
                @unlink($oldPath);
            }
            $advert->setAdvertFile(null);
        }

        $uploadedFile = new UploadedFile(
            $tmpPath,
            'avis-'.str_replace('/', '-', $advert->getOrderNumber()->getNumOrder()).'.pdf',
            'application/pdf',
            null,
            true
        );

        $advert->setAdvertFileUpload($uploadedFile);
        $advert->setUpdatedAt(new \DateTimeImmutable());

        $this->em->flush();

        @unlink($tmpPath);

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

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

    #[Route('/{id}/send', name: 'send', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function send(
        int $id,
        MailerService $mailer,
        Environment $twig,
        UrlGeneratorInterface $urlGenerator,
        #[Autowire('%kernel.project_dir%')] string $projectDir,
    ): Response {
        $advert = $this->em->getRepository(Advert::class)->find($id);
        if (null === $advert) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        if (null === $advert->getAdvertFile()) {
            $this->addFlash('error', 'Le PDF doit etre genere avant l\'envoi.');

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

        $customer = $advert->getCustomer();
        if (null === $customer || null === $customer->getEmail()) {
            $this->addFlash('error', 'Client ou email introuvable.');

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

        $this->sendAdvertEmail($advert, $customer, $mailer, $twig, $urlGenerator, $projectDir, 'Avis de paiement');

        $advert->setState(Advert::STATE_SEND);
        $this->em->flush();

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

        $this->em->persist(new AdvertEvent($advert, AdvertEvent::TYPE_MAIL_SEND, 'Avis envoye a '.$customer->getEmail()));
        $this->em->flush();

        $this->addFlash('success', 'Avis de paiement envoye a '.$customer->getEmail().'.');

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

    #[Route('/{id}/resend', name: 'resend', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function resend(
        int $id,
        MailerService $mailer,
        Environment $twig,
        UrlGeneratorInterface $urlGenerator,
        #[Autowire('%kernel.project_dir%')] string $projectDir,
    ): Response {
        $advert = $this->em->getRepository(Advert::class)->find($id);
        if (null === $advert) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        $customer = $advert->getCustomer();
        if (null === $customer || null === $customer->getEmail()) {
            $this->addFlash('error', 'Client ou email introuvable.');

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

        if (null === $advert->getAdvertFile()) {
            $this->addFlash('error', 'Le PDF doit exister pour renvoyer l\'avis.');

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

        $this->sendAdvertEmail($advert, $customer, $mailer, $twig, $urlGenerator, $projectDir, 'Rappel - Avis de paiement');

        $this->em->persist(new AdvertEvent($advert, AdvertEvent::TYPE_MAIL_SEND, 'Rappel envoye a '.$customer->getEmail()));
        $this->em->flush();

        $this->addFlash('success', 'Avis de paiement renvoye a '.$customer->getEmail().'.');

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

    private function sendAdvertEmail(Advert $advert, \App\Entity\Customer $customer, MailerService $mailer, Environment $twig, UrlGeneratorInterface $urlGenerator, string $projectDir, string $subjectPrefix): void
    {
        $numOrder = $advert->getOrderNumber()->getNumOrder();
        $paymentUrl = $urlGenerator->generate('app_order_payment', ['numOrder' => $numOrder], UrlGeneratorInterface::ABSOLUTE_URL);

        $attachments = [];
        $pdfPath = $projectDir.'/public/uploads/adverts/'.$advert->getAdvertFile();
        if (file_exists($pdfPath)) {
            $attachments[] = ['path' => $pdfPath, 'name' => 'avis-paiement-'.str_replace('/', '-', $numOrder).'.pdf'];
        }

        $mailer->sendEmail(
            $customer->getEmail(),
            $subjectPrefix.' '.$numOrder,
            $twig->render('emails/advert_send.html.twig', [
                'customer' => $customer,
                'advert' => $advert,
                'paymentUrl' => $paymentUrl,
            ]),
            null,
            null,
            false,
            $attachments,
        );
    }

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

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

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

    #[Route('/{id}/create-facture', name: 'create_facture', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function createFacture(int $id, FactureService $factureService): Response
    {
        $advert = $this->em->getRepository(Advert::class)->find($id);
        if (null === $advert) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        if (Advert::STATE_ACCEPTED !== $advert->getState()) {
            $this->addFlash('error', 'L\'avis doit etre paye pour creer une facture.');

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

        if ($advert->getFactures()->count() > 0) {
            $this->addFlash('error', 'Une facture existe deja pour cet avis.');

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

        $facture = $factureService->createFromAdvert($advert);

        $this->addFlash('success', 'Facture '.$facture->getInvoiceNumber().' creee.');

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

    /**
     * Enregistre un paiement manuel (virement, cheque, especes, etc.).
     */
    #[Route('/{id}/manual-payment', name: 'manual_payment', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function manualPayment(
        int $id,
        \Symfony\Component\HttpFoundation\Request $request,
        FactureService $factureService,
    ): Response {
        $advert = $this->em->getRepository(Advert::class)->find($id);
        if (null === $advert) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        $amount = $request->request->getString('amount');
        $method = $request->request->getString('method');
        $reference = trim($request->request->getString('reference'));

        if ('' === $amount || '' === $method) {
            $this->addFlash('error', 'Montant et methode de paiement requis.');

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

        $methodLabel = match ($method) {
            'virement' => 'Virement bancaire',
            'cheque' => 'Cheque',
            'especes' => 'Especes',
            'cb_externe' => 'CB (terminal externe)',
            default => 'Autre',
        };

        if ('' !== $reference) {
            $methodLabel .= ' (Ref: '.$reference.')';
        }

        // Creer l'AdvertPayment
        $amountFormatted = number_format((float) $amount, 2, '.', '');
        $payment = new \App\Entity\AdvertPayment($advert, \App\Entity\AdvertPayment::TYPE_SUCCESS, $amountFormatted);
        $payment->setMethod($method);

        // Lier a un echeancier si methode = echeancier
        if ('echeancier' === $method) {
            $echeancierId = $request->request->getInt('echeancierId');
            if ($echeancierId > 0) {
                $echeancier = $this->em->getRepository(\App\Entity\Echeancier::class)->find($echeancierId);
                if (null !== $echeancier) {
                    $payment->setEcheancier($echeancier);
                    $methodLabel = 'Echeancier #'.$echeancier->getId().' - '.$echeancier->getDescription();
                }
            }
        }

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

        // Tracker l'evenement
        $this->em->persist(new \App\Entity\AdvertEvent($advert, \App\Entity\AdvertEvent::TYPE_PAY, 'Paiement manuel : '.$methodLabel.' - '.$amount.' EUR'));

        // Calculer le total des paiements reussis
        $totalPaid = 0.0;
        foreach ($advert->getPayments() as $p) {
            if (\App\Entity\AdvertPayment::TYPE_SUCCESS === $p->getType()) {
                $totalPaid += (float) $p->getAmount();
            }
        }
        $totalPaid += (float) $amountFormatted;

        $advertTotal = (float) $advert->getTotalTtc();
        $isFullyPaid = $totalPaid >= ($advertTotal - 0.01);

        if ($isFullyPaid) {
            $advert->setState(Advert::STATE_ACCEPTED);
        }

        $this->em->flush();

        // Generer la facture uniquement si totalement paye
        if ($isFullyPaid) {
            try {
                $factureService->createPaidFactureFromAdvert($advert, number_format($advertTotal, 2, '.', ''), $methodLabel);
            } catch (\Throwable $e) {
                $this->addFlash('warning', 'Paiement enregistre mais erreur generation facture : '.$e->getMessage());
            }
        }

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

        $remaining = $advertTotal - $totalPaid;
        if ($isFullyPaid) {
            $this->addFlash('success', 'Paiement complet de '.$amount.' EUR enregistre ('.$methodLabel.'). Avis '.$advert->getOrderNumber()->getNumOrder().' marque paye.');
        } else {
            $this->addFlash('success', 'Paiement partiel de '.$amount.' EUR enregistre ('.$methodLabel.'). Reste a payer : '.number_format($remaining, 2, ',', ' ').' EUR.');
        }

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

    #[Route('/{id}/sync-payment', name: 'sync_payment', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function syncPayment(
        int $id,
        FactureService $factureService,
        #[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
    ): Response {
        $advert = $this->em->getRepository(Advert::class)->find($id);
        if (null === $advert) {
            throw $this->createNotFoundException(self::MSG_NOT_FOUND);
        }

        $piId = $advert->getStripePaymentId();
        if (null === $piId || '' === $stripeSk) {
            $this->addFlash('error', 'Pas de PaymentIntent ou Stripe non configure.');

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

        try {
            $this->processSyncPayment($advert, $piId, $stripeSk, $factureService);
        } catch (\Throwable $e) {
            $this->addFlash('error', 'Erreur sync Stripe : '.$e->getMessage());
        }

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

    /**
     * @codeCoverageIgnore
     */
    private function processSyncPayment(Advert $advert, string $piId, string $stripeSk, FactureService $factureService): void
    {
        \Stripe\Stripe::setApiKey($stripeSk);
        $pi = \Stripe\PaymentIntent::retrieve($piId);

        if ('succeeded' !== $pi->status) {
            $this->addFlash('warning', 'PaymentIntent statut : '.$pi->status.' (pas encore succeeded).');

            return;
        }

        $amount = number_format($pi->amount_received / 100, 2, '.', '');
        /** @phpstan-ignore-next-line */
        $metadata = $pi->metadata instanceof \Stripe\StripeObject ? $pi->metadata->toArray() : (array) ($pi->metadata ?? []);
        $method = $metadata['payment_method'] ?? ($pi->payment_method_types[0] ?? 'card');
        $methodLabel = $this->resolveMethodLabel($method);

        if (Advert::STATE_ACCEPTED !== $advert->getState()) {
            $advert->setState(Advert::STATE_ACCEPTED);
        }

        $this->ensureAdvertPayment($advert, $amount, $method);
        $this->ensureFacture($advert, $amount, $methodLabel, $factureService);

        $this->em->flush();
        $this->meilisearch->indexAdvert($advert);

        $this->addFlash('success', 'Sync Stripe OK : avis '.$advert->getOrderNumber()->getNumOrder().' paye ('.$methodLabel.', '.$amount.' EUR).');
    }

    /** @codeCoverageIgnore */
    private function resolveMethodLabel(string $method): string
    {
        return match ($method) {
            'sepa_debit' => 'Prelevement SEPA',
            'customer_balance' => 'Virement bancaire (Stripe)',
            'paypal' => 'PayPal',
            'klarna' => 'Klarna',
            'revolut_pay' => 'Revolut Pay',
            'amazon_pay' => 'Amazon Pay',
            'link' => 'Link (Stripe)',
            default => 'Carte bancaire',
        };
    }

    /** @codeCoverageIgnore */
    private function ensureAdvertPayment(Advert $advert, string $amount, string $method): void
    {
        $existing = $this->em->getRepository(\App\Entity\AdvertPayment::class)
            ->findOneBy(['advert' => $advert, 'type' => \App\Entity\AdvertPayment::TYPE_SUCCESS]);

        if (null === $existing) {
            $payment = new \App\Entity\AdvertPayment($advert, \App\Entity\AdvertPayment::TYPE_SUCCESS, $amount);
            $payment->setMethod($method);
            $this->em->persist($payment);
        }
    }

    /** @codeCoverageIgnore */
    private function ensureFacture(Advert $advert, string $amount, string $methodLabel, FactureService $factureService): void
    {
        if (0 === $advert->getFactures()->count()) {
            $factureService->createPaidFactureFromAdvert($advert, $amount, $methodLabel);

            return;
        }

        $facture = $advert->getFactures()->first();
        if (false !== $facture && !$facture->isPaid()) {
            $facture->setIsPaid(true);
            $facture->setPaidAt(new \DateTimeImmutable());
            $facture->setPaidMethod($methodLabel);
            $facture->setState(\App\Entity\Facture::STATE_PAID);
        }
    }

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

        if (Advert::STATE_CANCEL === $advert->getState()) {
            $this->addFlash('error', 'Avis de paiement deja annule.');
        } else {
            $advert->setState(Advert::STATE_CANCEL);

            // Delie l'avis du devis pour permettre d'en recrée un nouveau
            $devis = $advert->getDevis();
            if (null !== $devis) {
                $advert->setDevis(null);
                $devis->setAdvert(null);
            }

            // Libere le numero
            $advert->getOrderNumber()->markAsUnused();

            $this->em->flush();

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

            $this->addFlash('success', 'Avis de paiement '.$advert->getOrderNumber()->getNumOrder().' annule. Le devis peut recevoir un nouvel avis.');
        }

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

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