<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Controller\Admin;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Mailer\ReviewModerationMailer;
use CpCreation\VitiCore\Marketing\Model\ProductReview;
use CpCreation\VitiCore\Marketing\Model\ProductReviewModerationAction;
use CpCreation\VitiCore\Marketing\ProductReviewHistoryRecorder;
use CpCreation\VitiCore\Marketing\Repository\ProductReviewRepository;
use Knp\Component\Pager\PaginatorInterface;
use Ramsey\Uuid\Uuid;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;

#[Route('/marketing/avis-produits')]
#[IsGranted(new Expression("is_granted('ROLE_ADMIN') or is_granted('ROLE_MARKETING')"))]
final class ProductReviewModerationController extends AbstractController
{
    private const int PAGE_SIZE = 50;

    public function __construct(
        private readonly ProductReviewRepository $reviewRepository,
        private readonly ReviewModerationMailer $moderationMailer,
        private readonly ProductReviewHistoryRecorder $historyRecorder,
        private readonly PaginatorInterface $paginator,
        private readonly TranslatorInterface $translator,
    ) {}

    #[Route('', name: 'admin_marketing_product_reviews', methods: ['GET'])]
    public function index(Request $request): Response
    {
        $filters = $this->resolveFilters($request);

        return $this->render('@CpCreationVitiCore/admin/marketing/product_review/index.html.twig', [
            'reviews' => $this->paginator->paginate(
                $this->reviewRepository->createAdminQueryBuilder(
                    '' !== $filters['search'] ? $filters['search'] : null,
                    '' !== $filters['product'] ? $filters['product'] : null,
                    0 !== $filters['rating'] ? $filters['rating'] : null,
                    'all' !== $filters['status'] ? $filters['status'] : null,
                ),
                max(1, $request->query->getInt('page', 1)),
                self::PAGE_SIZE,
            ),
            'filter_products' => $this->reviewRepository->findProductsForAdminFilter(),
            'filters' => $filters,
            'filter_query' => $this->filterQuery($request),
            'has_filters' => '' !== $filters['search']
                || '' !== $filters['product']
                || 0 !== $filters['rating']
                || 'all' !== $filters['status'],
            'moderation_reason_codes' => ProductReview::getAllowedModerationReasonCodes(),
        ]);
    }

    #[Route('/{id}', name: 'admin_marketing_product_review_moderate', methods: ['GET'])]
    public function moderate(Request $request, ProductReview $review): Response
    {
        $intent = $request->query->getString('intent');
        if (!\in_array($intent, ['publish', 'reject'], true)) {
            $intent = null;
        }

        return $this->render('@CpCreationVitiCore/admin/marketing/product_review/moderate.html.twig', [
            'review' => $review,
            'intent' => $intent,
            'moderation_reason_codes' => ProductReview::getAllowedModerationReasonCodes(),
        ]);
    }

    #[Route('/{id}/publier', name: 'admin_marketing_product_review_publish', methods: ['POST'])]
    #[IsGranted('ROLE_SUPER_ADMIN')]
    public function publish(Request $request, ProductReview $review): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('publish-review-' . $review->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', $this->translator->trans('marketing.review.moderation.invalid_request'));

            return $this->redirectToReviewList($request);
        }
        if (\in_array($review->getStatus(), [
            ProductReview::STATUS_PUBLISHED,
            ProductReview::STATUS_WITHDRAWN,
        ], true)) {
            return $this->redirectToReviewList($request);
        }

        $now = new \DateTimeImmutable();
        $review->publish($now);
        $this->historyRecorder->record(
            review: $review,
            action: ProductReviewModerationAction::ACTION_PUBLISHED,
            occurredAt: $now,
        );
        $this->reviewRepository->save($review);
        $this->addFlash('alert-success', $this->translator->trans('marketing.review.moderation.published'));

