<?php

namespace App\Controller;

use App\Entity\Adherent;
use App\Entity\CotisationAdherent;
use App\Entity\CotisationPrestataire;
use App\Entity\GlobalParameter;
use App\Entity\Payment;
use App\Entity\Prestataire;
use App\Entity\Reconversion;
use App\Entity\TransactionPrestataireAdherent;
use App\Entity\User;
use App\Entity\TransactionAdherentPrestataire;
use App\Entity\InformationPopup;
use App\Entity\InformationPopupUser;
use App\Enum\MoyenEnum;
use App\Form\Type\CotiserFormType;
use App\Form\Type\DonAdherentFormType;
use App\Form\Type\DonPrestataireFormType;
use App\Form\Type\UserInfosFormType;
use App\Form\Type\EncaissementFormType;
use App\Form\Type\EncaissementValidationFormType;
use App\Utils\CotisationUtils;
use App\Utils\OperationUtils;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Translation\TranslatorInterface;
use Twig\Environment;
use App\Entity\Flux;

class UserController extends AbstractController
{
    private $em;
    private $translator;
    private $security;
    private $operationUtils;
    private $cotisationUtils;
    private $session;
    private $mailer;
    private $templating;

    public function __construct(
        EntityManagerInterface $em,
        TranslatorInterface $translator,
        Security $security,
        OperationUtils $operationUtils,
        CotisationUtils $cotisationUtils,
        SessionInterface $session,
        \Swift_Mailer $mailer,
        Environment $templating
    ) {
        $this->em = $em;
        $this->translator = $translator;
        $this->security = $security;
        $this->operationUtils = $operationUtils;
        $this->cotisationUtils = $cotisationUtils;
        $this->session = $session;
        $this->mailer = $mailer;
        $this->templating = $templating;
    }

