<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\AdminBundle\Components;

use Doctrine\DBAL\Types\Types;
use Ferienpass\CoreBundle\Entity\Edition;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Enum\OfferStatus;
use Ferienpass\CoreBundle\Export\Offer\OffersExportInterface;
use Ferienpass\CoreBundle\Message\ExportOffers;
use Ferienpass\CoreBundle\Repository\EditionRepository;
use Ferienpass\CoreBundle\Repository\HostRepository;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

#[AsLiveComponent(route: 'live_component_admin')]
final class Export extends AbstractController
{
    use ComponentToolsTrait;
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    #[LiveProp(writable: true)]
    #[Assert\NotBlank]
    public ?string $export = null;

    #[LiveProp]
    public ?Edition $edition = null;

    #[LiveProp(writable: true)]
    public array $selectedEditions = [];

    #[LiveProp(writable: true)]
    public array $selectedHosts = [];

    #[LiveProp(writable: true)]
    public bool $onlyPublished = true;

    public bool $noEntriesNotification = false;
    public bool $successNotification = false;

    public function __construct(private readonly OfferRepositoryInterface $offers, private readonly EditionRepository $editions, #[TaggedLocator(OffersExportInterface::class, indexAttribute: 'name', defaultIndexMethod: 'getName')] private readonly ServiceLocator $exporters, private readonly HostRepository $hostRepository)
    {
    }

    public function mount(?Edition $edition = null): void
    {
        $this->edition = $edition;
        if ($this->edition) {
            $this->selectedEditions = [$this->edition->getId()];
        }
    }

    #[ExposeInTemplate]
    public function exporterOptions(): array
    {
        return array_keys($this->exporters->getProvidedServices());
    }

    #[ExposeInTemplate]
    public function editionOptions(): array
    {
        return $this->editions->findBy(['archived' => 0]);
    }

    #[ExposeInTemplate]
    public function hostOptions(): array
    {
        return $this->hostRepository->findBy([]);
    }

    #[LiveListener('selectExport')]
    public function selectExport(#[LiveArg] string $name): void
    {
        $this->export = $name;
    }

    #[LiveAction]
    public function submit(#[CurrentUser] User $user, MessageBusInterface $commandBus): void
    {
        $this->validate();

        $offers = $this->queryOffers();
        if ([] === $offers) {
            $this->noEntriesNotification = true;

            return;
        }

        $uuids = array_map(static fn (string $uuid) => Uuid::fromBinary($uuid)->toRfc4122(), $offers);
        $commandBus->dispatch(new ExportOffers($this->export, $uuids, $user->getEmail()));

        $this->export = null;
        $this->successNotification = true;
    }

    private function queryOffers(): array
    {
        $qb = $this->offers
            ->createQueryBuilder('offer')
            ->select('offer.uuid')
            ->addSelect('MIN(dates.begin) AS earliestDate')
            ->leftJoin('offer.dates', 'dates')
            ->orderBy('earliestDate', 'ASC')
            ->groupBy('offer.uuid')
        ;

        if ($this->onlyPublished) {
            $published = OfferStatus::PUBLISHED;
            $qb->andWhere("JSON_CONTAINS_PATH(offer.status, 'one', '$.$published') = 1");
        }

        if ($this->selectedEditions) {
            $qb->andWhere('offer.edition IN (:editions)')->setParameter('editions', $this->selectedEditions, Types::SIMPLE_ARRAY);
        }

        if ($this->selectedHosts) {
            $qb->innerJoin('offer.hosts', 'hosts')->andWhere('hosts.id IN (:hosts)')->setParameter('hosts', $this->selectedHosts, Types::SIMPLE_ARRAY);
        }

        return $qb->getQuery()->getSingleColumnResult();
    }
}
