<?php

namespace App\Controller\Api;

use App\Entity\Billet;
use App\Entity\BilletOrder;
use App\Entity\Category;
use App\Entity\Event;
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,
    ) {
    }

    #[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;
        }

        $events = $em->getRepository(Event::class)->findBy(['account' => $user], ['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 || $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 || $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 || $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 || $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;
        }

        $reference = (json_decode($request->getContent(), true) ?? [])['reference'] ?? '';
        $ticket = '' !== $reference ? $em->getRepository(BilletOrder::class)->findOneBy(['reference' => $reference]) : null;

        if (!$ticket || $ticket->getBilletBuyer()->getEvent()->getAccount()->getId() !== $user->getId()) {
            return $this->error(!$ticket && '' === $reference ? 'Reference requise.' : self::ERR_BILLET, '' === $reference ? 400 : 404);
        }

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

    /**
     * @return array<string, mixed>
     */
    private function processScan(BilletOrder $ticket, EntityManagerInterface $em): array
    {
        $reasonMap = [BilletOrder::STATE_INVALID => 'invalid', BilletOrder::STATE_EXPIRED => 'expired'];

        if (isset($reasonMap[$ticket->getState()])) {
            return $this->buildScanResponse('refused', $reasonMap[$ticket->getState()], $ticket);
        }

        if (null !== $ticket->getFirstScannedAt() && ($ticket->getBillet()?->hasDefinedExit() ?? false)) {
            return $this->buildScanResponse('refused', 'exit_definitive', $ticket);
        }

        if (null === $ticket->getFirstScannedAt()) {
            $ticket->setFirstScannedAt(new \DateTimeImmutable());
            $em->flush();
        }

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

    /**
     * @return array<string, mixed>
     */
    private function buildScanResponse(string $state, ?string $reason, BilletOrder $ticket): array
    {
        return [
            'state' => $state,
            'reason' => $reason,
            'reference' => $ticket->getReference(),
            'billetName' => $ticket->getBilletName(),
            'buyerFirstName' => $ticket->getBilletBuyer()->getFirstName(),
            'buyerLastName' => $ticket->getBilletBuyer()->getLastName(),
            'isInvitation' => (bool) $ticket->isInvitation(),
            'firstScannedAt' => $ticket->getFirstScannedAt()?->format(\DateTimeInterface::ATOM),
            'hasDefinedExit' => $ticket->getBillet()?->hasDefinedExit() ?? false,
            'details' => 'accepted' === $state ? [] : null,
        ];
    }
}
