<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Twig\Components\Marketing;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Site\ProductReviewSubmissionType;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Mailer\ReviewModerationMailer;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Messenger\Messaging\CommandBusInterface;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Notification\Command\NotificationCommand;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Notification\Enum\PushLevel;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Notification\Enum\PushTransport;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Twig\Components\Behavior\HasTemplateOverride;
use CpCreation\VitiCore\Cart\Model\CartItem;
use CpCreation\VitiCore\Marketing\DTO\ProductReviewSubmission;
use CpCreation\VitiCore\Marketing\Model\ProductReview;
use CpCreation\VitiCore\Marketing\Model\ProductReviewModerationAction;
use CpCreation\VitiCore\Marketing\Model\ReviewInvitation;
use CpCreation\VitiCore\Marketing\ProductReviewHistoryRecorder;
use CpCreation\VitiCore\Marketing\Repository\ProductReviewRepository;
use CpCreation\VitiCore\Marketing\Repository\ReviewInvitationRepository;
use CpCreation\VitiCore\Marketing\Repository\ReviewRequestConfigurationRepository;
use CpCreation\VitiCore\Marketing\ReviewAuthorDisplayNameFormatter;
use CpCreation\VitiCore\Product\Model\Product;
use Psr\Log\LoggerInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent('marketing:ProductReviewForm', template: '@components/marketing/ProductReviewForm.html.twig')]
final class ProductReviewForm
{
    use ComponentWithFormTrait;
    use DefaultActionTrait;
    use HasTemplateOverride;

    #[LiveProp]
    public string $token = '';

    #[LiveProp]
    public bool $saved = false;

    private ?ReviewInvitation $invitation = null;

    /** @var array<string, CartItem>|null */
    private ?array $eligibleItems = null;

    public function __construct(
        private readonly FormFactoryInterface $formFactory,
        private readonly ReviewInvitationRepository $invitationRepository,
        private readonly ReviewRequestConfigurationRepository $configurationRepository,
        private readonly ProductReviewRepository $reviewRepository,
        private readonly ReviewAuthorDisplayNameFormatter $authorDisplayNameFormatter,
        private readonly ProductReviewHistoryRecorder $historyRecorder,
        private readonly ReviewModerationMailer $moderationMailer,
        private readonly TranslatorInterface $translator,
        private readonly CommandBusInterface $commandBus,
        private readonly ?LoggerInterface $logger = null,
    ) {}

    public function hasEligibleProducts(): bool
    {
        return [] !== $this->resolveEligibleItems();
    }

