<?php

namespace App\Controller;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Translation\TranslatorInterface;
use Payum\Core\Payum;
use Payum\Core\Request\Notify;
use Payum\Core\Request\GetHumanStatus;
use App\Entity\Flux;
use App\Entity\Payment;
use App\Entity\User;
use App\Entity\GlobalParameter;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use App\Security\LoginAuthenticator;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;

/**
 * Gestion des paiements avec Payum
 */
class PaymentController extends AbstractController
{
    protected $em;
    protected $translator;
    protected $payum;
    protected $authenticator;
    protected $guardHandler;

    public function __construct(EntityManagerInterface $em,
                                TranslatorInterface $translator,
                                LoginAuthenticator $authenticator,
                                GuardAuthenticatorHandler $guardHandler,
                                Payum $payum)
    {
        $this->em = $em;
        $this->translator = $translator;
        $this->payum = $payum;
        $this->authenticator = $authenticator;
        $this->guardHandler = $guardHandler;
    }

    /**
     * Crée une instance de Payment, les tokens associés, et redirige vers la page de paiement
     */
    public function preparePaymentAction(Form $form, $type, $extra_data = null)
    {
        // Enregistre les données du Flux en json, pour l'enregistrer une fois le paiement validé
        $serializer = $this->container->get('serializer');
        $toSerialize = Payment::TYPE_ADHESION == $type ? $form->get('cotisation')->getData() : $form->getData();
        $data = $serializer->normalize($toSerialize,
            null,
            [AbstractNormalizer::ATTRIBUTES => [
                'reference',
                'moyen',
                'montant',
                'role',
                'don' => [
                    'reference',
                    'moyen',
                    'montant',
                    'role',
                    'type',
                    'expediteur' => ['id'],
                    'destinataire' => ['id'],
                    'operateur' => ['id'],
                ],
                'expediteur' => ['id'],
                'destinataire' => ['id'],
                'operateur' => ['id']]
            ]);

        $jsondata = $serializer->serialize($data, 'json');

        // Prepare CB Payment
        if ($this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::USE_PAYZEN) === 'true') {
          $gatewayName = 'payzen';
        } else {
          $this->addFlash(
            'error',
            $this->translator->trans('Une erreur est survenue due à la configuration du paiement dans l\'application. Il est pour l\'instant impossible de payer par CB, merci de contacter votre monnaie locale.')
          );
          return $this->redirectToRoute('index');
        }

        $storage = $this->payum->getStorage('App\Entity\Payment');

        $payment = $storage->create();
        $payment->setNumber(uniqid());
        $payment->setCurrencyCode('978');
        $payment->setDescription($type);
        $payment->setFluxData($jsondata);

        // Data to persist when payment is valid (other than Flux data)
        if (null != $extra_data) {
            $payment->setExtraData($extra_data);
        }

        if ($type == Payment::TYPE_ADHESION) {
            $payment->setTotalAmount($form->get('cotisation')->get('montant')->getData()*100); // 1.23 EUR
            $payment->setClientId('Nouvel adhérent');
            $payment->setClientEmail($form->get('user')->get('email')->getData());
        } else {
            if ($form->has('don') && $form->get('don')->getData()->getMontant() > 0) {
                $payment->setTotalAmount(($form->get('montant')->getData() * 100) + ($form->get('don')->getData()->getMontant() * 100)); // 1.23 EUR
            } else {
                $payment->setTotalAmount($form->get('montant')->getData() * 100); // 1.23 EUR
            }

            $payment->setClientId($this->getUser()->getId());
            $payment->setClientEmail($this->getUser()->getEmail());
        }

        if ($type == Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV) {
          $payment->setRecurrenceAmount($form->get('montant')->getData() * 100);
          $payment->setIsRecurrent(true);
          $payment->setRecurrenceMonthsCount($form->get('nombreMois')->getData());
          $payment->setRecurrenceMonthDay($form->get('jourPrelevement')->getData());
        }

        $storage->update($payment);

        $captureToken = $this->payum->getTokenFactory()->createCaptureToken(
          $gatewayName,
          $payment,
          'payment_done' // the route to redirect after capture
        );

        // Symfony creates URLs with http and not https -> replace
        $targetUrl = preg_replace('/^http:/', 'https:', $captureToken->getTargetUrl());
        $afterUrl = preg_replace('/^http:/', 'https:', $captureToken->getAfterUrl());

        $captureToken->setTargetUrl($targetUrl);
        $captureToken->setAfterUrl($afterUrl);

        $this->em->persist($captureToken);
        $this->em->flush();

