<?php

namespace App\Controller\Api;

use App\Entity\Billet;
use App\Entity\BilletBuyerItem;
use App\Entity\BilletOrder;
use App\Entity\Category;
use App\Entity\Event;
use App\Entity\User;
use App\Service\MailerService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

/**
 * @codeCoverageIgnore Requires DB + JWT auth integration
 */
#[Route('/api/live')]
class ApiLiveController extends AbstractController
{
    use ApiAuthTrait;

    private const ERR_EVENT = 'Evenement introuvable.';
    private const ERR_BILLET = 'Billet introuvable.';
    private const ERR_CATEGORY = 'Categorie introuvable.';

    public function __construct(
        #[Autowire('%kernel.secret%')] private string $appSecret,
    ) {
    }

    private function isRoot(User $user): bool
    {
        return \in_array('ROLE_ROOT', $user->getRoles(), true);
    }

    #[Route('/events', name: 'app_api_live_events', methods: ['GET'])]
    public function events(Request $request, EntityManagerInterface $em): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        $criteria = $this->isRoot($user) ? [] : ['account' => $user];
        $events = $em->getRepository(Event::class)->findBy($criteria, ['startAt' => 'DESC']);

        $data = array_map(fn (Event $e) => [
            'id' => $e->getId(),
            'title' => $e->getTitle(),
            'startAt' => $e->getStartAt()?->format(\DateTimeInterface::ATOM),
            'endAt' => $e->getEndAt()?->format(\DateTimeInterface::ATOM),
            'address' => $e->getAddress(),
            'zipcode' => $e->getZipcode(),
            'city' => $e->getCity(),
            'isOnline' => $e->isOnline(),
            'isSecret' => $e->isSecret(),
            'imageUrl' => $e->getEventMainPictureName() ? $request->getSchemeAndHttpHost().'/uploads/events/'.$e->getEventMainPictureName() : null,
        ], $events);