        return $this->redirectToReviewList($request);
    }

    #[Route('/{id}/rejeter', name: 'admin_marketing_product_review_reject', methods: ['POST'])]
    #[IsGranted('ROLE_SUPER_ADMIN')]
    public function reject(Request $request, ProductReview $review): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('reject-review-' . $review->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', $this->translator->trans('marketing.review.moderation.invalid_request'));

            return $this->redirectToReviewList($request);
        }
        if (\in_array($review->getStatus(), [
            ProductReview::STATUS_REJECTED,
            ProductReview::STATUS_PUBLISHED,
            ProductReview::STATUS_WITHDRAWN,
        ], true)) {
            return $this->redirectToReviewList($request);
        }

        $reasonCode = (string) $request->request->get('reason_code');
        if (!\in_array($reasonCode, ProductReview::getAllowedModerationReasonCodes(), true)) {
            $this->addFlash('alert-danger', $this->translator->trans('marketing.review.moderation.reason_required'));

            return $this->redirectToReviewList($request);
        }

        $reasonDetail = $this->normalizeOptionalText($request->request->get('reason_detail'));
        $now = new \DateTimeImmutable();
        $review->reject($reasonCode, $reasonDetail, $now);
        $this->historyRecorder->record(
            review: $review,
            action: ProductReviewModerationAction::ACTION_REJECTED,
            reasonCode: $reasonCode,
            reasonDetail: $reasonDetail,
            occurredAt: $now,
        );
        $this->reviewRepository->save($review);
        $this->moderationMailer->sendRejection($review);
        $this->addFlash('alert-success', $this->translator->trans('marketing.review.moderation.rejected'));

        return $this->redirectToReviewList($request);
    }

    #[Route('/{id}/retirer', name: 'admin_marketing_product_review_withdraw', methods: ['POST'])]
    #[IsGranted('ROLE_SUPER_ADMIN')]
    public function withdraw(Request $request, ProductReview $review): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('withdraw-review-' . $review->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', $this->translator->trans('marketing.review.moderation.invalid_request'));

            return $this->redirectToReviewList($request);
        }
        if (ProductReview::STATUS_PUBLISHED !== $review->getStatus()) {
            return $this->redirectToReviewList($request);
        }

        $reasonCode = (string) $request->request->get('reason_code');
        if (!\in_array($reasonCode, ProductReview::getAllowedModerationReasonCodes(), true)) {
            $this->addFlash('alert-danger', $this->translator->trans('marketing.review.moderation.withdrawal_reason_required'));

            return $this->redirectToReviewList($request);
        }

        $reasonDetail = $this->normalizeOptionalText($request->request->get('reason_detail'));
        $now = new \DateTimeImmutable();
        $review->withdraw($reasonCode, $reasonDetail, $now);
        $this->historyRecorder->record(
            review: $review,
            action: ProductReviewModerationAction::ACTION_WITHDRAWN,
            reasonCode: $reasonCode,
            reasonDetail: $reasonDetail,
            occurredAt: $now,
        );
        $this->reviewRepository->save($review);
        $this->moderationMailer->sendWithdrawal($review);
        $this->addFlash('alert-success', $this->translator->trans('marketing.review.moderation.withdrawn'));

        return $this->redirectToReviewList($request);
    }

    #[Route('/{id}/repondre', name: 'admin_marketing_product_review_reply', methods: ['POST'])]
    public function reply(Request $request, ProductReview $review): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('reply-review-' . $review->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', $this->translator->trans('marketing.review.moderation.invalid_request'));

            return $this->redirectToReviewList($request);
        }
        if (ProductReview::STATUS_PUBLISHED !== $review->getStatus()) {
            return $this->redirectToReviewList($request);
        }

        $reply = $this->normalizeOptionalText($request->request->get('reply'), 5000);
        $review->reply($reply);
        $this->historyRecorder->record(
            review: $review,
            action: null === $reply
                ? ProductReviewModerationAction::ACTION_REPLY_REMOVED
                : ProductReviewModerationAction::ACTION_REPLY_SAVED,
        );
        $this->reviewRepository->save($review);
        $this->addFlash('alert-success', $this->translator->trans(
            null === $reply
                ? 'marketing.review.moderation.reply_removed'
                : 'marketing.review.moderation.reply_saved',
        ));

        return $this->redirectToReviewList($request);
    }

    /**
     * @return array{search: string, product: string, rating: int, status: string}
     */
    private function resolveFilters(Request $request): array
    {
        $search = mb_substr(mb_trim($request->query->getString('search')), 0, 200);
        $product = mb_trim($request->query->getString('product'));
        $rating = $request->query->filter(
            'rating',
            null,
            \FILTER_VALIDATE_INT,
            ['flags' => \FILTER_NULL_ON_FAILURE],
        );
        $status = $request->query->getString('status', 'all');
        if (!\in_array($status, [
            'all',
            ProductReview::STATUS_PENDING,
            ProductReview::STATUS_PUBLISHED,
            ProductReview::STATUS_REJECTED,
            ProductReview::STATUS_WITHDRAWN,
        ], true)) {
            $status = 'all';
        }

        return [
            'search' => $search,
            'product' => Uuid::isValid($product) ? $product : '',
            'rating' => \is_int($rating) && $rating >= 1 && $rating <= 5 ? $rating : 0,
            'status' => $status,
        ];
    }

    /** @return array<string, int|string> */
    private function filterQuery(Request $request): array
    {
        $filters = $this->resolveFilters($request);
        $query = array_filter(
            $filters,
            static fn(int|string $value): bool => '' !== $value && 0 !== $value,
        );
        if ('all' === ($query['status'] ?? null)) {
            unset($query['status']);
        }
        $page = max(1, $request->query->getInt('page', 1));
        if ($page > 1) {
            $query['page'] = $page;
        }

        return $query;
    }

    private function redirectToReviewList(Request $request): RedirectResponse
    {
        if ('detail' === $request->query->getString('return')) {
            return $this->redirectToRoute('admin_marketing_product_review_moderate', [
                'id' => $request->attributes->getString('id'),
            ]);
        }

        return $this->redirectToRoute('admin_marketing_product_reviews', $this->filterQuery($request));
    }

    private function normalizeOptionalText(mixed $value, int $maxLength = 2000): ?string
    {
        if (!\is_scalar($value) && !$value instanceof \Stringable) {
            return null;
        }

        $value = mb_trim((string) $value);

        return '' === $value ? null : mb_substr($value, 0, $maxLength);
    }
}
