<?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\CmsBundle\Controller\Fragment;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CmsBundle\Form\SimpleType\ContaoRequestTokenType;
use Ferienpass\CoreBundle\Controller\AbstractPrivateFragmentController;
use Ferienpass\CoreBundle\Entity\Payment;
use Ferienpass\CoreBundle\Entity\PaymentItem;
use Ferienpass\CoreBundle\Payments\Provider\PaymentProviderInterface;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpClient\Exception\ServerException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

class PayController extends AbstractPrivateFragmentController
{
    public function __construct(private readonly PaymentProviderInterface $paymentProvider, private readonly EntityManagerInterface $entityManager)
    {
    }

    public function __invoke(Payment $payment, Request $request, Session $session): Response
    {
        if (!$payment->getBillingAddress()) {
            $form = $this->buildForm();

            $form->handleRequest($request);
            if ($form->isSubmitted() && $form->isValid()) {
                $this->updateBillingAddress($form, $payment);

                return $this->redirectToRoute('cms_pay', ['token' => $payment->getUuid()]);
            }

            return $this->render('@Contao/fragment/pay.html.twig', [
                'form' => $form,
                'payment' => $payment,
            ]);
        }

        // A payment should only be considered if the attendances are not paid yet to prevent overpayment
        $isValid = $payment->getItems()->forAll(static fn ($k, PaymentItem $i) => !($i->getAttendance()?->isPaid() ?? false) || !$i->getAttendance()->getOffer()->getFee());

        if (!$isValid) {
            return $this->render('@Contao/fragment/pay.html.twig', [
                'payment' => $payment,
            ]);
        }

        try {
            $payUrl = $this->paymentProvider->dispatchPayment($payment);
        } catch (ServerException|ClientException) {
            return $this->render('@Contao/fragment/pay.html.twig', [
                'paymentProviderError' => true,
                'payment' => $payment,
            ]);
        }

        $session->set('fp.processPayment', $payment->getId());

        return $this->render('@Contao/fragment/pay.html.twig', [
            'payment' => $payment,
            'payUrl' => $payUrl,
        ]);
    }

    private function updateBillingAddress(FormInterface $form, Payment $payment): void
    {
        $payment->setBillingAddress(
            <<<EOF
                {$form->get('name')->getData()}
                {$form->get('street')->getData()}
                {$form->get('postalCode')->getData()} {$form->get('city')->getData()}
                {$form->get('country')->getData()}
                EOF
        );

        $this->entityManager->flush();
    }

    private function buildForm(): FormInterface
    {
        return $this->createFormBuilder()
            ->add('name', TextType::class, ['label' => 'Vor- und Zuname'])
            ->add('street', TextType::class, ['label' => 'Straße + Hausnummer'])
            ->add('postalCode', TextType::class, ['label' => 'Postleitzahl'])
            ->add('city', TextType::class, ['label' => 'Ort'])
            ->add('country', CountryType::class, ['label' => 'Land', 'preferred_choices' => ['DE']])
            ->add('requestToken', ContaoRequestTokenType::class)
            ->add('submit', SubmitType::class, ['label' => 'Speichern'])
            ->getForm()
        ;
    }
}