    #[LiveAction]
    public function submitReview(): void
    {
        $this->saved = false;

        try {
            $this->submitForm();
        } catch (UnprocessableEntityHttpException) {
            $this->notify(
                $this->translator->trans('marketing.review.form.invalid'),
                PushLevel::ERROR,
            );

            return;
        }

        $submission = $this->form?->getData();
        if (
            !$submission instanceof ProductReviewSubmission
            || null === $submission->productId
            || null === $submission->comment
        ) {
            throw new UnprocessableEntityHttpException();
        }

        $item = $this->resolveEligibleItems()[$submission->productId] ?? null;
        $product = $item?->getVariant()?->getProduct();
        if (!$item instanceof CartItem || !$product instanceof Product) {
            throw new UnprocessableEntityHttpException(
                $this->translator->trans('marketing.review.form.product_invalid'),
            );
        }

        $configuration = $this->configurationRepository->findOne();
        if (null === $configuration || !$configuration->isEnabled()) {
            throw new NotFoundHttpException();
        }

        $invitation = $this->resolveInvitation();
        $authorDisplayName = $this->authorDisplayNameFormatter->format(
            $invitation->getCart(),
            $this->translator->trans('marketing.review.form.anonymous'),
        );
        $review = $this->reviewRepository->findForInvitationAndProduct($invitation, $product);
        $resubmission = $review instanceof ProductReview;

        if ($resubmission) {
            if (ProductReview::STATUS_REJECTED !== $review->getStatus()) {
                throw new UnprocessableEntityHttpException(
                    $this->translator->trans('marketing.review.form.product_invalid'),
                );
            }
            $this->historyRecorder->record(
                review: $review,
                action: ProductReviewModerationAction::ACTION_RESUBMITTED,
                actorIdentifier: 'customer',
            );
            $review->resubmit(
                cartItem: $item,
                rating: $submission->rating,
                authorDisplayName: $authorDisplayName,
                title: null,
                comment: $submission->comment,
                moderationRequired: $configuration->isModerationRequired(),
            );
        } else {
            $review = new ProductReview(
                invitation: $invitation,
                product: $product,
                cartItem: $item,
                rating: $submission->rating,
                authorDisplayName: $authorDisplayName,
                title: null,
                comment: $submission->comment,
                moderationRequired: $configuration->isModerationRequired(),
            );
            $this->historyRecorder->record(
                review: $review,
                action: ProductReviewModerationAction::ACTION_SUBMITTED,
                actorIdentifier: 'customer',
            );
        }

        $this->reviewRepository->persist($review);
        $invitation->markSubmitted();
        $this->invitationRepository->persist($invitation);
        $this->reviewRepository->flush();

        try {
            $this->moderationMailer->sendSubmissionNotification($review, $resubmission);
        } catch (\Throwable $exception) {
            $this->logger?->error('La notification de dépôt d’un avis n’a pas pu être envoyée.', [
                'review_id' => $review->getId()->toString(),
                'exception' => $exception,
            ]);
        }

        $this->eligibleItems = null;
        $this->saved = true;
        $this->notify(
            $this->translator->trans($this->hasEligibleProducts()
                ? 'marketing.review.form.saved_next'
                : 'marketing.review.form.saved'),
            PushLevel::SUCCESS,
        );
        $this->resetForm();
        $this->isValidated = false;
        $this->validatedFields = [];
    }

    protected function instantiateForm(): FormInterface
    {
        return $this->formFactory->create(ProductReviewSubmissionType::class, new ProductReviewSubmission(), [
            'product_choices' => $this->productChoices(),
        ]);
    }

    private function resolveInvitation(): ReviewInvitation
    {
        if ($this->invitation instanceof ReviewInvitation) {
            return $this->invitation;
        }

        $configuration = $this->configurationRepository->findOne();
        $invitation = $this->invitationRepository->findOneByToken($this->token);

        if (
            null === $configuration
            || !$configuration->isEnabled()
            || !$invitation instanceof ReviewInvitation
            || !$invitation->isUsableAt(new \DateTimeImmutable())
        ) {
            throw new NotFoundHttpException();
        }

        return $this->invitation = $invitation;
    }

    /**
     * @return array<string, CartItem>
     */
    private function resolveEligibleItems(): array
    {
        if (null !== $this->eligibleItems) {
            return $this->eligibleItems;
        }

        $eligibleItems = [];

        foreach ($this->resolveInvitation()->getCart()->getItems() as $item) {
            $product = $item->getVariant()?->getProduct();
            if (!$product instanceof Product || $this->reviewRepository->hasReview($this->resolveInvitation(), $product)) {
                continue;
            }

            $eligibleItems[$product->getId()->toString()] = $item;
        }

        return $this->eligibleItems = $eligibleItems;
    }

    /**
     * @return array<string, string>
     */
    private function productChoices(): array
    {
        $choices = [];

        foreach ($this->resolveEligibleItems() as $productId => $item) {
            $product = $item->getVariant()?->getProduct();
            if ($product instanceof Product) {
                $choices[$product->getFullName()] = $productId;
            }
        }

        return $choices;
    }

    private function notify(string $message, PushLevel $level): void
    {
        $this->commandBus->dispatch(new NotificationCommand(
            message: $message,
            level: $level,
            transport: PushTransport::MERCURE,
        ));
    }
}
