<?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\CoreBundle\MessageHandler;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Debtor;
use Ferienpass\CoreBundle\Entity\Payment;
use Ferienpass\CoreBundle\Entity\PaymentItem;
use Ferienpass\CoreBundle\Facade\PaymentsFacade;
use Ferienpass\CoreBundle\Message\DispatchMailing as DispatchMailingCommand;
use Ferienpass\CoreBundle\Message\MailingCopyMode;
use Ferienpass\CoreBundle\Message\SendMailing as SendMailingCommand;
use Ferienpass\CoreBundle\Repository\AttendanceRepository;
use Ferienpass\CoreBundle\Repository\UserRepository;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
use Symfony\Component\Uid\Uuid;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

#[AsMessageHandler(bus: 'command.bus')]
class DispatchMailing
{
    public function __construct(private readonly MessageBusInterface $eventBus, private readonly AttendanceRepository $attendances, private readonly EventDispatcherInterface $dispatcher, private readonly EntityManagerInterface $entityManager, private readonly UserRepository $userRepository)
    {
    }

    public function __invoke(DispatchMailingCommand $command, Uuid $uuid): void
    {
        $invokingUser = $command->getInvokingUserId() ? $this->userRepository->findOneBy(['uuid' => $command->getInvokingUserId()]) : null;

        // Resolve how the sender wants to be copied in. The address is always
        // the invoking user's current one — never anything client-supplied.
        $copyMode = $command->getCopyMode();
        $selfEmail = MailingCopyMode::None !== $copyMode && null !== $invokingUser ? (string) $invokingUser->getEmail() : '';
        $bcc = MailingCopyMode::Bcc === $copyMode && '' !== $selfEmail ? $selfEmail : null;

        foreach ($command->getEmails() as $email) {
            if ('' === $email) {
                continue;
            }

            if ($command->isWithPaymentLink()) {
                $payableAttendances = $this->findPayableAttendances($email);

                $possibleDebtors = array_unique(array_filter(array_map(static fn (Attendance $a) => $a->getParticipant()->getUser()?->getDebtor(), $payableAttendances)));
                $debtor = $possibleDebtors[0] ?? null;
                $debtor ??= new Debtor();

                $payment = Payment::fromAttendances($debtor, $payableAttendances, $this->dispatcher, user: $invokingUser);
                $payment->setBillingEmail($email);

                if ($debtor->getBalance() > 0) {
                    // Clamp the credit to the bill so an over-balance can't drive
                    // the payment negative (ledger integrity — SECURITY_REVIEW FP-012).
                    $balance = min($debtor->getBalance(), $payment->getTotalAmount());
                    $payment->addItem(new PaymentItem($payment, (-1) * $balance, type: 'discount'));
                }

                $this->entityManager->persist($payment);
            }

            $event = new SendMailingCommand($email, $command->getSubject(), $command->getContent(), $command->getContext(), $command->getAttachments(), $uuid, payment: $payment ?? null, bcc: $bcc);

            $this->eventBus->dispatch(
                new Envelope($event)
                    ->with(new DispatchAfterCurrentBusStamp())
            );
        }

        // "Copy to me": a single, non-personalized version (no recipient-specific
        // token resolution, no payment link) sent to the invoking user's current
        // email. Part of the same dispatch group so it shows up in the tracker.
        // ("BCC me" needs nothing here — it rides along on each message above.)
        if (MailingCopyMode::Copy === $copyMode && '' !== $selfEmail) {
            $copy = new SendMailingCommand($selfEmail, $command->getSubject(), $command->getContent(), $command->getContext(), $command->getAttachments(), $uuid, personalized: false);

            $this->eventBus->dispatch(
                new Envelope($copy)
                    ->with(new DispatchAfterCurrentBusStamp())
            );
        }
    }

    /**
     * @return Attendance[]
     */
    private function findPayableAttendances(string $email): array
    {
        /** @var QueryBuilder $qb */
        $qb = $this->attendances->createQueryBuilder('attendance');

        $qb
            ->innerJoin('attendance.participant', 'participant')
            ->innerJoin('attendance.offer', 'offer')
            ->leftJoin('participant.user', 'user')

            ->where($qb->expr()->orX($qb->expr()->eq('participant.email', ':email'), $qb->expr()->andX($qb->expr()->isNotNull('user'), $qb->expr()->eq('user.email', ':email'))))
            ->setParameter('email', $email)
        ;

        PaymentsFacade::addWhereLogic($qb);

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