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

class PaymentStatusExtension implements ExtensionInterface
{
    private $em;
    protected $eventDispatcher;
    protected $serializer;
    protected $userManager;

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

    /**
     * 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();

        // Payment can be captured if it hasn't been captured before 
        if ($current_payment_status !== GetHumanStatus::STATUS_CAPTURED 
            && $current_payment_status != GetHumanStatus::STATUS_AUTHORIZED) 
        {
            // 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);
                } 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);
                } 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);
                } 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);
                } 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 {
                    // Bad request
                }

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

                $this->eventDispatcher->dispatch(
                    MLCEvents::FLUX,
                    new FluxEvent($flux)
                );

                // Invalidate (delete) notify token after payment is captured
                $this->em->remove($token);
            }
        }

        // 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)
    {
    }
}