        return $this->redirect($captureToken->getTargetUrl());
    }

    /**
     *
     * Traitement des notifications Payzen.
     * @see doneAction
     *
     * @param Request $request
     * @return Response
     * @Route("/payment/notify/", name="payment_notify")
     */
    public function notifyAction(Request $request)
    {
        //TODO Security possible issue : someone else than Payzen could get the token and submit
        //TODO a recurring notification payment occurence here, which will be accepted...
        //TODO until blocked if user has already payed its cotisation...
        //TODO but if e.g. recurring payment time range is over, the kohinos
        //TODO would not realize it and will continue to trigger new payments on the system
        //TODO clean solution could be to detect last occurence of recurring payment
        //TODO and invalidate token at this point.

        try {
            $token = $this->payum->getHttpRequestVerifier()->verify($request);
        } catch (\Exception  $e) {
            // Token expired
            return new Response("NotifyAction controller : jeton déjà invalidé.");
        }
        // Get payment
        $gateway = $this->payum->getGateway($token->getGatewayName());
        $gateway->execute($status = new GetHumanStatus($token));
        $payment = $status->getFirstModel();
        if ($payment->getStatus() == GetHumanStatus::STATUS_NEW || $payment->getIsRecurrent()) {
            // No notification arrived at this point or payment is recurrent : execute Notify action
            $gateway->execute(new Notify($token));
            return new Response("NotifyAction controller : notification prise en compte.");
        } else {
            // Invalidate token
            $this->payum->getHttpRequestVerifier()->invalidate($token);
            return new Response("NotifyAction controller : jeton valide invalidé.");
        }
    }

    /**
     * Ce contrôleur est sollicité lorsque Payzen renvoie le cotisant sur l'URL de retour.
     *
     * Avant fin mars 2024, on pouvait croire que ce contrôleur était également sollicité lorsque Payzen notifie du paiement
     * via l'URL de notification, mais ce n'est pas le cas car la configuration du back office Payzen
     * est réécrite par la configuration par défaut du module Payum.
     *
     * C'est donc désormais le contrôleur notifyAction qui est sollicité
     * (ajout de ce contrôleur suite découverte bug lors de la mise en place du paiement récurrent,
     * jusqu'ici les notifications payzen n'étaient donc pas captées, ce qui ne pose pas nécessairement
     * problème car les utilisateurs sont redirigés vers l'URL de retour s'ils ne ferment pas leur navigateur.)
     *
     * Pour plus de clarté, on gagne à modifier finalement l'URL de retour Payzen dans le backoffice de sorte
     * à pointer vers notifyAction également.
     *
     * @Route("/payment/done/", name="payment_done")
     */
    public function doneAction(Request $request)
    {
        try {
            $token = $this->payum->getHttpRequestVerifier()->verify($request);
        } catch (\Exception  $e) {
            // Token expired
            return $this->redirectToRoute('index');
        }

        // Get payment
        $gateway = $this->payum->getGateway($token->getGatewayName());
        $gateway->execute($status = new GetHumanStatus($token));
        $payment = $status->getFirstModel();

        //Done action is normaly only accessed by users browser, not by Payzen agent,
        //so we should not be handling recurring payment after the initial payement.
        //We must indeed prevent a user from a browser reload e.g.

        if ($payment->getStatus() == GetHumanStatus::STATUS_NEW) {
            // No notification arrived at this point : execute Notify action
            // Recurring payment should not trigger notify action here except on initial payment
            // as only Payzen agent is allowed to trigger occurences after the first one
            $gateway->execute(new Notify($token));
        } elseif (!$payment->getIsRecurrent()) {
            // Invalidate token, except if payment is recurrent
            $this->payum->getHttpRequestVerifier()->invalidate($token);
        } else {
            //maybe user is trying to trigger another occurence of a recurring payment
            return $this->redirectToRoute('index');
        }

        // Set flash message according to payment status
        if ($payment->getStatus() == GetHumanStatus::STATUS_CAPTURED || $payment->getStatus() == GetHumanStatus::STATUS_AUTHORIZED) {
          $type = $payment->getDescription();

          if (Payment::TYPE_ACHAT_MONNAIE_ADHERENT == $type || Payment::TYPE_ACHAT_MONNAIE_PRESTA == $type) {
            $this->addFlash(
              'success',
              $this->translator->trans('Achat de monnaie locale bien effectué !')
            );
          } else if (Payment::TYPE_COTISATION_ADHERENT == $type || Payment::TYPE_COTISATION_PRESTA == $type) {
            $this->addFlash(
              'success',
              $this->translator->trans('Cotisation bien reçue. Merci !')
            );
          } else if (Payment::TYPE_ADHESION == $type) {
            $this->addFlash(
              'success',
              $this->translator->trans('Votre adhésion a bien été prise en compte, bienvenue !')
            );

            // Connect new user
            return $this->guardHandler
              ->authenticateUserAndHandleSuccess(
                $this->em->getRepository(User::class)->findOneBy(array('id' => $payment->getClientId())),
                $request,
                $this->authenticator,
                'main'
            );
          } else if (Payment::TYPE_PAIEMENT_COTISATION_TAV == $type || Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV)  {
            $this->addFlash(
              'success',
              $this->translator->trans('Cotisation payée !')
            );
          }
        } else if ($payment->getStatus() == GetHumanStatus::STATUS_CANCELED ||
                    $payment->getStatus() == GetHumanStatus::STATUS_EXPIRED ||
                    $payment->getStatus() == GetHumanStatus::STATUS_FAILED) 
        {
          $this->addFlash(
            'error',
            $this->translator->trans('La transaction a été annulée.')
          );
        }        

        return $this->redirectToRoute('index');
    }

}
