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

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\Debtor;
use Ferienpass\CoreBundle\Entity\Payment;
use Ferienpass\CoreBundle\Entity\PaymentItem;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Enum\PaymentStatus;
use Ferienpass\CoreBundle\Message\ReorderParticipantList;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

#[AsLiveComponent(route: 'live_component_admin')]
#[IsGranted('ROLE_ADMIN')]
class PaymentReverse extends AbstractController
{
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    #[LiveProp]
    public Payment $payment;

    #[LiveProp(writable: true)]
    public array $items = [];

    #[LiveProp(writable: true)]
    public bool $unpay = true;

    #[LiveProp(writable: true)]
    public bool $withdraw = false;

    #[LiveProp(writable: true)]
    public bool $partialReverse = false;

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

    #[LiveProp(writable: true)]
    #[Assert\PositiveOrZero]
    #[Assert\Expression(
        "this.partialReverse == false or this.partialReverseType != 'percent' or value <= 100",
        message: 'Der Prozentsatz darf nicht größer als 100% sein.'
    )]
    #[Assert\Expression(
        "this.partialReverse == false or this.partialReverseType != 'EUR' or (value * 100) <= this.getOriginalTotalAmount()",
        message: 'Der Betrag darf nicht größer als der Gesamtbetrag sein.'
    )]
    public float $partialReverseValue = 0;

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

    public ?Payment $success = null;
    public ?string $error = null;

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly WorkflowInterface $paymentStateMachine,
        private readonly MessageBusInterface $commandBus,
    ) {
    }

    public function mount(array $items = []): void
    {
        $this->items = array_map('intval', $items);
    }

    public function getOriginalTotalAmount(): int
    {
        $total = 0;
        foreach ($this->payment->getItems() as $item) {
            if (\in_array($item->getId(), $this->items, true)) {
                $total += $item->getAmount();
            }
        }

        return $total;
    }

    #[ExposeInTemplate]
    public function getTotalAmount(): int
    {
        $total = $this->getOriginalTotalAmount();

        if ($this->partialReverse) {
            if ('percent' === $this->partialReverseType) {
                $total = (int) round($total * (1 - $this->partialReverseValue / 100));
            } else {
                $total = (int) round($this->partialReverseValue * 100);
            }
        }

        return (int) $total;
    }

    #[ExposeInTemplate]
    public function getPayoutMode(): ?string
    {
        if (!$this->payment->getDebtor() instanceof Debtor) {
            return null;
        }

        return $this->payout;
    }

    #[LiveAction]
    public function save(): ?Response
    {
        $this->validate();

        $user = $this->getUser();
        if (!$user instanceof User) {
            throw $this->createAccessDeniedException();
        }

        $items = $this->payment->getItems()->filter(fn (PaymentItem $i) => \in_array($i->getId(), $this->items, true) && false !== $i->getAttendance()?->isPaid());

        if ($items->isEmpty()) {
            $this->error = 'Es wurde nichts storniert. Entweder wurde keine Auswahl getroffen, oder die Buchungen waren schon storniert.';

            return null;
        }

        $debtor = $this->payment->getDebtor();
        // Legacy
        if (!$debtor instanceof Debtor) {
            $debtor = array_unique(array_filter(array_map(static fn (PaymentItem $a) => $a->getAttendance()->getParticipant()->getUser()?->getDebtor(), $items->toArray())))[0] ?? null;
        }

        $reversalPayment = new Payment($debtor, user: $user);
        $reversalPayment->setBillingAddress($this->payment->getBillingAddress());
        $reversalPayment->setBillingEmail($this->payment->getBillingEmail());
        $originalTotal = $this->getOriginalTotalAmount();
        $newTotal = $this->getTotalAmount();
        $ratio = $originalTotal > 0 ? $newTotal / $originalTotal : 0;

        foreach ($items as $item) {
            $reversalPayment->addItem(new PaymentItem(
                $reversalPayment,
                (-1) * (int) round($item->getAmount() * $ratio),
                $item->getAttendance()
            ));
        }

        $this->paymentStateMachine->apply($reversalPayment, PaymentStatus::TRANSITION_PAY, [
            'reverse' => $this->unpay,
            'payout' => $this->getPayoutMode(),
        ]);

        if ($this->withdraw) {
            foreach ($reversalPayment->getItems() as $item) {
                if ($item->getAttendance()->isWithdrawn()) {
                    continue;
                }

                $item->getAttendance()->setStatus(AttendanceStatus::WITHDRAWN);

                $this->commandBus->dispatch(new ReorderParticipantList((string) $item->getAttendance()->getOffer()->getUuid()));
            }
        }

        $this->entityManager->persist($reversalPayment);
        $this->entityManager->flush();

        $this->success = $reversalPayment;

        return null;
    }
}
