<?php

namespace App\EventListener;

use Payum\Core\Extension\Context;
use Payum\Core\Extension\ExtensionInterface;
use Payum\Core\Model\PaymentInterface;
use Payum\Core\Request\Generic;
use Payum\Core\Request\GetHumanStatus;
use Payum\Core\Request\GetStatusInterface;
use Payum\Core\Bridge\Symfony\Event\ExecuteEvent;

use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use App\Events\MLCEvents;
use App\Events\FluxEvent;
use App\Entity\Flux;
use App\Entity\Payment;
use App\Entity\PaymentToken;
use App\Entity\Siege;
use App\Entity\User;
use App\Entity\Adherent;
use App\Entity\Prestataire;
use App\Entity\Geoloc;
use App\Entity\Groupe;
use App\Entity\Usergroup;
use App\Entity\AchatMonnaieAdherent;
use App\Entity\AchatMonnaiePrestataire;
use App\Entity\CotisationAdherent;
use App\Entity\CotisationPrestataire;
use App\Entity\Don;
use App\Utils\OperationUtils;
use App\Utils\TAVCotisationUtils;

class PaymentStatusExtension implements ExtensionInterface
{
    private $em;
    private $eventDispatcher;
    private $serializer;
    private $userManager;
    private $operationUtils;
    private $tavCotisationsUtils;
    private $container;

    /**
     * PaymentStatusExtension constructor.
     *
     * @param EntityManagerInterface $em
     */
    public function __construct(
        EntityManagerInterface $em, 
        EventDispatcherInterface $eventDispatcher, 
        SerializerInterface $serializer, 
        UserManagerInterface $userManager,
        OperationUtils $operationUtils,
        TAVCotisationUtils $tavCotisationsUtils,
        ContainerInterface $container
    ) {
        $this->em = $em;
        $this->eventDispatcher = $eventDispatcher;
        $this->serializer = $serializer;
        $this->userManager = $userManager;
        $this->operationUtils = $operationUtils;
        $this->tavCotisationsUtils = $tavCotisationsUtils;
        $this->container = $container;
    }

