<?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\Files;

use Contao\CoreBundle\Filesystem\FilesystemItem;
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\AdminBundle\Controller\Page\FilesController;
use Ferienpass\CoreBundle\Entity\Dbafs;
use Ferienpass\CoreBundle\Entity\DbafsAttachment;
use Ferienpass\CoreBundle\Entity\DbafsMedia;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;

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

    #[LiveProp]
    public ?string $storage = null;

    #[LiveProp(hydrateWith: 'hydrateItem', dehydrateWith: 'dehydrateItem')]
    public ?FilesystemItem $item = null;

    #[LiveProp(writable: ['alt', 'creator'], hydrateWith: 'hydrateModel', dehydrateWith: 'dehydrateModel')]
    public ?DbafsMedia $mediaModel = null;

    #[LiveProp(writable: ['title'], hydrateWith: 'hydrateModel', dehydrateWith: 'dehydrateModel')]
    public ?DbafsAttachment $attachmentModel = null;

    #[LiveProp]
    public ?string $isEditing = null;
    #[LiveProp]
    public bool $addToOffer = false;

    #[LiveProp]
    public ?string $offerUuid = null;

    #[LiveProp(writable: true)]
    public string $offerSearch = '';

    public function __construct(private readonly EntityManagerInterface $entityManager, private readonly OfferRepositoryInterface $offerRepository)
    {
    }

    public function mount(?FilesystemItem $item = null, ?string $storage = null): void
    {
        $this->item = $item;
        $this->storage = $storage;

        $model = $item?->getExtraMetadata()->get('model');
        if ($model instanceof DbafsMedia) {
            $this->mediaModel = $model;
        } elseif ($model instanceof DbafsAttachment) {
            $this->attachmentModel = $model;
        }
    }

    public function dehydrateItem(?FilesystemItem $item): ?string
    {
        if (!$item) {
            return null;
        }

        return $item->getPath();
    }

    public function hydrateItem(?string $data): ?FilesystemItem
    {
        if (!$data) {
            return null;
        }

        /** @noinspection MissingServiceXml */
        return $this->container->get("contao.filesystem.virtual.{$this->storage}")->get($data);
    }

    public function dehydrateModel(?Dbafs $model): ?array
    {
        if (!$model) {
            return null;
        }

        return [
            'class' => $model::class,
            'uuid' => $model->getUuid()->toRfc4122(),
        ];
    }

    public function hydrateModel(?array $data): ?Dbafs
    {
        if (!$data) {
            return null;
        }

        return $this->entityManager->find($data['class'], $data['uuid']);
    }

    public static function getSubscribedServices(): array
    {
        $services = parent::getSubscribedServices();

        foreach (FilesController::STORAGES as $storage) {
            $services["contao.filesystem.virtual.$storage"] = VirtualFilesystemInterface::class;
        }

        return $services;
    }

    #[LiveAction]
    public function activateEditing(#[LiveArg] string $field): void
    {
        $this->isEditing = $field;
    }

    #[LiveAction]
    public function toggleAddToOffer(): void
    {
        $this->addToOffer = !$this->addToOffer;
    }

    #[LiveAction]
    public function save(EntityManagerInterface $entityManager): void
    {
        $this->validate();

        $entityManager->flush();

        $this->isEditing = null;
    }

    public function getOffers(): array
    {
        if (\strlen($this->offerSearch) < 2) {
            return [];
        }

        $qb = $this->offerRepository
            ->createQueryBuilder('o')
            ->where('o.name LIKE :search OR o.id = :id')
            ->setParameter('search', '%'.$this->offerSearch.'%')
            ->setParameter('id', (int) $this->offerSearch)
            ->setMaxResults(10);

        if (!$this->isGranted('ROLE_ADMIN')) {
            $qb->innerJoin('o.hosts', 'h')
                ->innerJoin('h.memberAssociations', 'ma')
                ->andWhere('ma.user = :user')
                ->setParameter('user', $this->getUser());
        }

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

    #[LiveAction]
    public function assignToOffer(#[LiveArg] int $id, OfferRepositoryInterface $repository, EntityManagerInterface $entityManager): void
    {
        $offer = $repository->find($id);
        $model = $this->mediaModel ?? $this->attachmentModel;
        if (!$offer || !$model) {
            return;
        }

        $this->denyAccessUnlessGranted('edit', $offer);

        if ($model instanceof DbafsMedia) {
            $offer->addMedia($model);
        } elseif ($model instanceof DbafsAttachment) {
            $offer->addAttachment($model);
        }

        $entityManager->flush();
        $this->offerSearch = '';
    }

    #[LiveAction]
    public function assignToCurrentOffer(OfferRepositoryInterface $repository, EntityManagerInterface $entityManager): void
    {
        if (!$this->offerUuid) {
            return;
        }

        $offer = $repository->findByUuid($this->offerUuid);
        $model = $this->mediaModel ?? $this->attachmentModel;
        if (!$offer || !$model) {
            return;
        }

        if ($model instanceof DbafsMedia) {
            $offer->addMedia($model);
        } elseif ($model instanceof DbafsAttachment) {
            $offer->addAttachment($model);
        }

        $entityManager->flush();

        $this->emit('fileAssigned', [
            'uuid' => $model->getUuid()->toRfc4122(),
        ]);
    }

    #[LiveAction]
    public function delete(EntityManagerInterface $entityManager): void
    {
        if (!$this->isGranted('ROLE_ADMIN')) {
            throw $this->createAccessDeniedException();
        }

        $model = $this->mediaModel ?? $this->attachmentModel;
        if (!$model) {
            return;
        }

        $uuid = $model->getUuid();

        if ($model instanceof DbafsMedia) {
            foreach ($model->getOffers() as $om) {
                $entityManager->remove($om);
            }
        } elseif ($model instanceof DbafsAttachment) {
            foreach ($model->getOffers() as $oa) {
                $entityManager->remove($oa);
            }
            foreach ($model->getNotifications() as $notification) {
                $notification->setEmailAttachment(null);
            }
        }

        $entityManager->remove($model);
        $entityManager->flush();

        if ($uuid && $this->item?->getStorage()->has($uuid->toRfc4122())) {
            $this->item->getStorage()->delete($uuid->toRfc4122());
        }

        $this->emitUp('fileDeleted');
    }
}