        return $this->success($data);
    }

    #[Route('/events/{id}', name: 'app_api_live_event', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function event(int $id, Request $request, EntityManagerInterface $em): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        $event = $em->getRepository(Event::class)->find($id);
        if (!$event || (!$this->isRoot($user) && $event->getAccount()->getId() !== $user->getId())) {
            return $this->error(self::ERR_EVENT, 404);
        }

        return $this->success([
            'id' => $event->getId(),
            'title' => $event->getTitle(),
            'description' => $event->getDescription(),
            'startAt' => $event->getStartAt()?->format(\DateTimeInterface::ATOM),
            'endAt' => $event->getEndAt()?->format(\DateTimeInterface::ATOM),
            'address' => $event->getAddress(),
            'zipcode' => $event->getZipcode(),
            'city' => $event->getCity(),
            'isOnline' => $event->isOnline(),
            'isSecret' => $event->isSecret(),
            'imageUrl' => $event->getEventMainPictureName() ? $request->getSchemeAndHttpHost().'/uploads/events/'.$event->getEventMainPictureName() : null,
        ]);
    }

    #[Route('/events/{id}/categories', name: 'app_api_live_categories', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function categories(int $id, Request $request, EntityManagerInterface $em): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        $event = $em->getRepository(Event::class)->find($id);
        if (!$event || (!$this->isRoot($user) && $event->getAccount()->getId() !== $user->getId())) {
            return $this->error(self::ERR_EVENT, 404);
        }

        $categories = $em->getRepository(Category::class)->findBy(['event' => $event], ['position' => 'ASC']);

        $data = array_map(fn (Category $c) => [
            'id' => $c->getId(),
            'name' => $c->getName(),
            'position' => $c->getPosition(),
            'startAt' => $c->getStartAt()->format(\DateTimeInterface::ATOM),
            'endAt' => $c->getEndAt()->format(\DateTimeInterface::ATOM),
            'isHidden' => $c->isHidden(),
            'isActive' => $c->isActive(),
        ], $categories);

        return $this->success($data);
    }

    #[Route('/categories/{id}/billets', name: 'app_api_live_billets', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function billets(int $id, Request $request, EntityManagerInterface $em): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        $category = $em->getRepository(Category::class)->find($id);
        if (!$category || (!$this->isRoot($user) && $category->getEvent()->getAccount()->getId() !== $user->getId())) {
            return $this->error(self::ERR_CATEGORY, 404);
        }

        $billets = $em->getRepository(Billet::class)->findBy(['category' => $category], ['position' => 'ASC']);

        $soldCounts = [];
        $billetIds = array_map(fn (Billet $b) => $b->getId(), $billets);
        if ($billetIds) {
            $rows = $em->createQueryBuilder()
                ->select('IDENTITY(bo.billet) AS billetId, COUNT(bo.id) AS cnt')
                ->from(BilletOrder::class, 'bo')
                ->where('bo.billet IN (:ids)')
                ->setParameter('ids', $billetIds)
                ->groupBy('bo.billet')
                ->getQuery()
                ->getArrayResult();
            foreach ($rows as $row) {
                $soldCounts[$row['billetId']] = (int) $row['cnt'];
            }
        }

        $data = array_map(fn (Billet $b) => [
            'id' => $b->getId(),
            'name' => $b->getName(),
            'priceHT' => $b->getPriceHT(),
            'quantity' => $b->getQuantity(),
            'sold' => $soldCounts[$b->getId()] ?? 0,
            'type' => $b->getType(),
            'isGeneratedBillet' => $b->isGeneratedBillet(),
            'notBuyable' => $b->isNotBuyable(),
            'position' => $b->getPosition(),
        ], $billets);

        return $this->success($data);
    }

    #[Route('/billets/{id}', name: 'app_api_live_billet', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function billet(int $id, Request $request, EntityManagerInterface $em): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        $billet = $em->getRepository(Billet::class)->find($id);
        if (!$billet || (!$this->isRoot($user) && $billet->getCategory()->getEvent()->getAccount()->getId() !== $user->getId())) {
            return $this->error(self::ERR_BILLET, 404);
        }

        $sold = $em->createQueryBuilder()
            ->select('COUNT(bo.id)')
            ->from(BilletOrder::class, 'bo')
            ->where('bo.billet = :billet')
            ->setParameter('billet', $billet)
            ->getQuery()
            ->getSingleScalarResult();

        return $this->success([
            'id' => $billet->getId(),
            'name' => $billet->getName(),
            'description' => $billet->getDescription(),
            'priceHT' => $billet->getPriceHT(),
            'quantity' => $billet->getQuantity(),
            'sold' => (int) $sold,
            'type' => $billet->getType(),
            'isGeneratedBillet' => $billet->isGeneratedBillet(),
            'hasDefinedExit' => $billet->hasDefinedExit(),
            'notBuyable' => $billet->isNotBuyable(),
            'position' => $billet->getPosition(),
            'imageUrl' => $billet->getPictureName() ? $request->getSchemeAndHttpHost().'/uploads/billets/'.$billet->getPictureName() : null,
            'category' => ['id' => $billet->getCategory()->getId(), 'name' => $billet->getCategory()->getName()],
            'event' => ['id' => $billet->getCategory()->getEvent()->getId(), 'title' => $billet->getCategory()->getEvent()->getTitle()],
        ]);
    }

    #[Route('/scan', name: 'app_api_live_scan', methods: ['POST'])]
    public function scan(Request $request, EntityManagerInterface $em): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        $data = json_decode($request->getContent(), true) ?? [];
        $reference = $data['reference'] ?? '';
        $securityKey = $data['securityKey'] ?? '';

        if ('' === $reference && '' === $securityKey) {
            return $this->error('Reference ou cle de securite requise.', 400);
        }

        $ticket = null;
        if ('' !== $reference) {
            $ticket = $em->getRepository(BilletOrder::class)->findOneBy(['reference' => $reference]);
        } elseif ('' !== $securityKey) {
            $ticket = $em->getRepository(BilletOrder::class)->findOneBy(['securityKey' => strtoupper($securityKey)]);
        }

        if (!$ticket || (!$this->isRoot($user) && $ticket->getBilletBuyer()->getEvent()->getAccount()->getId() !== $user->getId())) {
            return $this->error(self::ERR_BILLET, 404);
        }

        $isOwner = $ticket->getBilletBuyer()->getEvent()->getAccount()->getId() === $user->getId();
        $canForce = $this->isRoot($user) || ($isOwner && \in_array('ROLE_ORGANIZER', $user->getRoles(), true));

        return $this->success($this->processScan($ticket, $em, $canForce));
    }

    #[Route('/scan/force', name: 'app_api_live_scan_force', methods: ['POST'])]
    public function scanForce(Request $request, EntityManagerInterface $em, MailerService $mailerService): JsonResponse
    {
        $user = $this->authenticateRequest($request, $em, $this->appSecret);
        if ($user instanceof JsonResponse) {
            return $user;
        }

        if (!$this->isRoot($user) && !\in_array('ROLE_ORGANIZER', $user->getRoles(), true)) {
            return $this->error('Acces reserve aux organisateurs.', 403);
        }

        $data = json_decode($request->getContent(), true) ?? [];
        $reference = $data['reference'] ?? '';

        if ('' === $reference) {
            return $this->error('Reference requise.', 400);
        }

        $ticket = $em->getRepository(BilletOrder::class)->findOneBy(['reference' => $reference]);

        if (!$ticket) {
            return $this->error(self::ERR_BILLET, 404);
        }

        $event = $ticket->getBilletBuyer()->getEvent();
        $isOwner = $event->getAccount()->getId() === $user->getId();

        if (!$this->isRoot($user) && !$isOwner) {
            return $this->error('Acces reserve aux organisateurs.', 403);
        }

        $previousState = $ticket->getState();
        $ticket->setState(BilletOrder::STATE_VALID);
        $ticket->setFirstScannedAt(new \DateTimeImmutable());
        $em->flush();

        $organizer = $event->getAccount();
        $html = $this->renderView('email/scan_force_notification.html.twig', [
            'event_title' => $event->getTitle(),
            'billet_name' => $ticket->getBilletName(),
            'reference' => $ticket->getReference(),
            'buyer_name' => $ticket->getBilletBuyer()->getFirstName().' '.$ticket->getBilletBuyer()->getLastName(),
            'previous_state' => $previousState,
            'forced_by_name' => $user->getFirstName().' '.$user->getLastName(),
            'forced_by_email' => $user->getEmail(),
        ]);
        $mailerService->sendEmail(
            $organizer->getEmail(),
            'Validation forcee d\'un billet - '.$event->getTitle(),
            $html,
        );

        return $this->success($this->buildScanResponse('accepted', 'forced', $ticket));
    }

    /**
     * @return array<string, mixed>
     */
    private function processScan(BilletOrder $ticket, EntityManagerInterface $em, bool $canForce = false): array
    {
        $billetType = $ticket->getBillet()?->getType() ?? 'billet';
        $isAlwaysValid = \in_array($billetType, ['staff', 'exposant'], true);

        if (!$isAlwaysValid) {
            $buyerEmail = $ticket->getBilletBuyer()->getEmail();
            $buyerUser = $em->getRepository(User::class)->findOneBy(['email' => $buyerEmail]);
            if ($buyerUser && \in_array('ROLE_ROOT', $buyerUser->getRoles(), true)) {
                $isAlwaysValid = true;
            }
        }

        if (!$isAlwaysValid) {
            $reasonMap = [BilletOrder::STATE_INVALID => 'invalid', BilletOrder::STATE_EXPIRED => 'expired'];

            if (isset($reasonMap[$ticket->getState()])) {
                $response = $this->buildScanResponse('refused', $reasonMap[$ticket->getState()], $ticket);
                $response['canForce'] = $canForce;

                return $response;
            }

            $scannedToday = null !== $ticket->getFirstScannedAt()
                && $ticket->getFirstScannedAt()->format('Y-m-d') === (new \DateTimeImmutable())->format('Y-m-d');

            if ($scannedToday && ($ticket->getBillet()?->hasDefinedExit() ?? false)) {
                $response = $this->buildScanResponse('refused', 'exit_definitive', $ticket);
                $response['canForce'] = $canForce;

                return $response;
            }
        }

        $alreadyScanned = null !== $ticket->getFirstScannedAt();

        if (!$alreadyScanned) {
            $ticket->setFirstScannedAt(new \DateTimeImmutable());
            $em->flush();
        }

        if ($isAlwaysValid) {
            return $this->buildScanResponse('accepted', null, $ticket);
        }

        $scannedToday = $alreadyScanned
            && $ticket->getFirstScannedAt()->format('Y-m-d') === (new \DateTimeImmutable())->format('Y-m-d');
        $reason = ($alreadyScanned && $scannedToday) ? 'already_scanned' : null;

        return $this->buildScanResponse('accepted', $reason, $ticket);
    }

    /**
     * @return array<string, mixed>
     */
    private function buildScanResponse(string $state, ?string $reason, BilletOrder $ticket): array
    {
        $order = $ticket->getBilletBuyer();

        $items = array_map(fn (BilletBuyerItem $i) => [
            'billetName' => $i->getBilletName(),
            'quantity' => $i->getQuantity(),
            'unitPriceHT' => $i->getUnitPriceHTDecimal(),
        ], $order->getItems()->toArray());

        return [
            'state' => $state,
            'reason' => $reason,
            'reference' => $ticket->getReference(),
            'billetName' => $ticket->getBilletName(),
            'buyerFirstName' => $order->getFirstName(),
            'buyerLastName' => $order->getLastName(),
            'buyerEmail' => $order->getEmail(),
            'isInvitation' => (bool) $ticket->isInvitation(),
            'billetType' => $ticket->getBillet()?->getType() ?? 'billet',
            'firstScannedAt' => $ticket->getFirstScannedAt()?->format(\DateTimeInterface::ATOM),
            'hasDefinedExit' => $ticket->getBillet()?->hasDefinedExit() ?? false,
            'order' => [
                'orderNumber' => $order->getOrderNumber(),
                'status' => $order->getStatus(),
                'totalHT' => $order->getTotalHTDecimal(),
                'paidAt' => $order->getPaidAt()?->format(\DateTimeInterface::ATOM),
                'items' => $items,
            ],
        ];
    }
}
