<?php

namespace App\Controller;

use App\Controller\Trait\AccountEventOwnershipTrait;
use App\Entity\Billet;
use App\Entity\BilletDesign;
use App\Entity\Category;
use App\Entity\Event;
use App\Entity\User;
use App\Service\AuditService;
use App\Service\EventIndexService;
use App\Service\StripeService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_USER')]
class AccountEventCatalogController extends AbstractController
{
    use AccountEventOwnershipTrait;

    private const BREADCRUMB_HOME = ['name' => 'Accueil', 'url' => '/'];
    private const BREADCRUMB_ACCOUNT = ['name' => 'Mon compte', 'url' => '/mon-compte'];
    private const EVENT_BASE_URL = '/mon-compte/evenement/';
    private const EVENT_CATEGORIES_SUFFIX = '/modifier?tab=categories';

    #[Route('/mon-compte/evenement/{id}/categorie/ajouter', name: 'app_account_event_add_category', methods: ['POST'])]
    public function addCategory(Event $event, Request $request, EntityManagerInterface $em, AuditService $audit): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);

        $name = trim($request->request->getString('name'));
        if ('' === $name) {
            $this->addFlash('error', 'Le nom de la categorie est requis.');

            return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
        }

        $maxPosition = $em->getRepository(Category::class)->count(['event' => $event]);

        $category = new Category();
        $category->setName($name);
        $category->setEvent($event);
        $category->setPosition($maxPosition);

        $startAt = $request->request->getString('start_at');
        if ('' !== $startAt) {
            $category->setStartAt(new \DateTimeImmutable($startAt));
        }

        $endAt = $request->request->getString('end_at');
        if ('' !== $endAt) {
            $category->setEndAt(new \DateTimeImmutable($endAt));
        }

        if ($category->getEndAt() < $category->getStartAt()) {
            $category->setEndAt($category->getStartAt()->modify('+30 days'));
        }

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

        $audit->log('category_created', 'Category', $category->getId(), ['name' => $name, 'event' => $event->getTitle()]);

        $this->addFlash('success', sprintf('Categorie "%s" ajoutee.', $name));

        return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
    }

    #[Route('/mon-compte/evenement/{id}/categorie/{categoryId}/modifier', name: 'app_account_event_edit_category', methods: ['GET', 'POST'])]
    public function editCategory(Event $event, int $categoryId, Request $request, EntityManagerInterface $em, AuditService $audit): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);
        $category = $this->requireCategory($categoryId, $event, $em);

        if ($request->isMethod('POST')) {
            $category->setName(trim($request->request->getString('name')));

            $startAt = $request->request->getString('start_at');
            if ('' !== $startAt) {
                $category->setStartAt(new \DateTimeImmutable($startAt));
            }

            $endAt = $request->request->getString('end_at');
            if ('' !== $endAt) {
                $category->setEndAt(new \DateTimeImmutable($endAt));
            }

            if ($category->getEndAt() < $category->getStartAt()) {
                $category->setEndAt($category->getStartAt()->modify('+30 days'));
            }

            $category->setIsHidden($request->request->getBoolean('is_hidden'));

            $em->flush();

            $audit->log('category_updated', 'Category', $category->getId(), ['name' => $category->getName(), 'event' => $event->getTitle()]);

            $this->addFlash('success', 'Categorie modifiee.');

            return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
        }

        return $this->render('account/edit_category.html.twig', [
            'event' => $event,
            'category' => $category,
            'breadcrumbs' => [
                self::BREADCRUMB_HOME,
                self::BREADCRUMB_ACCOUNT,
                ['name' => $event->getTitle(), 'url' => self::EVENT_BASE_URL.$event->getId().self::EVENT_CATEGORIES_SUFFIX],
                ['name' => $category->getName(), 'url' => ''],
            ],
        ]);
    }

    #[Route('/mon-compte/evenement/{id}/categorie/{categoryId}/supprimer', name: 'app_account_event_delete_category', methods: ['POST'])]
    public function deleteCategory(Event $event, int $categoryId, EntityManagerInterface $em, AuditService $audit): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);

        $category = $em->getRepository(Category::class)->find($categoryId);
        if ($category && $category->getEvent()->getId() === $event->getId()) {
            $catName = $category->getName();
            $catId = $category->getId();
            $em->remove($category);
            $em->flush();
            $audit->log('category_deleted', 'Category', $catId, ['name' => $catName, 'event' => $event->getTitle()]);
            $this->addFlash('success', 'Categorie supprimee.');
        }

        return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
    }

    #[Route('/mon-compte/evenement/{id}/categorie/reorder', name: 'app_account_event_reorder_categories', methods: ['POST'])]
    public function reorderCategories(Event $event, Request $request, EntityManagerInterface $em): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);

        $order = json_decode($request->getContent(), true);
        if (\is_array($order)) {
            foreach ($order as $position => $categoryId) {
                $category = $em->getRepository(Category::class)->find($categoryId);
                if ($category && $category->getEvent()->getId() === $event->getId()) {
                    $category->setPosition($position);
                }
            }
            $em->flush();
        }

        return $this->json(['success' => true]);
    }

    #[Route('/mon-compte/evenement/{id}/categorie/{categoryId}/billet/ajouter', name: 'app_account_event_add_billet', methods: ['GET', 'POST'])]
    public function addBillet(Event $event, int $categoryId, Request $request, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $user = $this->requireEventOwnership($event);
        $category = $this->requireCategory($categoryId, $event, $em);

        if ($request->isMethod('POST')) {
            $billet = new Billet();
            $billet->setCategory($category);
            $billet->setPosition($em->getRepository(Billet::class)->count(['category' => $category]));
            $this->hydrateBilletFromRequest($billet, $request);

            $em->persist($billet);
            $this->syncBilletToStripe($billet, $user, $stripeService);
            $em->flush();

            $audit->log('billet_created', 'Billet', $billet->getId(), ['name' => $billet->getName(), 'event' => $event->getTitle()]);

            $this->addFlash('success', 'Billet ajoute avec succes.');

            return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
        }

        return $this->render('account/add_billet.html.twig', [
            'event' => $event,
            'category' => $category,
            'allowedTypes' => self::getAllowedBilletTypes($user->getOffer()),
            'breadcrumbs' => [
                self::BREADCRUMB_HOME,
                self::BREADCRUMB_ACCOUNT,
                ['name' => $event->getTitle(), 'url' => self::EVENT_BASE_URL.$event->getId().self::EVENT_CATEGORIES_SUFFIX],
                ['name' => 'Ajouter un billet'],
            ],
        ]);
    }

    #[Route('/mon-compte/evenement/{id}/billet/{billetId}/modifier', name: 'app_account_event_edit_billet', methods: ['GET', 'POST'])]
    public function editBillet(Event $event, int $billetId, Request $request, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $user = $this->requireEventOwnership($event);

        $billet = $em->getRepository(Billet::class)->find($billetId);
        if (!$billet || $billet->getCategory()->getEvent()->getId() !== $event->getId()) {
            throw $this->createNotFoundException();
        }

        if ($request->isMethod('POST')) {
            $this->hydrateBilletFromRequest($billet, $request);
            $this->syncBilletToStripe($billet, $user, $stripeService);
            $em->flush();

            $audit->log('billet_updated', 'Billet', $billet->getId(), ['name' => $billet->getName(), 'event' => $event->getTitle()]);

            $this->addFlash('success', 'Billet modifie avec succes.');

            return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
        }

        return $this->render('account/edit_billet.html.twig', [
            'event' => $event,
            'billet' => $billet,
            'allowedTypes' => self::getAllowedBilletTypes($user->getOffer()),
            'breadcrumbs' => [
                self::BREADCRUMB_HOME,
                self::BREADCRUMB_ACCOUNT,
                ['name' => $event->getTitle(), 'url' => self::EVENT_BASE_URL.$event->getId().self::EVENT_CATEGORIES_SUFFIX],
                ['name' => 'Modifier un billet'],
            ],
        ]);
    }

    #[Route('/mon-compte/evenement/{id}/billet/{billetId}/supprimer', name: 'app_account_event_delete_billet', methods: ['POST'])]
    public function deleteBillet(Event $event, int $billetId, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $user = $this->requireEventOwnership($event);

        $billet = $em->getRepository(Billet::class)->find($billetId);
        if (!$billet || $billet->getCategory()->getEvent()->getId() !== $event->getId()) {
            throw $this->createNotFoundException();
        }

        $billetName = $billet->getName();
        $billetDbId = $billet->getId();
        $this->deleteBilletFromStripe($billet, $user, $stripeService);

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

        $audit->log('billet_deleted', 'Billet', $billetDbId, ['name' => $billetName, 'event' => $event->getTitle()]);

        $this->addFlash('success', 'Billet supprime avec succes.');

        return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'categories']);
    }

    #[Route('/mon-compte/evenement/{id}/billet/reorder', name: 'app_account_event_reorder_billets', methods: ['POST'])]
    public function reorderBillets(Event $event, Request $request, EntityManagerInterface $em): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);

        $order = json_decode($request->getContent(), true);
        if (\is_array($order)) {
            foreach ($order as $position => $billetId) {
                $billet = $em->getRepository(Billet::class)->find($billetId);
                if ($billet && $billet->getCategory()->getEvent()->getId() === $event->getId()) {
                    $billet->setPosition($position);
                }
            }
            $em->flush();
        }

        return $this->json(['success' => true]);
    }

    #[Route('/mon-compte/evenement/{id}/billet-preview', name: 'app_account_event_billet_preview', methods: ['GET'])]
    public function billetPreview(Event $event, Request $request): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $user = $this->requireEventOwnership($event);

        return $this->render('account/billet_preview.html.twig', [
            'event' => $event,
            'user' => $user,
            'bg_color' => '#ffffff',
            'text_color' => '#111111',
            'accent_color' => $request->query->getString('accent_color', '#4f46e5'),
            'show_logo' => true,
            'show_invitation' => true,
            'invitation_title' => $request->query->getString('invitation_title', 'Invitation'),
            'invitation_color' => $request->query->getString('invitation_color', '#d4a017'),
        ]);
    }

    #[Route('/mon-compte/evenement/{id}/billet-design', name: 'app_account_event_save_billet_design', methods: ['POST'])]
    public function saveBilletDesign(Event $event, Request $request, EntityManagerInterface $em): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);

        $design = $em->getRepository(BilletDesign::class)->findOneBy(['event' => $event]);
        if (!$design) {
            $design = new BilletDesign();
            $design->setEvent($event);
            $em->persist($design);
        }

        $design->setAccentColor($request->request->getString('accent_color', '#4f46e5'));
        $design->setInvitationTitle($request->request->getString('invitation_title', 'Invitation'));
        $design->setInvitationColor($request->request->getString('invitation_color', '#d4a017'));
        $design->setUpdatedAt(new \DateTimeImmutable());

        $em->flush();

        return $this->json(['success' => true]);
    }

    #[Route('/mon-compte/evenement/{id}/en-ligne', name: 'app_account_toggle_event_online', methods: ['POST'])]
    public function toggleEventOnline(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $user = $this->requireEventOwnership($event);

        if (!$event->isOnline() && (!$user->isStripeChargesEnabled() || !$user->isStripePayoutsEnabled())) { // @codeCoverageIgnoreStart
            $this->addFlash('error', 'Configuration Stripe requise pour mettre un evenement en ligne.');

            return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId()]);
        } // @codeCoverageIgnoreEnd

        $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_account_edit_event', ['id' => $event->getId()]);
    }

    #[Route('/mon-compte/evenement/{id}/secret', name: 'app_account_toggle_event_secret', methods: ['POST'])]
    public function toggleEventSecret(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $this->requireEventOwnership($event);

        $event->setIsSecret(!$event->isSecret());
        $em->flush();

        $eventIndex->indexEvent($event);

        $this->addFlash('success', $event->isSecret() ? 'Evenement marque comme secret.' : 'Evenement rendu public.');

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

    #[Route('/mon-compte/evenement/{id}/qrcode', name: 'app_account_event_qrcode', methods: ['GET'])]
    public function eventQrCode(Event $event, UrlGeneratorInterface $urlGenerator): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ORGANIZER');
        if ($redirect = $this->requireStripeReady()) { // @codeCoverageIgnoreStart
            return $redirect;
        } // @codeCoverageIgnoreEnd

        $user = $this->requireEventOwnership($event);

        $eventUrl = $urlGenerator->generate('app_event_detail', [
            'orgaSlug' => $user->getSlug(),
            'id' => $event->getId(),
            'eventSlug' => $event->getSlug(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        $qrCode = (new \Endroid\QrCode\Builder\Builder(
            writer: new \Endroid\QrCode\Writer\PngWriter(),
            data: $eventUrl,
            encoding: new \Endroid\QrCode\Encoding\Encoding('UTF-8'),
            size: 400,
            margin: 20,
        ))->build();

        return new Response($qrCode->getString(), 200, [
            'Content-Type' => 'image/png',
            'Content-Disposition' => 'attachment; filename="qrcode-'.$event->getSlug().'.png"',
        ]);
    }

    /**
     * @return string[]
     */
    public static function getAllowedBilletTypes(?string $offer): array
    {
        return match ($offer) {
            'basic', 'sur-mesure' => ['billet', 'reservation_brocante', 'vote'],
            default => ['billet'],
        };
    }

    private function requireCategory(int $categoryId, Event $event, EntityManagerInterface $em): Category
    {
        $category = $em->getRepository(Category::class)->find($categoryId);
        if (!$category || $category->getEvent()->getId() !== $event->getId()) {
            throw $this->createNotFoundException();
        }

        return $category;
    }

    private function hydrateBilletFromRequest(Billet $billet, Request $request): void
    {
        $billet->setName(trim($request->request->getString('name')));
        $billet->setPriceHT((int) round((float) $request->request->getString('price_ht') * 100));
        $qty = $request->request->getString('quantity');
        $billet->setQuantity('' === $qty ? null : (int) $qty);
        $billet->setIsGeneratedBillet($request->request->getBoolean('is_generated_billet'));
        $billet->setHasDefinedExit($request->request->getBoolean('has_defined_exit'));
        $billet->setNotBuyable($request->request->getBoolean('not_buyable'));

        $type = $request->request->getString('type', 'billet');
        /** @var User $user */
        $user = $this->getUser();
        $allowedTypes = self::getAllowedBilletTypes($user->getOffer());
        if (!\in_array($type, $allowedTypes, true)) {
            $type = 'billet';
        }
        $billet->setType($type);

        $billet->setDescription(trim($request->request->getString('description')) ?: null);

        $pictureFile = $request->files->get('picture');
        if ($pictureFile) {
            $billet->setPictureFile($pictureFile);
        }
    }

    /**
     * @codeCoverageIgnore Requires live Stripe API
     */
    private function syncBilletToStripe(Billet $billet, User $user, StripeService $stripeService): void
    {
        if (!$user->getStripeAccountId()) {
            return;
        }

        try {
            if ($billet->getStripeProductId()) {
                $stripeService->updateProduct($billet, $user->getStripeAccountId());
            } else {
                $productId = $stripeService->createProduct($billet, $user->getStripeAccountId());
                $billet->setStripeProductId($productId);
            }
        } catch (\Exception) {
            // Stripe failure is non-blocking
        }
    }

    /**
     * @codeCoverageIgnore Requires live Stripe API
     */
    private function deleteBilletFromStripe(Billet $billet, User $user, StripeService $stripeService): void
    {
        if ($billet->getStripeProductId() && $user->getStripeAccountId()) {
            try {
                $stripeService->deleteProduct($billet->getStripeProductId(), $user->getStripeAccountId());
            } catch (\Exception) {
                // Stripe failure is non-blocking
            }
        }
    }
}
