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

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Mailer\ReviewInvitationMailer;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\Marketing\Model\ReviewInvitation;
use CpCreation\VitiCore\Marketing\Model\ReviewRequestConfiguration;
use CpCreation\VitiCore\Marketing\Repository\ReviewInvitationRepository;
use CpCreation\VitiCore\Marketing\Repository\ReviewRequestConfigurationRepository;
use CpCreation\VitiCore\Marketing\Repository\ReviewSolicitationOptOutRepository;
use CpCreation\VitiCore\Marketing\ReviewInvitationIssuer;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'viti-core:marketing:test-review-request',
    description: 'Teste une demande d’avis sur la dernière commande livrée ou sur un panier précis.'
)]
final class TestReviewRequestCommand extends Command
{
    public function __construct(
        private readonly CartRepository $cartRepository,
        private readonly ReviewRequestConfigurationRepository $configurationRepository,
        private readonly ReviewInvitationRepository $invitationRepository,
        private readonly ReviewSolicitationOptOutRepository $optOutRepository,
        private readonly ReviewInvitationIssuer $invitationIssuer,
        private readonly ReviewInvitationMailer $mailer,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addArgument('cart', InputArgument::OPTIONAL, 'UUID ou référence du panier. La dernière commande livrée sans invitation est utilisée si omis.')
            ->addOption('send', null, InputOption::VALUE_NONE, 'Envoie l’invitation à l’adresse email du panier.')
            ->addOption('send-to', null, InputOption::VALUE_REQUIRED, 'Redirige l’invitation vers cette adresse email de test.')
            ->addOption('force', null, InputOption::VALUE_NONE, 'Avec --send ou --send-to, ignore l’activation, les dates de campagne et le désabonnement du destinataire.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $cartIdentifier = $this->normalizeString($input->getArgument('cart'));
        $send = (bool) $input->getOption('send');
        $sendTo = $this->normalizeString($input->getOption('send-to'));
        $force = (bool) $input->getOption('force');

        if ($send && null !== $sendTo) {
            $io->error('Les options --send et --send-to ne peuvent pas être utilisées ensemble.');

            return Command::INVALID;
        }

        $willSend = $send || null !== $sendTo;
        $cart = $this->resolveCart($cartIdentifier);

        if (!$cart instanceof Cart) {
            $io->error(null === $cartIdentifier
                ? 'Aucune commande livrée sans invitation d’avis n’a été trouvée.'
                : sprintf('Aucun panier n’a été trouvé pour « %s ».', $cartIdentifier));

            return Command::FAILURE;
        }

        if (!$cart->isCheckoutPayed() || !$cart->isDelivery() || null === $cart->getFulfilledAt()) {
            $io->error('Le panier sélectionné doit être payé et marqué comme livré.');

            return Command::FAILURE;
        }

        $configuration = $this->configurationRepository->findOne();
        if (!$configuration instanceof ReviewRequestConfiguration) {
            $io->error('La campagne de demandes d’avis n’est pas encore configurée.');

            return Command::FAILURE;
        }

        $now = new \DateTimeImmutable();
        $customerEmail = $cart->getEmail();
        $recipientEmail = $sendTo ?? $customerEmail;
        $existingInvitation = $this->invitationRepository->findOneBy(['cart' => $cart]);
        $validCustomerEmail = null !== $customerEmail
            && false !== filter_var($customerEmail, \FILTER_VALIDATE_EMAIL);
        $validRecipientEmail = null !== $recipientEmail
            && false !== filter_var($recipientEmail, \FILTER_VALIDATE_EMAIL);
        $afterActivation = $cart->getFulfilledAt() >= $configuration->getFulfilledAfter();
        $delayReached = $cart->getFulfilledAt() <= $now->modify(sprintf(
            '-%d days',
            $configuration->getSendDelayDays(),
        ));
        $optedOut = $validRecipientEmail && $this->optOutRepository->hasOptedOut($recipientEmail);

        $io->title('Test de demande d’avis');
        $io->definitionList(
            ['Panier' => sprintf('%s (%s)', $cart->getToken() ?: '-', $cart->getId()->toString())],
            ['Livré le' => $cart->getFulfilledAt()->format('d/m/Y H:i')],
            ['Email client' => $customerEmail ?? '-'],
            ['Destinataire évalué' => $recipientEmail ?? '-'],
            ['Campagne active' => $configuration->isEnabled() ? 'oui' : 'non'],
            ['Délai configuré' => sprintf('%d jour(s)', $configuration->getSendDelayDays())],
            ['Durée du lien' => sprintf('%d jour(s)', $configuration->getTokenLifetimeDays())],
            ['Mode' => $willSend ? sprintf('envoi réel vers %s', $recipientEmail ?? '-') : 'simulation sans écriture']
        );
        $io->table(
            ['Email valide', 'Après activation', 'Délai atteint', 'Désabonné', 'Invitation existante'],
            [[
                $validRecipientEmail ? 'oui' : 'non',
                $afterActivation ? 'oui' : 'non',
                $delayReached ? 'oui' : 'non',
                $optedOut ? 'oui' : 'non',
                $existingInvitation instanceof ReviewInvitation ? 'oui' : 'non',
            ]],
        );

        if (!$willSend) {
            $io->success('Simulation terminée. Utilisez --send pour écrire à l’adresse du panier ou --send-to=adresse@example.com pour rediriger le test.');

            return Command::SUCCESS;
        }

        if (!$validRecipientEmail || null === $recipientEmail) {
            $io->error($send
                ? 'Le panier ne possède pas d’adresse email valide.'
                : 'L’adresse fournie avec --send-to n’est pas valide.');

            return Command::INVALID;
        }

        if ($existingInvitation instanceof ReviewInvitation) {
            $io->error('Une invitation d’avis existe déjà pour ce panier.');

            return Command::FAILURE;
        }

        if (!$force && (
            !$configuration->isEnabled()
            || !$validCustomerEmail
            || !$afterActivation
            || !$delayReached
            || $optedOut
        )) {
            $io->error('Cette commande n’est pas encore éligible à la campagne. Utilisez --force pour effectuer malgré tout un envoi de test.');

            return Command::FAILURE;
        }

        $issued = $this->invitationIssuer->issue(
            cart: $cart,
            recipientEmail: $recipientEmail,
            tokenLifetimeDays: $configuration->getTokenLifetimeDays(),
            now: $now,
        );
        $this->invitationRepository->flush();

        try {
            $this->mailer->send($issued->invitation, $issued->token, $configuration);
            $issued->invitation->markSent($now);
            $this->invitationRepository->save($issued->invitation);
        } catch (\Throwable $exception) {
            $this->invitationRepository->delete($issued->invitation);
            $io->error(sprintf('L’email de test n’a pas pu être envoyé : %s', $exception->getMessage()));

            return Command::FAILURE;
        }

        $io->success(sprintf(
            'Invitation d’avis créée et envoyée à %s. Le lien reçu est réel et valable jusqu’au %s.',
            $recipientEmail,
            $issued->invitation->getExpiresAt()->format('d/m/Y H:i'),
        ));
        $io->note('Cette invitation est enregistrée : la campagne ne sollicitera pas de nouveau le client de ce panier.');

        return Command::SUCCESS;
    }

    private function resolveCart(?string $identifier): ?Cart
    {
        if (null === $identifier) {
            return $this->cartRepository->findLatestFulfilledCartWithoutReviewInvitation();
        }

        try {
            $cart = $this->cartRepository->find($identifier);
        } catch (\Throwable) {
            $cart = null;
        }

        if ($cart instanceof Cart) {
            return $cart;
        }

        $cart = $this->cartRepository->findOneBy(['token' => mb_strtoupper($identifier)]);

        return $cart instanceof Cart ? $cart : null;
    }

    private function normalizeString(mixed $value): ?string
    {
        if (!\is_scalar($value) && !$value instanceof \Stringable) {
            return null;
        }

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

        return '' !== $value ? $value : null;
    }
}
