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

use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\AdminBundle\Section\SectionComponentInterface;
use Ferienpass\AdminBundle\Service\FileUploader;
use Ferienpass\CoreBundle\Entity\DbafsAttachment;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\OfferAttachment;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Validator\ValidatorInterface;
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;

#[AsLiveComponent(template: '@FerienpassAdmin/components/Offer/AttachmentManager.html.twig', route: 'live_component_admin')]
class AttachmentManager extends AbstractController implements SectionComponentInterface
{
    use ComponentToolsTrait;
    use DefaultActionTrait;

    #[LiveProp]
    public OfferInterface $offer;

    #[LiveProp(writable: ['agreementLetter'])]
    public ?OfferAttachment $activeAttachment = null;

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

    #[LiveProp(writable: true)]
    public ?string $activeAttachmentUuid = null;
    public bool $attachmentUnassigned = false;
    public bool $attachmentDeleted = false;
    public bool $attachmentUnassignedAndNotDeleted = false;
    public ?string $error = null;
    public bool $success = false;

    public string $message = '';

    #[LiveProp]
    public array $sections = [];

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

    #[LiveProp]
    public bool $isDirty = false;

    #[LiveProp]
    public bool $isReadonly = false;

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly FileUploader $fileUploader,
        private readonly VirtualFilesystemInterface $attachmentsStorage,
        private readonly ValidatorInterface $validator
    ) {
    }

    public static function getSectionName(): string
    {
        return 'attachments';
    }

    public static function getSectionScope(): string
    {
        return 'offer';
    }

    public function getPublicUrl(OfferAttachment $oa): string
    {
        return (string) $this->attachmentsStorage->generatePublicUri($oa->getAttachment()->getUuid());
    }

    #[LiveListener('fileAssigned')]
    public function onFileAssigned(): void
    {
        $this->denyAccessIfReadonly();

        $this->entityManager->refresh($this->offer);
    }

    #[LiveAction]
    public function reorderAttachments(#[LiveArg] array $uuids): void
    {
        $this->denyAccessIfReadonly();

        $sorting = 0;
        foreach ($uuids as $uuid) {
            foreach ($this->offer->getAttachments() as $oa) {
                if ($oa->getAttachment()->getUuid()->toRfc4122() === $uuid) {
                    $oa->setSorting($sorting++);
                    break;
                }
            }
        }

        $this->entityManager->flush();
        $this->entityManager->refresh($this->offer);
    }

    #[LiveAction]
    public function switchAttachmentAction(#[LiveArg] string $mode): void
    {
        $this->denyAccessIfReadonly();

        $this->attachmentAction = $mode;
    }

    #[LiveAction]
    public function unassignAttachment(#[LiveArg] string $uuid): void
    {
        $this->denyAccessIfReadonly();

        foreach ($this->offer->getAttachments() as $oa) {
            if ($oa->getAttachment()->getUuid()->toRfc4122() === $uuid) {
                $this->offer->removeAttachment($oa);
                $oa->getAttachment()->getOffers()->removeElement($oa);
                $this->entityManager->remove($oa);
                break;
            }
        }

        if ($this->activeAttachmentUuid === $uuid) {
            $this->activeAttachmentUuid = null;
        }

        $this->entityManager->flush();
        $this->entityManager->refresh($this->offer);

        $this->attachmentUnassigned = true;
    }

    #[LiveAction]
    public function deleteAttachment(#[LiveArg] string $uuid): void
    {
        $this->denyAccessIfReadonly();

        $attachment = $this->entityManager->find(DbafsAttachment::class, $uuid);
        if (!$attachment) {
            return;
        }

        foreach ($this->offer->getAttachments() as $oa) {
            if ($oa->getAttachment()->getUuid()->toRfc4122() === $uuid) {
                $this->offer->removeAttachment($oa);
                $attachment->getOffers()->removeElement($oa);
                $this->entityManager->remove($oa);
                break;
            }
        }

        $this->entityManager->flush();

        if ($attachment->getOffers()->isEmpty()) {
            $attachmentUuid = $attachment->getUuid();
            if ($attachmentUuid && $this->attachmentsStorage->has($attachmentUuid)) {
                $this->attachmentsStorage->delete($attachmentUuid);
            }

            $this->entityManager->remove($attachment);
            $this->attachmentDeleted = true;
        } else {
            $this->attachmentUnassignedAndNotDeleted = true;
        }

        if ($this->activeAttachmentUuid === $uuid) {
            $this->activeAttachmentUuid = null;
        }

        $this->entityManager->flush();
        $this->entityManager->refresh($this->offer);
    }

    #[LiveAction]
    public function resetAttachmentAction(): void
    {
        $this->denyAccessIfReadonly();

        $this->attachmentAction = '';
    }

    #[LiveListener('attachmentSelected')]
    public function onAttachmentSelected(#[LiveArg] string $uuid): void
    {
        $this->denyAccessIfReadonly();

        $this->attachmentAction = '';
        $this->activeAttachmentUuid = $uuid;

        $attachment = $this->entityManager->find(DbafsAttachment::class, $uuid);
        if ($attachment) {
            if (!$this->isGranted('view', $attachment)) {
                $this->error = 'Du hast keine Berechtigung, diesen Anhang zu verwenden.';

                return;
            }

            // Check if already assigned
            $found = false;
            foreach ($this->offer->getAttachments() as $oa) {
                if ($oa->getAttachment()->getUuid()->toRfc4122() === $uuid) {
                    $found = true;
                    $this->activeAttachment = $oa;
                    break;
                }
            }

            if (!$found) {
                $this->offer->addAttachment($attachment);
                foreach ($this->offer->getAttachments() as $oa) {
                    if ($oa->getAttachment()->getUuid()->toRfc4122() === $uuid) {
                        $this->activeAttachment = $oa;
                        break;
                    }
                }
            }

            // Re-sort attachments collection: selected one first (sorting 0)
            $attachmentCollection = $this->offer->getAttachments();
            $found = false;
            foreach ($attachmentCollection as $oa) {
                if ($oa->getAttachment()->getUuid()->toRfc4122() === $uuid) {
                    $oa->setSorting(0);
                    $found = true;
                    break;
                }
            }

            if ($found) {
                $i = 1;
                foreach ($attachmentCollection as $oa) {
                    if ($oa->getAttachment()->getUuid()->toRfc4122() !== $uuid) {
                        $oa->setSorting($i++);
                    }
                }
            }

            $this->entityManager->flush();
            $this->entityManager->refresh($this->offer);
        }
    }

    #[LiveAction]
    public function upload(Request $request): void
    {
        $this->denyAccessIfReadonly();

        $uploadedFile = $request->files->get('attachment');
        if (!$uploadedFile instanceof UploadedFile) {
            return;
        }

        $violations = $this->validator->validate($uploadedFile, [
            new File([
                'maxSize' => '6M',
                'mimeTypes' => [
                    'application/pdf',
                ],
            ]),
        ]);

        if ($violations->count() > 0) {
            foreach ($violations as $violation) {
                $this->error = $violation->getMessage();
            }

            return;
        }

        $uuid = $this->fileUploader->upload($this->attachmentsStorage, $uploadedFile);

        if ($uuid) {
            $attachment = $this->entityManager->getReference(DbafsAttachment::class, $uuid);
            $attachment->setUploadedBy($this->getUser());

            $this->offer->addAttachment($attachment);
            $this->entityManager->flush();
            $this->entityManager->refresh($this->offer);

            $this->attachmentAction = '';
            $this->activeAttachmentUuid = $uuid->toRfc4122();

            foreach ($this->offer->getAttachments() as $oa) {
                if ($oa->getAttachment()->getUuid()->toRfc4122() === $this->activeAttachmentUuid) {
                    $this->activeAttachment = $oa;
                    break;
                }
            }
        }
    }

    #[LiveAction]
    public function save(#[LiveArg] ?string $nextSection = null, #[LiveArg] ?string $redirect = null): ?Response
    {
        $this->denyAccessIfReadonly();

        $this->entityManager->flush();

        // Re-hydrate offer to ensure collection order is reflected if changed by re-sorting
        $this->entityManager->refresh($this->offer);

        $this->success = true;

        if ($nextSection || $redirect) {
            $this->addFlash('success', ['type' => 'success', 'title' => 'Daten gespeichert', 'message' => 'Die Änderungen wurden erfolgreich übernommen.']);
        } else {
            $this->message = 'Die Änderungen wurden erfolgreich übernommen.';
        }

        $this->dispatchBrowserEvent('live:saved', ['success' => true]);

        if ($nextSection) {
            return $this->redirectToRoute('admin_offers_edit', ['uuid' => $this->offer->getUuid()->toBase58(), 'section' => $nextSection]);
        }
        if ($redirect) {
            return $this->redirectToRoute($redirect, ['uuid' => $this->offer->getUuid()->toBase58()]);
        }

        return null;
    }

    /**
     * Two reasons the attachments section can be read-only:
     *  - the offer is a variant (its attachments are propagated from
     *    the base by {@see \Ferienpass\CoreBundle\MessageHandler\SyncOfferVariants});
     *  - the current user lacks the 'edit' voter (e.g. host editing a
     *    finalised offer).
     *
     * The template hides every mutation control in either case; this
     * is the defensive server-side backstop against a tampered request
     * or stale LiveProp. We re-check the voter rather than trusting
     * the {@see $isReadonly} LiveProp, which the client could flip.
     */
    private function denyAccessIfReadonly(): void
    {
        if ($this->offer->isVariant()) {
            throw new BadRequestHttpException('Cannot modify attachments on a variant offer.');
        }

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