<?php

/*
 * kohinos_cooperatic
 * Copyright (C) 2019-2020  ADML63
 * Copyright (C) 2020- Cooperatic
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
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\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',
                                              '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 ($extra_data != null) {
          $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 {
          $payment->setTotalAmount($form->get('montant')->getData()*100); // 1.23 EUR
          $payment->setClientId($this->getUser()->getId());
          $payment->setClientEmail($this->getUser()->getEmail());
        }

        $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());
    }

    /**
     * Fonction de retour sur le site par l'utilisateur après paiement
     *
     * @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();

        if ($payment->getStatus() == GetHumanStatus::STATUS_NEW) {
            // No notification arrived: execute Notify action
            // TODO: notify token isn't deleted
            $gateway->execute(new Notify($token));
        } else {
            // Invalidate token
            $this->payum->getHttpRequestVerifier()->invalidate($token);
        }

        // 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->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');
    }

}
