<?php

namespace App\Utils;

use App\Entity\AchatMonnaieAdherent;
use App\Entity\AchatMonnaiePrestataire;
use App\Entity\Adherent;
use App\Entity\CotisationAdherent;
use App\Entity\CotisationPrestataire;
use App\Entity\Don;
use App\Entity\Geoloc;
use App\Entity\Groupe;
use App\Entity\Payment;
use App\Entity\Prestataire;
use App\Entity\Siege;
use App\Entity\User;
use App\Entity\Usergroup;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Payum\Core\Request\GetHumanStatus;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Serializer\SerializerInterface;

class PaymentUtils
{
    private $em;
    private $serializer;
    private $userManager;
    private $operationUtils;
    private $tavCotisationUtils;
    private $container;

    /**
     * PaymentUtils constructor.
     */
    public function __construct(
        EntityManagerInterface $em,
        SerializerInterface $serializer,
        UserManagerInterface $userManager,
        OperationUtils $operationUtils,
        TAVCotisationUtils $tavCotisationUtils,
        ContainerInterface $container
    ) {
        $this->em = $em;
        $this->serializer = $serializer;
        $this->userManager = $userManager;
        $this->operationUtils = $operationUtils;
        $this->tavCotisationUtils = $tavCotisationUtils;
        $this->container = $container;
    }

    /**
     * @param $payment
     * @return void
     * @throws \Exception
     */
    public function handlePayzenNotificationCore($payment): void
    {
        $current_payment_status = $payment->getStatus();

        // Payment can be captured if it hasn't been captured before
        // Allow STATUS_CAPTURED and STATUS_AUTHORIZED for recurrent payment only.
        $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 == $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);

            //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 == $type) {
            $allocationMethod = $this->tavCotisationUtils->getAppropriateAllocationMethod($flux->getDestinataire());
            
            // Create new flux for cotisation, depending on process
            if ($allocationMethod == 'household_based_allowance' || $allocationMethod == 'simplified_household_based_allowance') {
                $this->tavCotisationUtils->applyHouseholdAllowance($flux);
            } else {
                $this->tavCotisationUtils->applyTauxCotisation($flux);
            }
        }
    }
}