    /**
     * Fired after a payum 'execute' to detect payment status changes after a notification
     * {@inheritDoc}
     */
    public function onPostExecute(Context $context)
    {
        $request = $context->getRequest();
        if (false == $request instanceof Generic) {
            return;
        }
        if ($request instanceof GetStatusInterface) {
            return;
        }

        $payment = $request->getFirstModel();
        if (false == $payment instanceof PaymentInterface) {
            return;
        }

        try {
            $token = $request->getToken();
        } catch (\Exception $e) {
            return;
        }

        // Get current & new status
        $context->getGateway()->execute($status = new GetHumanStatus($payment));
        $current_payment_status = $payment->getStatus();
        $current_payment_is_recurrent = $payment->getIsRecurrent();

        // Payment can be captured if it hasn't been captured before 
        // Allow STATUS_CAPTURED and STATUS_AUTHORIZED for recurrent payment only.
        if ($current_payment_status !== GetHumanStatus::STATUS_CAPTURED 
            && $current_payment_status != GetHumanStatus::STATUS_AUTHORIZED
            || $current_payment_is_recurrent)
        {
            // If payment succesful, persist serialized 'Flux' stored in payment
            if ($status->getValue() == GetHumanStatus::STATUS_CAPTURED 
                || $status->getValue() == GetHumanStatus::STATUS_AUTHORIZED) 
            {
                $flux_array = json_decode($payment->getFluxData(), true);

                $type = $payment->getDescription();
                if (Payment::TYPE_ACHAT_MONNAIE_ADHERENT == $type) {
                    $flux = $this->serializer->deserialize(
                        $payment->getFluxData(),
                        AchatMonnaieAdherent::class,
                        'json',
                        ['disable_type_enforcement' => true]
                    );

                    $exp = $this->em->getRepository(Siege::class)->find($flux_array['expediteur']);
                    $flux->setExpediteur($exp);

                    $dest = $this->em->getRepository(Adherent::class)->find($flux_array['destinataire']);
                    $flux->setDestinataire($dest);

                    $op = $this->em->getRepository(User::class)->find($flux_array['operateur']);
                    $flux->setOperateur($op);
                    $flux->setReconverti(true);

                    if (null != $flux->getDon()) {
                        $flux->getDon()->setType(Don::TYPE_DON_ADHERENT);
                        $flux->getDon()->setOperateur($op);
                        $flux->getDon()->setExpediteur($dest);
                        $flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true]));
                    }
                } else if (Payment::TYPE_ACHAT_MONNAIE_PRESTA == $type) {
                    $flux = $this->serializer->deserialize(
                        $payment->getFluxData(),
                        AchatMonnaiePrestataire::class,
                        'json',
                        ['disable_type_enforcement' => true]
                    );

                    $exp = $this->em->getRepository(Siege::class)->find($flux_array['expediteur']);
                    $flux->setExpediteur($exp);

                    $dest = $this->em->getRepository(Prestataire::class)->find($flux_array['destinataire']);
                    $flux->setDestinataire($dest);

                    $op = $this->em->getRepository(User::class)->find($flux_array['operateur']);
                    $flux->setOperateur($op);
                    $flux->setReconverti(true);

                    if (null != $flux->getDon()) {
                        $flux->getDon()->setType(Don::TYPE_DON_PRESTATAIRE);
                        $flux->getDon()->setOperateur($op);
                        $flux->getDon()->setExpediteur($dest);
                        $flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true]));
                    }                    
                } else if (Payment::TYPE_COTISATION_ADHERENT == $type) {
                    $flux = $this->serializer->deserialize(
                        $payment->getFluxData(),
                        CotisationAdherent::class,
                        'json',
                        ['disable_type_enforcement' => true]
                    );

                    $exp = $this->em->getRepository(Adherent::class)->find($flux_array['expediteur']);
                    $flux->setExpediteur($exp);

                    $dest = $this->em->getRepository(Prestataire::class)->find($flux_array['destinataire']);
                    $flux->setDestinataire($dest);

                    $op = $this->em->getRepository(User::class)->find($flux_array['operateur']);
                    $flux->setOperateur($op);

                    $flux->setRecu(true);

                    if (null != $flux->getDon()) {
                        $flux->getDon()->setType(Don::TYPE_DON_ADHERENT);
                        $flux->getDon()->setOperateur($op);
                        $flux->getDon()->setExpediteur($exp);
                        $flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true]));
                    }
                } else if (Payment::TYPE_COTISATION_PRESTA == $type) {
                    $flux = $this->serializer->deserialize(
                        $payment->getFluxData(),
                        CotisationPrestataire::class,
                        'json',
                        ['disable_type_enforcement' => true]
                    );

                    $exp = $this->em->getRepository(Prestataire::class)->find($flux_array['expediteur']);
                    $flux->setExpediteur($exp);

                    $dest = $this->em->getRepository(Prestataire::class)->find($flux_array['destinataire']);
                    $flux->setDestinataire($dest);

                    $op = $this->em->getRepository(User::class)->find($flux_array['operateur']);
                    $flux->setOperateur($op);

                    $flux->setRecu(true);

                    if (null != $flux->getDon()) {
                        $flux->getDon()->setType(Don::TYPE_DON_PRESTATAIRE);
                        $flux->getDon()->setOperateur($op);
                        $flux->getDon()->setExpediteur($exp);
                        $flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true]));
                    }
                } else if (Payment::TYPE_ADHESION == $type)  {
                    $new_adherent_data = json_decode($payment->getExtraData());

                    $adherent = new Adherent();
                    $user = $this->userManager->createUser();
                    $usergroup = $this->em->getRepository(Usergroup::class)->findOneByName('Adherent');
                    $group = $this->em->getRepository(Groupe::class)->findOneBy(array('id' => $new_adherent_data->groupe->id));

                    $user->setEmail($new_adherent_data->user->email);
                    $user->setUsername($new_adherent_data->user->username);
                    $user->setFirstname($new_adherent_data->user->firstname);
                    $user->setLastname($new_adherent_data->user->lastname);
                    $user->setPlainPassword($new_adherent_data->user->plainPassword);
                    $user->setEnabled(true);
                    $user->addPossiblegroup($usergroup);
                    $user->addGroup($usergroup);
                    $user->addRole('ROLE_ADHERENT');
                    $user->setAdherent($adherent);
                    $adherent->setEcompte('0');
                    $adherent->setUser($user);
                    $adherent->setGroupe($group);

                    if ($adherent->getGeoloc() == null) {
                        $geoloc = new Geoloc();
                        $geoloc->setAdresse($new_adherent_data->geoloc->adresse);
                        $geoloc->setCpostal($new_adherent_data->geoloc->cpostal);
                        $geoloc->setVille($new_adherent_data->geoloc->ville);
                        $adherent->setGeoloc($geoloc);
                    }

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

                    // Create first cotisation
                    $flux = $this->serializer->deserialize(
                        $payment->getFluxData(),
                        CotisationAdherent::class,
                        'json',
                        ['disable_type_enforcement' => true]
                    );

                    $flux->setOperateur($user);
                    $flux->setExpediteur($adherent);
                    $flux->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(array('mlc' => true)));
                    $flux->setRole('Adherent');
                    $flux->setRecu(true);

                    // Update payment with new user id, remove user data
                    $payment->setClientId($user->getId());
                    $payment->setExtraData('');
                    $this->em->persist($payment);
                } else if (Payment::TYPE_PAIEMENT_COTISATION_TAV == $type || Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV) {
                    // TODO deal with recurrent payment (don only on first payment...)
                    $flux = $this->serializer->deserialize(
                        $payment->getFluxData(),
                        AchatMonnaieAdherent::class,
                        'json',
                        ['disable_type_enforcement' => true]
                    );

                    $exp = $this->em->getRepository(Siege::class)->find($flux_array['expediteur']);
                    $flux->setExpediteur($exp);

                    $dest = $this->em->getRepository(Adherent::class)->find($flux_array['destinataire']);
                    $flux->setDestinataire($dest);

                    $op = $this->em->getRepository(User::class)->find($flux_array['operateur']);
                    $flux->setOperateur($op);
                    $flux->setReconverti(true);

                    //For recurrent payment: save don only for first payment, otherwise remove don
                    //Use current payment status to decide (if first payment -> add don, if not -> remove don)
                    if (null != $flux->getDon() && $current_payment_status === GetHumanStatus::STATUS_NEW) {
                        $flux->getDon()->setType(Don::TYPE_DON_ADHERENT);
                        $flux->getDon()->setOperateur($op);
                        $flux->getDon()->setExpediteur($dest);
                        $flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true]));
                    } else {
                        $flux->setDon(null);
                    }
                } else {
                    // Bad request
                }

                $this->em->persist($flux);
                $this->operationUtils->executeOperations($flux);

                if (Payment::TYPE_PAIEMENT_COTISATION_TAV == $type || Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV) {
                    // Create new flux for cotisation, depending on process
                    if ($this->container->getParameter('household_based_allowance')) {
                        $this->tavCotisationsUtils->applyHouseholdAllowance($flux);
                    } else {
                        $this->tavCotisationsUtils->applyTauxCotisation($flux);
                    }
                }

                // Invalidate (delete) notify token after payment is captured
                // For recurrent payment: don't delete notify token because we'll be notified on next recurrence payments
                if(!$current_payment_is_recurrent) {
                    $this->em->remove($token);
                }

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

        // Update payment status with status received in payzen response
        $payment->setStatus($status->getValue());
        $this->em->persist($payment);
        $this->em->flush();
    }

    /**
     * {@inheritDoc}
     */
    public function onPreExecute(Context $context)
    {
    }

    /**
     * {@inheritDoc}
     */
    public function onExecute(Context $context)
    {
    }
}
