<?php

namespace App\Controller;

use App\Entity\AnalyticsEvent;
use App\Entity\AnalyticsUniqId;
use App\Entity\AuditLog;
use App\Entity\BilletBuyer;
use App\Entity\BilletOrder;
use App\Entity\Event;
use App\Entity\OrganizerInvitation;
use App\Entity\User;
use App\Service\AuditService;
use App\Service\BilletOrderService;
use App\Service\EventIndexService;
use App\Service\ExportService;
use App\Service\MailerService;
use App\Service\MeilisearchService;
use App\Service\SiretService;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;

#[Route('/admin')]
#[IsGranted('ROLE_ROOT')]
class AdminController extends AbstractController
{
    private const DQL_STATUS_PAID = 'o.status = :paid';
    private const DQL_EXCLUDE_INVITATIONS = 'o.isInvitation = false OR o.isInvitation IS NULL';
    private const NO_SNAPSHOT_MSG = self::NO_SNAPSHOT_MSG;

    #[Route('', name: 'app_admin_dashboard')]
    public function dashboard(EntityManagerInterface $em, #[Autowire(service: 'stats.cache')] CacheInterface $cache): Response
    {
        $stats = $cache->get('admin_dashboard_stats', function (ItemInterface $item) use ($em) {
            $item->expiresAfter(600);

            $allUsers = $em->getRepository(User::class)->findAll();
            $organizers = array_filter($allUsers, fn (User $u) => \in_array('ROLE_ORGANIZER', $u->getRoles(), true) && $u->isApproved());

            $totalCA = (int) ($em->createQueryBuilder()
                ->select('SUM(o.totalHT)')
                ->from(BilletBuyer::class, 'o')
                ->where(self::DQL_STATUS_PAID)
                ->andWhere(self::DQL_EXCLUDE_INVITATIONS)
                ->setParameter('paid', BilletBuyer::STATUS_PAID)
                ->getQuery()
                ->getSingleScalarResult() ?? 0);

            $nbOrders = $em->createQueryBuilder()
                ->select('COUNT(o.id)')
                ->from(BilletBuyer::class, 'o')
                ->where(self::DQL_STATUS_PAID)
                ->andWhere(self::DQL_EXCLUDE_INVITATIONS)
                ->setParameter('paid', BilletBuyer::STATUS_PAID)
                ->getQuery()
                ->getSingleScalarResult();
            $nbBillets = $em->getRepository(BilletOrder::class)->count([]);

            $commissionEticket = 0;
            $commissionStripe = 0;
            $paidOrders = $em->createQueryBuilder()
                ->select('o', 'e')
                ->from(BilletBuyer::class, 'o')
                ->join('o.event', 'e')
                ->join('e.account', 'a')
                ->where(self::DQL_STATUS_PAID)
                ->andWhere(self::DQL_EXCLUDE_INVITATIONS)
                ->setParameter('paid', BilletBuyer::STATUS_PAID)
                ->getQuery()
                ->getResult();

            $stripeFeeRate = (float) $this->getParameter('stripe_fee_rate');
            $stripeFeeFixed = (int) $this->getParameter('stripe_fee_fixed');

            foreach ($paidOrders as $order) {
                $rate = $order->getEvent()->getAccount()->getCommissionRate() ?? 3;
                $ht = $order->getTotalHT() / 100;
                $commissionEticket += $ht * ($rate / 100);
                $commissionStripe += $ht * $stripeFeeRate + $stripeFeeFixed / 100;
            }

            return [
                'nbOrgas' => \count($organizers),
                'nbOrders' => $nbOrders,
                'nbBillets' => $nbBillets,
                'totalCA' => $totalCA / 100,
                'commissionEticket' => $commissionEticket,
                'commissionStripe' => $commissionStripe,
            ];
        });

        return $this->render('admin/dashboard.html.twig', $stats);
    }

    /** @codeCoverageIgnore Requires live Meilisearch */
    #[Route('/sync-meilisearch', name: 'app_admin_sync_meilisearch', methods: ['POST'])]
    public function syncMeilisearch(EntityManagerInterface $em, MeilisearchService $meilisearch): Response
    {
        $allUsers = $em->getRepository(User::class)->findAll();
        $buyers = array_filter($allUsers, fn (User $u) => $u->isVerified() && !\in_array('ROLE_ORGANIZER', $u->getRoles(), true) && !\in_array('ROLE_ROOT', $u->getRoles(), true));

        $meilisearch->createIndexIfNotExists('buyers');

        $documents = array_map(fn (User $u) => [
            'id' => $u->getId(),
            'firstName' => $u->getFirstName(),
            'lastName' => $u->getLastName(),
            'email' => $u->getEmail(),
            'createdAt' => $u->getCreatedAt()->format('d/m/Y'),
        ], array_values($buyers));

        if ([] !== $documents) {
            $meilisearch->addDocuments('buyers', $documents);
        }

        $organizers = array_filter($allUsers, fn (User $u) => $u->isApproved() && \in_array('ROLE_ORGANIZER', $u->getRoles(), true));

        $meilisearch->createIndexIfNotExists('organizers');

        $orgaDocs = array_map(fn (User $u) => [
            'id' => $u->getId(),
            'firstName' => $u->getFirstName(),
            'lastName' => $u->getLastName(),
            'email' => $u->getEmail(),
            'companyName' => $u->getCompanyName(),
            'siret' => $u->getSiret(),
            'city' => $u->getCity(),
        ], array_values($organizers));

        if ([] !== $orgaDocs) {
            $meilisearch->addDocuments('organizers', $orgaDocs);
        }

        $this->addFlash('success', sprintf('%d acheteur(s) et %d organisateur(s) synchronise(s) dans Meilisearch.', \count($documents), \count($orgaDocs)));

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

    #[Route('/utilisateurs', name: 'app_admin_users')]
    public function users(EntityManagerInterface $em): Response
    {
        $users = $em->getRepository(User::class)->findBy([], ['createdAt' => 'DESC']);

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

    #[Route('/acheteurs', name: 'app_admin_buyers')]
    public function buyers(Request $request, EntityManagerInterface $em, PaginatorInterface $paginator, MeilisearchService $meilisearch): Response
    {
        $query = $request->query->getString('q', '');
        $searchResults = null;

        if ('' !== $query) {
            try {
                $searchResults = $meilisearch->search('buyers', $query, ['limit' => 50]);
            } catch (\Throwable) {
                $this->addFlash('error', 'Erreur de recherche Meilisearch.');
            }
        }

        if (null !== $searchResults && isset($searchResults['hits'])) {
            $hitIds = array_map(fn (array $hit) => $hit['id'], $searchResults['hits']);
            $allUsers = $em->getRepository(User::class)->findBy(['id' => $hitIds]);
            $buyers = $allUsers;
        } else {
            $allUsers = $em->getRepository(User::class)->findBy([], ['createdAt' => 'DESC']);
            $buyers = array_values(array_filter($allUsers, fn (User $u) => !\in_array('ROLE_ORGANIZER', $u->getRoles(), true) && !\in_array('ROLE_ROOT', $u->getRoles(), true)));
        }

        $pagination = $paginator->paginate(
            $buyers,
            $request->query->getInt('page', 1),
            10,
        );

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

    #[Route('/organisateurs', name: 'app_admin_organizers')]
    public function organizers(Request $request, EntityManagerInterface $em, PaginatorInterface $paginator, MeilisearchService $meilisearch): Response
    {
        $query = $request->query->getString('q', '');
        $tab = $request->query->getString('tab', 'pending');
        $searchResults = null;

        if ('' !== $query) {
            try {
                $searchResults = $meilisearch->search('organizers', $query, ['limit' => 50]);
            } catch (\Throwable) {
                $this->addFlash('error', 'Erreur de recherche Meilisearch.');
            }
        }

        if (null !== $searchResults && isset($searchResults['hits'])) {
            $hitIds = array_map(fn (array $hit) => $hit['id'], $searchResults['hits']);
            $organizers = $em->getRepository(User::class)->findBy(['id' => $hitIds]);
        } else {
            $allUsers = $em->getRepository(User::class)->findBy([], ['createdAt' => 'DESC']);
            $organizers = array_values(array_filter($allUsers, fn (User $u) => \in_array('ROLE_ORGANIZER', $u->getRoles(), true)));
        }

        if ('' === $query) {
            if ('approved' === $tab) {
                $filtered = array_values(array_filter($organizers, fn (User $u) => $u->isApproved()));
            } else {
                $filtered = array_values(array_filter($organizers, fn (User $u) => !$u->isApproved()));
            }
        } else {
            $filtered = $organizers;
        }

        $pagination = $paginator->paginate(
            $filtered,
            $request->query->getInt('page', 1),
            10,
        );

        return $this->render('admin/organizers.html.twig', [
            'organizers' => $pagination,
            'tab' => $tab,
            'query' => $query,
        ]);
    }

    #[Route('/acheteurs/creer', name: 'app_admin_create_buyer', methods: ['POST'])]
    public function createBuyer(
        Request $request,
        EntityManagerInterface $em,
        UserPasswordHasherInterface $passwordHasher,
        ValidatorInterface $validator,
        MailerService $mailerService,
    ): Response {
        $user = new User();
        $user->setFirstName(trim($request->request->getString('first_name')));
        $user->setLastName(trim($request->request->getString('last_name')));
        $user->setEmail(trim($request->request->getString('email')));

        $user->setPassword($passwordHasher->hashPassword($user, bin2hex(random_bytes(16))));

        $token = bin2hex(random_bytes(32));
        $user->setEmailVerificationToken($token);

        $errors = $validator->validate($user);
        if (0 === count($errors)) {
            $em->persist($user);
            $em->flush();

            $verificationUrl = $this->generateUrl('app_verify_email', [
                'token' => $token,
            ], UrlGeneratorInterface::ABSOLUTE_URL);

            $mailerService->sendEmail(
                to: $user->getEmail(),
                subject: 'Verifiez votre adresse email - E-Ticket',
                content: $this->renderView('email/verification.html.twig', [
                    'firstName' => $user->getFirstName(),
                    'verificationUrl' => $verificationUrl,
                ]),
                withUnsubscribe: false,
            );

            $this->addFlash('success', sprintf('Acheteur %s %s cree.', $user->getFirstName(), $user->getLastName()));

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

        foreach ($errors as $error) {
            $this->addFlash('error', $error->getMessage());
        }

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

    #[Route('/acheteur/{id}/renvoyer-verification', name: 'app_admin_resend_verification', methods: ['POST'])]
    public function resendVerification(User $user, EntityManagerInterface $em, MailerService $mailerService): Response
    {
        $token = bin2hex(random_bytes(32));
        $user->setEmailVerificationToken($token);
        $em->flush();

        $verificationUrl = $this->generateUrl('app_verify_email', [
            'token' => $token,
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        $mailerService->sendEmail(
            to: $user->getEmail(),
            subject: 'Verifiez votre adresse email - E-Ticket',
            content: $this->renderView('email/verification.html.twig', [
                'firstName' => $user->getFirstName(),
                'verificationUrl' => $verificationUrl,
            ]),
            withUnsubscribe: false,
        );

        $this->addFlash('success', sprintf('Email de verification renvoye a %s.', $user->getEmail()));

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

    #[Route('/acheteur/{id}/forcer-verification', name: 'app_admin_force_verification', methods: ['POST'])]
    public function forceVerification(User $user, EntityManagerInterface $em): Response
    {
        $user->setIsVerified(true);
        $user->setEmailVerifiedAt(new \DateTimeImmutable());
        $user->setEmailVerificationToken(null);
        $em->flush();

        $this->addFlash('success', sprintf('Email de %s %s force comme verifie.', $user->getFirstName(), $user->getLastName()));

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

    #[Route('/acheteur/{id}/reset-password', name: 'app_admin_reset_password', methods: ['POST'])]
    public function resetPassword(User $user, MailerService $mailerService): Response
    {
        $resetUrl = $this->generateUrl('app_forgot_password', [], UrlGeneratorInterface::ABSOLUTE_URL);

        $mailerService->sendEmail(
            to: $user->getEmail(),
            subject: 'Reinitialisation de votre mot de passe - E-Ticket',
            content: $this->renderView('email/admin_reset_password.html.twig', [
                'firstName' => $user->getFirstName(),
                'email' => $user->getEmail(),
                'resetUrl' => $resetUrl,
            ]),
            withUnsubscribe: false,
        );

        $this->addFlash('success', sprintf('Lien de reinitialisation envoye a %s.', $user->getEmail()));

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

    #[Route('/acheteur/{id}/supprimer', name: 'app_admin_delete_buyer', methods: ['POST'])]
    public function deleteBuyer(User $user, EntityManagerInterface $em, MeilisearchService $meilisearch): Response
    {
        $name = sprintf('%s %s', $user->getFirstName(), $user->getLastName());
        $userId = $user->getId();

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

        try {
            $meilisearch->deleteDocument('buyers', $userId);
        } catch (\Throwable) {
            // Meilisearch failure should not block user deletion
        }

        $this->addFlash('success', sprintf('Compte de %s supprime.', $name));

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

    #[Route('/organisateur/{id}/approuver', name: 'app_admin_approve_organizer', methods: ['POST'])]
    public function approveOrganizer(User $user, Request $request, EntityManagerInterface $em, MailerService $mailerService, MeilisearchService $meilisearch): Response
    {
        $offer = $request->request->getString('offer', 'free');
        $commissionRate = (float) $request->request->getString('commission_rate', '3');
        $billingAmount = (int) $request->request->getString('billing_amount', '1000');

        $user->setIsApproved(true);
        $user->setOffer($offer);
        $user->setCommissionRate($commissionRate);
        $user->setIsBilling(true);
        $user->setBillingAmount($billingAmount);
        $user->setBillingState('good');
        $em->flush();

        $meilisearch->createIndexIfNotExists('organizers');
        $meilisearch->addDocuments('organizers', [[
            'id' => $user->getId(),
            'firstName' => $user->getFirstName(),
            'lastName' => $user->getLastName(),
            'email' => $user->getEmail(),
            'companyName' => $user->getCompanyName(),
            'siret' => $user->getSiret(),
            'city' => $user->getCity(),
        ]]);

        $loginUrl = $this->generateUrl('app_login', [], UrlGeneratorInterface::ABSOLUTE_URL);

        $mailerService->sendEmail(
            to: $user->getEmail(),
            subject: 'Votre compte organisateur a ete approuve - E-Ticket',
            content: $this->renderView('email/organizer_approved.html.twig', [
                'firstName' => $user->getFirstName(),
                'loginUrl' => $loginUrl,
            ]),
            withUnsubscribe: false,
        );

        $this->addFlash('success', sprintf('Organisateur %s %s approuve.', $user->getFirstName(), $user->getLastName()));

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

    #[Route('/organisateur/{id}/refuser', name: 'app_admin_reject_organizer', methods: ['POST'])]
    public function rejectOrganizer(User $user, Request $request, EntityManagerInterface $em, MailerService $mailerService): Response
    {
        $reason = trim($request->request->getString('reason'));
        $email = $user->getEmail();
        $firstName = $user->getFirstName();
        $lastName = $user->getLastName();

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

        $mailerService->sendEmail(
            to: $email,
            subject: 'Votre demande de compte organisateur a ete refusee - E-Ticket',
            content: $this->renderView('email/organizer_rejected.html.twig', [
                'firstName' => $firstName,
                'reason' => $reason,
            ]),
            withUnsubscribe: false,
        );

        $this->addFlash('success', sprintf('Demande de %s %s refusee.', $firstName, $lastName));

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

    #[Route('/organisateur/{id}/siret', name: 'app_admin_siret_check')]
    public function siretCheck(User $user, SiretService $siretService): Response
    {
        if ($user->isApproved()) {
            return $this->redirectToRoute('app_admin_organizers');
        }

        $siret = $user->getSiret();

        if (!$siret) {
            $this->addFlash('error', 'Aucun SIRET renseigne.');

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

        $data = $siretService->lookup($siret);
        $rna = $data['complements']['identifiant_association'] ?? null;
        $rnaData = $rna ? $siretService->lookupRna($rna) : null;

        return $this->render('admin/siret_check.html.twig', [
            'user' => $user,
            'siret' => $siret,
            'data' => $data,
            'rnaData' => $rnaData,
        ]);
    }

    #[Route('/organisateur/{id}/siret/refresh', name: 'app_admin_siret_refresh', methods: ['POST'])]
    public function siretRefresh(User $user, SiretService $siretService): Response
    {
        $siret = $user->getSiret();

        if ($siret) {
            $data = $siretService->lookup($siret);
            $rna = $data['complements']['identifiant_association'] ?? null;
            $siretService->clearCache($siret, $rna);
            $this->addFlash('success', 'Cache SIRET vide. Les donnees ont ete rechargees.');
        }

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

    #[Route('/organisateur/{id}/modifier', name: 'app_admin_edit_organizer', methods: ['GET', 'POST'])]
    public function editOrganizer(User $user, Request $request, EntityManagerInterface $em): Response
    {
        if (!$user->isApproved()) {
            return $this->redirectToRoute('app_admin_organizers');
        }

        if ($request->isMethod('POST')) {
            $user->setFirstName(trim($request->request->getString('first_name')));
            $user->setLastName(trim($request->request->getString('last_name')));
            $user->setEmail(trim($request->request->getString('email')));
            $user->setPhone(trim($request->request->getString('phone')));
            $user->setCompanyName(trim($request->request->getString('company_name')));
            $user->setSiret(trim($request->request->getString('siret')));
            $user->setAddress(trim($request->request->getString('address')));
            $user->setPostalCode(trim($request->request->getString('postal_code')));
            $user->setCity(trim($request->request->getString('city')));
            $user->setOffer($request->request->getString('offer'));
            $user->setCommissionRate((float) $request->request->getString('commission_rate'));

            $logoFile = $request->files->get('logo');
            if ($logoFile) {
                $user->setLogoFile($logoFile);
            }

            $em->flush();

            $this->addFlash('success', sprintf('Organisateur %s %s mis a jour.', $user->getFirstName(), $user->getLastName()));

            return $this->redirectToRoute('app_admin_organizers', ['tab' => 'approved']);
        }

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

    #[Route('/organisateur/{id}/suspendre', name: 'app_admin_suspend_organizer', methods: ['POST'])]
    public function suspendOrganizer(User $user, EntityManagerInterface $em, AuditService $audit): Response
    {
        $suspended = !$user->isSuspended();
        $user->setIsSuspended($suspended ?: null);
        $em->flush();

        $audit->log($suspended ? 'organizer_suspended' : 'organizer_reactivated', 'User', $user->getId(), [
            'email' => $user->getEmail(),
            'companyName' => $user->getCompanyName(),
        ]);

        $this->addFlash('success', $suspended
            ? 'Organisateur '.$user->getCompanyName().' suspendu.'
            : 'Organisateur '.$user->getCompanyName().' reactive.');

        return $this->redirectToRoute('app_admin_organizers', ['tab' => 'approved']);
    }

    #[Route('/commandes', name: 'app_admin_orders', methods: ['GET'])]
    public function orders(Request $request, EntityManagerInterface $em, PaginatorInterface $paginator): Response
    {
        $status = $request->query->getString('status', '');
        $search = $request->query->getString('q', '');

        $qb = $em->createQueryBuilder()
            ->select('o', 'i')
            ->from(BilletBuyer::class, 'o')
            ->leftJoin('o.items', 'i')
            ->orderBy('o.createdAt', 'DESC');

        if ('' !== $status) {
            $qb->andWhere('o.status = :status')->setParameter('status', $status);
        }

        if ('' !== $search) {
            $qb->andWhere('o.orderNumber LIKE :q OR o.firstName LIKE :q OR o.lastName LIKE :q OR o.email LIKE :q')
                ->setParameter('q', '%'.$search.'%');
        }

        $orders = $paginator->paginate($qb->getQuery(), $request->query->getInt('page', 1), 20);

        $totalCA = $em->createQueryBuilder()
            ->select('SUM(o.totalHT)')
            ->from(BilletBuyer::class, 'o')
            ->where(self::DQL_STATUS_PAID)
            ->setParameter('paid', BilletBuyer::STATUS_PAID)
            ->getQuery()
            ->getSingleScalarResult() ?? 0;

        $totalOrders = $em->getRepository(BilletBuyer::class)->count(['status' => BilletBuyer::STATUS_PAID]);
        $totalRefunded = $em->getRepository(BilletBuyer::class)->count(['status' => BilletBuyer::STATUS_REFUNDED]);
        $totalCancelled = $em->getRepository(BilletBuyer::class)->count(['status' => BilletBuyer::STATUS_CANCELLED]);
        $totalPending = $em->getRepository(BilletBuyer::class)->count(['status' => BilletBuyer::STATUS_PENDING]);

        return $this->render('admin/orders.html.twig', [
            'orders' => $orders,
            'status' => $status,
            'search' => $search,
            'totalCA' => (int) $totalCA / 100,
            'totalOrders' => $totalOrders,
            'totalRefunded' => $totalRefunded,
            'totalCancelled' => $totalCancelled,
            'totalPending' => $totalPending,
        ]);
    }

    #[Route('/commandes/{id}/billets', name: 'app_admin_order_tickets', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function orderTickets(int $id, EntityManagerInterface $em, BilletOrderService $billetOrderService): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->find($id);
        if (!$order) {
            throw $this->createNotFoundException();
        }

        $tickets = $em->getRepository(BilletOrder::class)->findBy(['billetBuyer' => $order]);
        if (!$tickets) {
            throw $this->createNotFoundException('Aucun billet pour cette commande.');
        }

        if (1 === \count($tickets)) {
            $pdf = $billetOrderService->generatePdf($tickets[0]);

            return new Response($pdf, 200, [
                'Content-Type' => 'application/pdf',
                'Content-Disposition' => 'inline; filename="'.$tickets[0]->getReference().'.pdf"',
            ]);
        }

        $zip = new \ZipArchive();
        $tmpFile = tempnam(sys_get_temp_dir(), 'tickets_');
        $zip->open($tmpFile, \ZipArchive::OVERWRITE);

        foreach ($tickets as $ticket) {
            $pdf = $billetOrderService->generatePdf($ticket);
            $zip->addFromString($ticket->getReference().'.pdf', $pdf);
        }

        $zip->close();
        $content = file_get_contents($tmpFile);
        unlink($tmpFile);

        return new Response($content, 200, [
            'Content-Type' => 'application/zip',
            'Content-Disposition' => 'attachment; filename="billets_'.$order->getOrderNumber().'.zip"',
        ]);
    }

    #[Route('/evenements', name: 'app_admin_events')]
    public function events(Request $request, PaginatorInterface $paginator, EventIndexService $eventIndex): Response
    {
        $searchQuery = $request->query->getString('q', '');
        $eventsQuery = $eventIndex->searchEvents('event_admin', $searchQuery);

        $events = $paginator->paginate($eventsQuery, $request->query->getInt('page', 1), 10);

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

    #[Route('/evenement/{id}/en-ligne', name: 'app_admin_toggle_event_online', methods: ['POST'])]
    public function toggleEventOnline(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex): Response
    {
        $event->setIsOnline(!$event->isOnline());
        $em->flush();

        $eventIndex->indexEvent($event);

        $this->addFlash('success', $event->isOnline() ? 'Evenement mis en ligne.' : 'Evenement passe hors ligne.');

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

    #[Route('/evenement/{id}/supprimer', name: 'app_admin_delete_event', methods: ['POST'])]
    public function adminDeleteEvent(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex, AuditService $audit): Response
    {
        $eventTitle = $event->getTitle();
        $eventDbId = $event->getId();
        $eventIndex->removeEvent($event);

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

        $audit->log('event_deleted', 'Event', $eventDbId, ['title' => $eventTitle]);

        $this->addFlash('success', 'Evenement supprime.');

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

    #[Route('/export/{year}/{month}', name: 'app_admin_export', requirements: ['year' => '\d{4}', 'month' => '\d{1,2}'], methods: ['GET'])]
    public function export(int $year, int $month, ExportService $exportService): Response
    {
        $stats = $exportService->getMonthlyStats($year, $month);
        $csv = $exportService->generateCsv($stats['orders'], true);

        $filename = sprintf('export_admin_%04d_%02d.csv', $year, $month);

        return new Response($csv, 200, [
            'Content-Type' => 'text/csv; charset=utf-8',
            'Content-Disposition' => 'attachment; filename="'.$filename.'"',
        ]);
    }

    #[Route('/export/{year}/{month}/pdf', name: 'app_admin_export_pdf', requirements: ['year' => '\d{4}', 'month' => '\d{1,2}'], methods: ['GET'])]
    public function exportPdf(int $year, int $month, ExportService $exportService): Response
    {
        $stats = $exportService->getMonthlyStats($year, $month);
        $pdf = $exportService->generatePdf($stats, $year, $month);

        return new Response($pdf, 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="recap_admin_'.sprintf('%04d_%02d', $year, $month).'.pdf"',
        ]);
    }

    #[Route('/logs', name: 'app_admin_logs', methods: ['GET'])]
    public function logs(Request $request, EntityManagerInterface $em, PaginatorInterface $paginator): Response
    {
        $logs = $em->getRepository(AuditLog::class)->findBy([], ['createdAt' => 'DESC']);
        $paginatedLogs = $paginator->paginate($logs, $request->query->getInt('page', 1), 30);

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

    #[Route('/infra', name: 'app_admin_infra', methods: ['GET'])]
    public function infra(#[Autowire('%kernel.project_dir%')] string $projectDir): Response
    {
        $path = $projectDir.'/var/infra.json';
        if (!file_exists($path)) {
            $emptyServer = [
                'hostname' => '?', 'os' => '?', 'uptime' => '?',
                'cpu_model' => '?', 'cpu_cores' => '?', 'load_1m' => '?', 'load_5m' => '?', 'load_15m' => '?', 'load_percent' => '?',
                'ram_total' => '?', 'ram_used' => '?', 'ram_free' => '?', 'ram_percent' => '?',
                'disk_total' => '?', 'disk_used' => '?', 'disk_free' => '?', 'disk_percent' => '?',
                'caddy' => ['status' => 'unknown', 'info' => '?'], 'docker' => ['status' => 'unknown', 'info' => '?'],
                'ssl' => ['domain' => '?', 'issuer' => '?', 'valid_until' => '?', 'days_left' => '?', 'status' => 'unknown'],
            ];

            return $this->render('admin/infra.html.twig', [
                'snapshot_missing' => true,
                'server' => $emptyServer, 'containers' => [], 'redis_global' => ['connected' => false, 'error' => self::NO_SNAPSHOT_MSG],
                'redis_dbs' => [], 'postgres' => ['connected' => false, 'error' => self::NO_SNAPSHOT_MSG], 'pgbouncer' => ['connected' => false, 'error' => self::NO_SNAPSHOT_MSG],
                'generated_at' => null,
            ]);
        }

        $data = json_decode(file_get_contents($path), true) ?? [];

        return $this->render('admin/infra.html.twig', $data);
    }

    #[Route('/analytics', name: 'app_admin_analytics', methods: ['GET'])]
    public function analytics(Request $request, EntityManagerInterface $em): Response
    {
        $period = $request->query->getString('period', '7d');
        $since = match ($period) {
            'today' => new \DateTimeImmutable('today'),
            '30d' => new \DateTimeImmutable('-30 days'),
            'all' => new \DateTimeImmutable('2020-01-01'),
            default => new \DateTimeImmutable('-7 days'),
        };

        $visitors = (int) $em->createQueryBuilder()
            ->select('COUNT(v.id)')
            ->from(AnalyticsUniqId::class, 'v')
            ->where('v.createdAt >= :since')
            ->setParameter('since', $since)
            ->getQuery()
            ->getSingleScalarResult();

        $pageviews = (int) $em->createQueryBuilder()
            ->select('COUNT(e.id)')
            ->from(AnalyticsEvent::class, 'e')
            ->where('e.createdAt >= :since')
            ->setParameter('since', $since)
            ->getQuery()
            ->getSingleScalarResult();

        // Bounce rate: visitors with only 1 pageview
        $bouncedVisitors = (int) $em->createQueryBuilder()
            ->select('COUNT(v.id)')
            ->from(AnalyticsUniqId::class, 'v')
            ->where('v.createdAt >= :since')
            ->andWhere('(SELECT COUNT(e2.id) FROM '.AnalyticsEvent::class.' e2 WHERE e2.visitor = v.id) = 1')
            ->setParameter('since', $since)
            ->getQuery()
            ->getSingleScalarResult();

        $bounceRate = $visitors > 0 ? round($bouncedVisitors / $visitors * 100, 1) : 0;

        $topPages = $em->createQueryBuilder()
            ->select('e.url, COUNT(e.id) AS hits')
            ->from(AnalyticsEvent::class, 'e')
            ->where('e.createdAt >= :since')
            ->setParameter('since', $since)
            ->groupBy('e.url')
            ->orderBy('hits', 'DESC')
            ->setMaxResults(20)
            ->getQuery()
            ->getArrayResult();

        $topReferrers = $em->createQueryBuilder()
            ->select('e.referrer, COUNT(e.id) AS hits')
            ->from(AnalyticsEvent::class, 'e')
            ->where('e.createdAt >= :since')
            ->andWhere('e.referrer IS NOT NULL')
            ->andWhere("e.referrer != ''")
            ->andWhere('e.referrer NOT LIKE :self1')
            ->andWhere('e.referrer NOT LIKE :self2')
            ->setParameter('self1', '%ticket.e-cosplay.fr%')
            ->setParameter('self2', '%esyweb.local%')
            ->setParameter('since', $since)
            ->groupBy('e.referrer')
            ->orderBy('hits', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getArrayResult();

        $devices = $em->createQueryBuilder()
            ->select('v.deviceType, COUNT(v.id) AS cnt')
            ->from(AnalyticsUniqId::class, 'v')
            ->where('v.createdAt >= :since')
            ->setParameter('since', $since)
            ->groupBy('v.deviceType')
            ->getQuery()
            ->getArrayResult();

        $browsers = $em->createQueryBuilder()
            ->select('v.browser, COUNT(v.id) AS cnt')
            ->from(AnalyticsUniqId::class, 'v')
            ->where('v.createdAt >= :since')
            ->andWhere('v.browser IS NOT NULL')
            ->setParameter('since', $since)
            ->groupBy('v.browser')
            ->orderBy('cnt', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getArrayResult();

        $osList = $em->createQueryBuilder()
            ->select('v.os, COUNT(v.id) AS cnt')
            ->from(AnalyticsUniqId::class, 'v')
            ->where('v.createdAt >= :since')
            ->andWhere('v.os IS NOT NULL')
            ->setParameter('since', $since)
            ->groupBy('v.os')
            ->orderBy('cnt', 'DESC')
            ->getQuery()
            ->getArrayResult();

        // Daily chart data
        $conn = $em->getConnection();

        $visitorsPerDay = $conn->executeQuery(
            'SELECT CAST(created_at AS DATE) AS day, COUNT(*) AS cnt FROM analytics_uniq_id WHERE created_at >= :since GROUP BY day ORDER BY day ASC',
            ['since' => $since->format('Y-m-d H:i:s')],
        )->fetchAllAssociative();

        $pageviewsPerDay = $conn->executeQuery(
            'SELECT CAST(created_at AS DATE) AS day, COUNT(*) AS cnt FROM analytics_event WHERE created_at >= :since GROUP BY day ORDER BY day ASC',
            ['since' => $since->format('Y-m-d H:i:s')],
        )->fetchAllAssociative();

        // Merge into aligned arrays
        $allDays = [];
        foreach ($visitorsPerDay as $r) {
            $allDays[$r['day']] = true;
        }
        foreach ($pageviewsPerDay as $r) {
            $allDays[$r['day']] = true;
        }
        ksort($allDays);

        $visitorsMap = [];
        foreach ($visitorsPerDay as $r) {
            $d = $r['day'] instanceof \DateTimeInterface ? $r['day']->format('Y-m-d') : (string) $r['day'];
            $visitorsMap[$d] = (int) $r['cnt'];
        }
        $pageviewsMap = [];
        foreach ($pageviewsPerDay as $r) {
            $d = $r['day'] instanceof \DateTimeInterface ? $r['day']->format('Y-m-d') : (string) $r['day'];
            $pageviewsMap[$d] = (int) $r['cnt'];
        }

        $chartLabels = array_keys($allDays);
        $chartVisitors = array_map(fn ($d) => (int) ($visitorsMap[$d] ?? 0), $chartLabels);
        $chartPageviews = array_map(fn ($d) => (int) ($pageviewsMap[$d] ?? 0), $chartLabels);

        return $this->render('admin/analytics.html.twig', [
            'period' => $period,
            'visitors' => $visitors,
            'pageviews' => $pageviews,
            'bounce_rate' => $bounceRate,
            'top_pages' => $topPages,
            'top_referrers' => $topReferrers,
            'devices' => $devices,
            'browsers' => $browsers,
            'os_list' => $osList,
            'chart_labels' => $chartLabels,
            'chart_visitors' => $chartVisitors,
            'chart_pageviews' => $chartPageviews,
        ]);
    }

    #[Route('/organisateurs/inviter', name: 'app_admin_invite_organizer', methods: ['GET', 'POST'])]
    public function inviteOrganizer(Request $request, EntityManagerInterface $em, MailerService $mailerService): Response
    {
        $invitations = $em->getRepository(OrganizerInvitation::class)->findBy([], ['createdAt' => 'DESC']);

        if ($request->isMethod('POST')) {
            $companyName = trim($request->request->getString('company_name'));
            $firstName = trim($request->request->getString('first_name'));
            $lastName = trim($request->request->getString('last_name'));
            $email = trim($request->request->getString('email'));
            $message = trim($request->request->getString('message')) ?: null;
            $offer = $request->request->getString('offer', 'free');
            $commissionRate = (float) $request->request->getString('commission_rate', '3');
            $billingAmount = (int) $request->request->getString('billing_amount', '1000');

            if ('' === $companyName || '' === $firstName || '' === $lastName || '' === $email) {
                $this->addFlash('error', 'Tous les champs obligatoires doivent etre remplis.');

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

            $invitation = new OrganizerInvitation();
            $invitation->setCompanyName($companyName);
            $invitation->setFirstName($firstName);
            $invitation->setLastName($lastName);
            $invitation->setEmail($email);
            $invitation->setMessage($message);
            $invitation->setOffer($offer);
            $invitation->setCommissionRate($commissionRate);
            $invitation->setBillingAmount($billingAmount);

            $em->persist($invitation);
            $em->flush();

            $viewUrl = $this->generateUrl('app_invitation_view', [
                'token' => $invitation->getToken(),
            ], UrlGeneratorInterface::ABSOLUTE_URL);

            $html = $this->renderView('email/organizer_invitation.html.twig', [
                'invitation' => $invitation,
                'viewUrl' => $viewUrl,
            ]);

            $mailerService->sendEmail(
                $email,
                'Invitation organisateur - E-Ticket',
                $html,
            );

            $this->addFlash('success', 'Invitation envoyee a '.$email.'.');

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

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

    #[Route('/organisateurs/invitation/{id}/supprimer', name: 'app_admin_delete_invitation', methods: ['POST'])]
    public function deleteInvitation(int $id, EntityManagerInterface $em): Response
    {
        $invitation = $em->getRepository(OrganizerInvitation::class)->find($id);
        if (!$invitation) {
            throw $this->createNotFoundException();
        }

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

        $this->addFlash('success', 'Invitation supprimee.');

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

    #[Route('/organisateurs/invitation/{id}/renvoyer', name: 'app_admin_resend_invitation', methods: ['POST'])]
    public function resendInvitation(int $id, EntityManagerInterface $em, MailerService $mailerService): Response
    {
        $invitation = $em->getRepository(OrganizerInvitation::class)->find($id);
        if (!$invitation) {
            throw $this->createNotFoundException();
        }

        $viewUrl = $this->generateUrl('app_invitation_view', [
            'token' => $invitation->getToken(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        $html = $this->renderView('email/organizer_invitation.html.twig', [
            'invitation' => $invitation,
            'viewUrl' => $viewUrl,
        ]);

        $mailerService->sendEmail(
            $invitation->getEmail(),
            'Invitation organisateur - E-Ticket',
            $html,
        );

        $invitation->setStatus(OrganizerInvitation::STATUS_SENT);
        $invitation->setRespondedAt(null);
        $em->flush();

        $this->addFlash('success', 'Invitation renvoyee a '.$invitation->getEmail().'.');

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