    /**
     * @Route("/cotiser", name="cotiser", defaults={"type": "null"})
     * @Route("/cotiser/next", name="cotisernextyear", defaults={"type": "nextyear"})
     * @IsGranted("ROLE_USER")
     */
    public function cotiserAction($type, Request $request)
    {
        $options = [];
        $payment_type = '';
        if ($this->security->isGranted('ROLE_ADHERENT')) {
            $options['data_class'] = CotisationAdherent::class;
            $options['don_class'] = DonAdherentFormType::class;
            $payment_type = Payment::TYPE_COTISATION_ADHERENT;
        } elseif ($this->security->isGranted('ROLE_PRESTATAIRE')) {
            $options['data_class'] = CotisationPrestataire::class;
            $options['don_class'] = DonPrestataireFormType::class;
            $payment_type = Payment::TYPE_COTISATION_PRESTA;
        }

        $form = $this->createForm(CotiserFormType::class, null, $options);
        $form->handleRequest($request);
        if ('nextyear' === $type) {
            $nextYear = new \DateTime('+1 year');
            $startDate = $this->cotisationUtils->isCotisationValid($this->getUser());
            $endDate = new \DateTime(date('Y-m-d H:i:s', strtotime('+1 year', strtotime($startDate->format('Y-m-d H:i:s')))));
            $kohYear = (string) $endDate->format('Y');
        } else {
            $now = new \DateTime();
            $kohYear = (string) $now->format('Y');
        }

        if ($form->isSubmitted()) {
            $cotisation = $form->getData();
            if ($form->isValid()) {
                //Manage don if null => delete it !
                if ($cotisation->getDon() && 0 == $cotisation->getDon()->getMontant()) {
                    $cotisation->setDon(null);
                }
                if ('nextyear' === $type) {
                    // Cotiser pour l'année suivante
                    $startDate = $this->cotisationUtils->isCotisationValid($this->getUser());
                    $cotisation->getCotisationInfos()->setDebut($startDate);
                    $endDate = new \DateTime(date('Y-m-d H:i:s', strtotime('+1 year', strtotime($startDate->format('Y-m-d H:i:s')))));
                    $cotisation->getCotisationInfos()->setFin($endDate);
                    $cotisation->getCotisationInfos()->setAnnee((string) $endDate->format('Y'));
                }
                if (MoyenEnum::MOYEN_EMLC == $cotisation->getMoyen()) {
                    try {
                        $cotisation->setRecu(true);
                        $this->operationUtils->executeOperations($cotisation);
                        $this->addFlash(
                            'success',
                            // "Cotisation pour l'année {$cotisation->getCotisationInfos()->getAnnee()} bien reçue. Merci !"
                            $this->translator->trans('Cotisation bien reçue. Merci !')
                        );
                    } catch (\Exception $e) {
                        $this->addFlash(
                            'error',
                            $this->translator->trans('Problème avec la cotisation !') . ' ' . $e->getMessage()
                        );
                    }

                    return $this->redirectToRoute('index');
                } elseif (MoyenEnum::MOYEN_HELLOASSO == $cotisation->getMoyen()) {
                    if ($this->security->isGranted('ROLE_ADHERENT')) {
                        $url = $this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::HELLOASSO_URL_COTISATION_ADHERENT);
                    } else {
                        $url = $this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::HELLOASSO_URL_COTISATION_PRESTATAIRE);
                    }

                    return $this->redirect($url);
                } elseif (MoyenEnum::MOYEN_CB == $cotisation->getMoyen()) {
                    // Redirect to payment page
                    return $this->forward('App\Controller\PaymentController::preparePaymentAction', [
                        'form' => $form,
                        'type' => $payment_type,
                    ]);
                }
            } else {
                $this->addFlash(
                    'error',
                    $this->translator->trans('Problème avec la cotisation !') . ' ' . $form->getErrors()
                );
            }
        }

        return $this->render('@kohinos/cotiser.html.twig', [
            'form' => $form->createView(),
            'koh_year' => $kohYear,
        ]);
    }

    /**
     * @Route("/userinfos", name="user_infos")
     * @IsGranted("ROLE_USER")
     */
    public function userInfosAction(Request $request)
    {
        $form = $this->createForm(UserInfosFormType::class, $this->getUser());
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->em->persist($form->getData());
            $this->em->flush();
            $this->addFlash(
                'success',
                $this->translator->trans("Infos de l'utilisateur modifiée !")
            );
            $referer = $request->headers->get('referer');
            if ($referer && !$request->isXmlHttpRequest()) {
                return $this->redirect($referer);
            } elseif (!$request->isXmlHttpRequest()) {
                return new Response('', Response::HTTP_BAD_REQUEST);
            }
        }

        return $this->redirectToRoute('index');
        // return $this->render('@kohinos/presta/infos.html.twig', array(
        //     'form' => $form->createView()
        // ));
    }

    /**
     * @Route("/account/user", name="myaccount")
     * @IsGranted("ROLE_USER")
     */
    public function myaccountAction(Request $request)
    {
        return $this->render('@kohinos/common/myaccount.html.twig', [
        ]);
    }

    /**
     * @Route("/search/adherent", methods="GET", name="searchadherent")
     * @IsGranted("ROLE_USER")
     */
    public function searchAdherentAction(Request $request)
    {
        $term = $request->get('term');
        $result = $this->em->getRepository(Adherent::class)->findsByData(['email' => $term, 'name' => $term]);
        $arrayData = [];
        foreach ($result as $adherent) {
            $arrayData[] = ['id' => $adherent->getId(), 'text' => $adherent->__toString()];
        }

        return new JsonResponse(['results' => $arrayData]);
    }

    /**
     * @Route("/user/achatMonnaieAConfirmer", name="achatMonnaieAConfirmer")
     * @IsGranted({"ROLE_ADHERENT", "ROLE_PRESTATAIRE"})
     */
    public function achatMonnaieAConfirmerAction(Request $request)
    {
        $obj = $this->operationUtils->getCurrentAccountable($this->security->getUser());
        if ($obj instanceof Prestataire) {
            return $this->redirectToRoute('achatMonnaieAConfirmerPrestataire');
        } elseif ($obj instanceof Adherent) {
            return $this->redirectToRoute('achatMonnaieAConfirmerAdherent');
        }

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

    /**
     * @Route("/user/listachatMonnaieAConfirmer", name="listachatMonnaieAConfirmer")
     * @IsGranted({"ROLE_ADHERENT", "ROLE_PRESTATAIRE"})
     */
    public function listachatMonnaieAConfirmerAction(Request $request)
    {
        $obj = $this->operationUtils->getCurrentAccountable($this->security->getUser());
        if ($obj instanceof Prestataire) {
            return $this->redirectToRoute('listachatMonnaieAConfirmerPrestataire');
        } elseif ($obj instanceof Adherent) {
            return $this->redirectToRoute('listachatMonnaieAConfirmerAdherent');
        }

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


    /**
     * @param Request $request
     * @param TransactionAdherentPrestataire $transactionAdherentPrestataire
     * @return void
     * @IsGranted({"ROLE_CAISSIER", "ROLE_PRESTATAIRE"})
     * @Route("/cancel-transaction-adherent-prestataire/{id}", name="cancel_transaction_adherent_prestataire")
     */
    public function cancelTransactionAdherentPrestataireAction(Request $request, TransactionAdherentPrestataire $transactionAdherentPrestataire)
    {
        $adherent = $transactionAdherentPrestataire->getExpediteur();
        $presta = $transactionAdherentPrestataire->getDestinataire();

        //Make sure current user is destinataire of transaction
        if(!in_array($this->getUser(),$presta->getUsers()->toArray())) {
            $this->addFlash(
                'error',
                "Vous n'êtes pas autorisé à annuler cette transaction."
            );
            return $this->redirectToRoute('index');
        }
        //Make sure flux has not been cancelled already
        if($transactionAdherentPrestataire->getCancellerFlux()) {
            $this->addFlash(
                'error',
                "Cette transaction a déjà été annulée."
            );
            return $this->redirectToRoute('index');
        }

        // Check prestataire has enough funds
        $balance = $presta->getEmlcAccount()->getBalance();
        //I think this is unlikely in real situation but we need to make sure prestataire has enough founds
        $montant = $transactionAdherentPrestataire->getMontant();
        if ($balance < $montant) {
            $this->addFlash(
                'error',
                'Fonds insuffisants pour annuler cette transaction.'
            );
            return $this->redirectToRoute('index');
        }

        //Ccas transactions.
        if ($transactionAdherentPrestataire->getIsCcas()) {
            //Allowed if and only if transaction is from current month
            $now = new \DateTime();
            if (
                $transactionAdherentPrestataire->getCreatedAt()->format('m') !== $now->format('m')
                || $transactionAdherentPrestataire->getCreatedAt()->format('Y') !== $now->format('Y')
            ) {
                //display the same error message for CCAS or non CCAS as prestataire are not supposed to know if a transaction is CCAS or not
                $this->addFlash(
                    'error',
                    'Cette transaction ne plus être annulée car une reconversion a été demandée depuis.'
                );
                return $this->redirectToRoute('index');
            }
        }
        //Non ccas transactions,
        else {
            //we prevent cancelling transactions which happened before a reconversion (to avoid refunding money that's been reconverted)
            if (
                $this->em->getRepository(Flux::class)->getQueryByPrestataire(
                    $presta,
                    null,
                    Reconversion::TYPE_RECONVERSION_PRESTATAIRE,
                    $transactionAdherentPrestataire->getCreatedAt()->format("Y-m-d H:i:s")
                )->getResult()
            ) {
                $this->addFlash(
                    'error',
                    'Cette transaction ne plus être annulée car une reconversion a été demandée depuis.'
                );
                return $this->redirectToRoute('index');
            }
        }

        //Create new transaction in opposite direction
        $flux = new TransactionPrestataireAdherent();
        $flux->setExpediteur($presta);
        $flux->setDestinataire($adherent);

        $flux->setOperateur($this->security->getUser());
        $flux->setMontant($montant);
        $flux->setMoyen(MoyenEnum::MOYEN_EMLC);

        // The flux to cancel a ccas flux is also marked as ccas
        if ($this->getParameter('ccas_mode')) {
            $flux->setIsCcas($transactionAdherentPrestataire->getIsCcas());
        }

        $now = (new \Datetime('now'))->format('d/m/Y H:i:s');
        $flux->setReference(
            'Remboursement en Monnaie Solidaire du ' . $now . ' annule ' . $transactionAdherentPrestataire->getReference()
        );

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

        //Mark original transaction as cancelled
        $transactionAdherentPrestataire->setCancellerFlux($flux);

        $this->em->flush();

        $this->addFlash(
            'success',
            'La transaction a bien été annulée.'
        );
        return $this->redirectToRoute('index');
    }

    /**
     * Payment terminal page.
     * 
     * @Route("/encaissement", name="encaissement")
     * @IsGranted({"ROLE_CAISSIER", "ROLE_PRESTATAIRE"})
     */
    public function encaissementAction(Request $request)
    {
        // If conventionnement process is activated, prevent access to payment terminal if prestataire conventionnement is not set
        $conventionnementMode = $this->getParameter('presta_self_init_and_eval');
        if ($conventionnementMode == true) {
            $presta = $this->session->get('_prestagere');
            $presta = $this->em->getRepository(Prestataire::class)->findOneById($presta->getId());

            $conventionnement = $presta->getConventionnement();
            if ($conventionnement == null || $conventionnement == 0) {
                $this->addFlash(
                    'error',
                    $this->translator->trans("Impossible d'accéder au terminal de paiement tant que votre conventionnement n'a pas été établi par un·e gestionnaire.")
                );

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

        $form = $this->createForm(EncaissementFormType::class, null, []);
        $form->handleRequest($request);
        $validation = false;  // validation = user enters its code
        $insufficientBalance = null;  // if not null, will indicate for the template an insufficient founds situation 

        if ($form->isSubmitted()) {
            $data = $form->getData();
            
            if ($form->isValid()) {
                $adherent = $data["adherent"];
                $adherent_code = $adherent->getPaymentCode();
                $input_code = $data["payment_code"];

                // Perform first step checks
                if (empty($input_code)) {
                    // if adherent account isn't enabled
                    if (!$adherent->getUser()->isEnabled()) {
                        $this->addFlash(
                            'error',
                            $this->translator->trans('Le compte de l\'habitant·e est désactivé.')
                        );

                        goto end;
                    }

                    // if adherent doesn't have a validation code
                    if (is_null($adherent_code)) {
                        $this->addFlash(
                            'error',
                            $this->translator->trans('L\'habitant·e n\'a pas encore défini un code de validation de paiement sur son espace personnel, il·elle ne peut pas payer en Monnaie Solidaire pour l\'instant.')
                        );

                        goto end;
                    }

                    // If entered amount is above maximum amount, depending on prestataire conventionnement
                    if ($conventionnementMode == true) {
                        $transaction_amount = floatval($data["montant"]);
                        $montantPanier = floatval($form['montantPanier']->getData());
                        $maxAmount = $montantPanier * floatval($conventionnement);

                        if ($transaction_amount > $maxAmount) {
                            $this->addFlash(
                                'error',
                                $this->translator->trans('Montant maximal en MonA dépassé. Veuillez modifier le montant en MonA.')
                            );
        
                            goto end;
                        }
                    }

                    // When the form is submitted & valid, and the user account checks passed, we enter the validation process
                    $validation = true;

                    // First step validated (set user & amount) -> go to validation
                    goto end;
                } else {
                    // We're in the validation process
                    $validation = true;
                }

                // Check validation code
                // NOTE as we use password salt, the user must change his payment code if his password changes
                $encoded_input = crypt($input_code, $adherent->getUser()->getSalt());
                if (!hash_equals($adherent_code, $encoded_input)) {
                    $this->addFlash(
                        'error',
                        $this->translator->trans('Code incorrect')
                    );

                    goto end;
                }

                // Check adherent has enough funds
                $balance = $adherent->getEmlcAccount()->getBalance();
                $transaction_amount = floatval($data["montant"]);
                if ($balance < $transaction_amount) {
                    // Send mail for insufficient funds
                    $subject = $this->translator->trans('[MONNAIE ALIMENTAIRE COMMUNE] – Solde insuffisant');
                    $mail = (new \Swift_Message($subject))
                        ->setFrom($this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::MLC_NOTIF_EMAIL))
                        ->setTo($adherent->getUser()->getEmail())
                        ->setBody(
                            $this->templating->render(
                                '@kohinos/email/tav/insufficient_funds.html.twig',
                                [
                                    'subject' => $subject
                                ]
                            ),
                            'text/html'
                        );
                    $this->mailer->send($mail);

                    $this->addFlash(
                        'error',
                        $this->translator->trans('Solde de l\'habitant·e insuffisant')
                    );

                    $insufficientBalance = $balance;

                    goto end;
                }

                // No error at this point: save transaction
                $flux = new TransactionAdherentPrestataire();
                $flux->setExpediteur($adherent);

                if (!isset($presta)) {
                    $presta = $this->session->get('_prestagere');
                    $presta = $this->em->getRepository(Prestataire::class)->findOneById($presta->getId());
                }
                $flux->setDestinataire($presta);

                $flux->setOperateur($this->security->getUser());
                $flux->setMontant($transaction_amount);
                $flux->setMoyen(MoyenEnum::MOYEN_EMLC);

                $now = (new \Datetime('now'))->format('d/m/Y H:i:s');
                $flux->setReference('Achat en Monnaie Solidaire du ' . $now);

                // Mark transaction as CCAS if at the time of the transation, both adherent & presta are CCAS complient
                if (
                    $this->getParameter('ccas_mode')
                    && $adherent->getCcasEligible() 
                    && $adherent->getCcasAccepted()
                    && $presta->getCcasOk()
                ) {
                    $flux->setIsCcas(true);
                }

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

                $redirectParams = [];
                if ($form->has('montantPanier')) {
                    $transaction_amount = floatval($data["montant"]);
                    $montantPanier = floatval($form['montantPanier']->getData());

                    $redirectParams['remainingAmount'] = $montantPanier - $transaction_amount;
                }

                return $this->redirectToRoute('encaissementSuccess', $redirectParams);
            } else {
                $this->addFlash(
                    'error',
                    $this->translator->trans('Problème avec l\'encaissement !') . ' ' . $form->getErrors()
                );
            }
        }

        end:
        $templateData = [
            'form' => $form->createView(),
            'validation' => $validation,
            'insufficientBalance' => $insufficientBalance
        ];

        if ($conventionnementMode == true) {
            $templateData['conventionnement'] = $conventionnement;
        }

        return $this->render('@kohinos/tav/encaissement_page.html.twig', $templateData);
    }

    /**
     * @Route("/encaissement/succes", name="encaissementSuccess")
     * @IsGranted({"ROLE_CAISSIER", "ROLE_PRESTATAIRE"})
     */
    public function encaissementSuccessAction(Request $request)
    {
        $templateData = [];

        $remainingAmount = $request->query->get('remainingAmount');
        if ($remainingAmount != '') {
            $templateData['remainingAmount'] = $remainingAmount;
        }

        return $this->render('@kohinos/tav/payment_done_page.html.twig', $templateData);
    }

    /**
     * Register that the connected user (currently only adherents) has clicked on the popup validation button,
     * So it's not shown to him/her again.
     * 
     * @Route("/user/setUserValidatedInformationPopup", name="set_user_validated_information_popup")
     * @IsGranted({"ROLE_ADHERENT"})
     */
    public function setUserValidatedInformationPopupAction(Request $request)
    {
        $user = $this->security->getUser();
        $activePopup = $this->em->getRepository(InformationPopup::class)->findOneBy(['enabled' => true]);
        $activePopupUser = $this->em->getRepository(InformationPopupUser::class)->findOneBy(['informationPopup' => $activePopup, 'user' => $user]);

        if (null === $activePopupUser) {
            $activePopupUser = new InformationPopupUser();
            $activePopupUser->setInformationPopup($activePopup);
            $activePopupUser->setUser($user);
        }

        $activePopupUser->setHasValidated(true);

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

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