<?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 App\Entity\Adherent;
use App\Entity\Cotisation;
use App\Entity\Comptoir;
use App\Entity\Faq;
use App\Entity\Geoloc;
use App\Entity\Groupe;
use App\Entity\GlobalParameter;
use App\Entity\Page;
use App\Entity\Prestataire;
use App\Entity\Siege;
use App\Entity\User;
use App\Entity\Usergroup;
use App\Entity\Payment;
use App\Form\Type\AdhererFormType;
use App\Form\Type\ContactFormType;
use App\Form\Type\InstallFormType;
use App\Form\Type\TransactionAdherentPrestataireFormType;
use App\Security\LoginAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\Event\GetResponseUserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Form\Factory\FactoryInterface;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Geocoder\Provider\Nominatim\Nominatim;
use Geocoder\Query\GeocodeQuery;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Swagger\Annotations as SWG;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
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\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security as Secur;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;

class IndexController extends AbstractController
{
    private $eventDispatcher;
    private $em;
    private $userManager;
    private $tokenManager;
    private $guard;
    private $authenticator;
    private $session;
    private $mailer;
    private $templating;

    public function __construct(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $em, UserManagerInterface $userManager, CsrfTokenManagerInterface $tokenManager = null, GuardAuthenticatorHandler $guard, SessionInterface $session, \Swift_Mailer $mailer, EngineInterface $templating, LoginAuthenticator $authenticator)
    {
        $this->eventDispatcher = $eventDispatcher;
        $this->em = $em;
        $this->userManager = $userManager;
        $this->tokenManager = $tokenManager;
        $this->guard = $guard;
        $this->authenticator = $authenticator;
        $this->session = $session;
        $this->mailer = $mailer;
        $this->templating = $templating;
    }

    /**
     * @Route("/", name="index")
     */
    // public function index(TranslatorInterface $translator)
    public function index(Request $request)
    {
        // Exemple pour la traduction :
        // $translated = $translator->trans('Symfony is great');
        // $translator->transChoice(
        //     'Hurry up %name%! There is one apple left.|There are %count% apples left.',
        //     10,
        //     // no need to include %count% here; Symfony does that for you
        //     array('%name%' => $user->getName())
        // );

        /* Pour la première installation */
        $siege = $this->em->getRepository(Siege::class)->findAll();
        if ($siege == null || empty($siege)) {
            return $this->redirectToRoute('installation');
        }
        /* Pour la modale de login => SecurityController loginAction */
        $session = $request->getSession();
        $lastUsernameKey = Secur::LAST_USERNAME;
        // last username entered by the user
        $lastUsername = (null === $session) ? '' : $session->get($lastUsernameKey);
        $csrfToken = $this->tokenManager
            ? $this->tokenManager->getToken('authenticate')->getValue()
            : null;

        $isWordpress = $this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::USE_WORDPRESS) != 'false';

        $template = 'index.html.twig';
        if ($isWordpress) {
            $template = 'index_wordpress.html.twig';
        }
        return $this->render($template, [
            'news' => array(),
            'last_username' => $lastUsername,
            'csrf_token' => $csrfToken
        ]);
    }

    /**
     * @Route("/installation", name="installation")
     */
    public function installationAction(Request $request)
    {
        $siege = $this->em->getRepository(Siege::class)->findOneById(1);
        if (!empty($siege)) {
            // Installation déjà effectuée !
            return $this->redirectToRoute('index');
        }
        $repogroup = $this->em->getRepository(Usergroup::class);
        $group = $repogroup->findOneBy(array('name' => 'Super Admin'));
        if (empty($group)) {
            return new Response('ERREUR !<br><br>Avant de pouvoir installer le kohinos, il faut charger les fixtures : <br>Soit charger les fixtures standards :<br><em>php bin/console hautelook:fixtures:load --purge-with-truncate</em><br/><br/>Soit charger les fixtures de dev (pour tester le kohinos avec des données/comptes factices):<br/><em>php bin/console hautelook:fixtures:load --purge-with-truncate --env=test</em>', 200);
        }
        $user = $this->userManager->createUser();
        $user->setEnabled(true);
        $user->addPossiblegroup($group);
        $user->addGroup($group);
        $form = $this->createForm(InstallFormType::class, ['user' => $user]);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $siege = $form['siege']->getData();
            $user = $form['user']->getData();
            $groupe = $form['groupe']->getData();
            $comptoir = $form['comptoir']->getData();
            $configs = $form['config']->getData();
            $groupe->setSiege($siege);
            $comptoir->setGroupe($groupe);

            //Création du prestataire recevant les cotisations
            $presta = new Prestataire();
            $presta->setMlc(true);
            $presta->setRaison('Monnaie locale');
            $presta->setDescription('Association gérant la monnaie locale et recevant les cotisations');
            $presta->setEnabled(true);
            $presta->setIban(' ');
            $presta->setSiret(' ');
            $groupePresta = $repogroup->findOneBy(array('name' => 'Prestataire'));
            $user->addPossiblegroup($groupePresta);
            $presta->addUser($user);
            $presta->setGroupe($groupe);

            $this->em->persist($siege);
            $this->em->persist($groupe);
            $this->em->persist($comptoir);
            $this->userManager->updateUser($user);
            $this->em->persist($presta);
            foreach ($configs as $config) {
                $this->em->persist($config);
            }
            $this->em->flush();
            // Création de l'utilisateur avec ROLE_API pour le plugin Wordpress ou les applis tierces !
            $userAPI = $this->userManager->createUser();
            $userAPI->setEnabled(true);
            $userAPI->setUsername('userapi');
            $userAPI->setPassword(md5(random_bytes(10)));
            $userAPI->setEmail('userapi@kohinos.fr');
            $userAPI->addRole('ROLE_API');
            $this->userManager->updateUser($userAPI);

            $this->em->flush();
            $this->addFlash(
                'success',
                "BRAVO ! Vous venez de configurer le Kohinos. Vous pouvez maintenant accéder à l'interface d'administration."
            );

            $url = $this->generateUrl('fos_user_registration_confirmed');
            $response = new RedirectResponse($url);

            // @TODO : send mail with this event catch !
            $this->eventDispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
            return new RedirectResponse($this->generateUrl('sonata_admin_dashboard'));
        }
        return $this->render('installation.html.twig', array(
            'form' => $form->createView()
        ));
    }

    /**
     * @Route("/adherer", name="adherer")
     */
    public function adhererAction(Request $request)
    {
        $adherent = new Adherent();

        $form = $this->createForm(AdhererFormType::class, $adherent);
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            if ($form->isValid()) {
                $adherentNew = $form->getData();

                // Serialize form data in json to store with payment object and persist when payment is valid
                $serializer = $this->container->get('serializer');
                $data = $serializer->normalize($adherentNew, null,
                  [AbstractNormalizer::ATTRIBUTES => ['user' => ['username', 'email', 'firstname', 'lastname', 'plainPassword'],
                                                      'groupe' => ['id'],
                                                      'geoloc' => ['adresse', 'cpostal', 'ville']]]);
                $jsondata = $serializer->serialize($data, 'json');

                // Redirect to payment page
                return $this->forward('App\Controller\FluxController::preparePaymentAction', [
                    'form'  => $form,
                    'type'  => Payment::TYPE_ADHESION,
                    'extra_data' => $jsondata
                ]);
            } else {
                $this->addFlash(
                    'error',
                    'Problème avec l\'adhésion, veuillez vérifier les informations du formulaire !'
                );
            }
        }

        return $this->render('adherent/adherer.html.twig', array(
            'form' => $form->createView()
        ));
    }

    /**
    * @Route("/geoloc", name="geolocAdresse")
    */
    public function geoLocAction(Request $request)
    {
        $referer = $request->headers->get('referer');
        if ($referer && !$request->isXmlHttpRequest()) {
            return $this->redirect($referer);
        } elseif (!$request->isXmlHttpRequest()) {
            return new Response('', Response::HTTP_BAD_REQUEST);
        }
        $status = 'success';
        $return = null;
        if (!empty($request->get('cpostal')) && !empty($request->get('ville'))) {
            try {
                // GEOCODING ADDRESS :
                $httpClient = new \Http\Adapter\Guzzle6\Client();
                $provider = Nominatim::withOpenStreetMapServer($httpClient, 'Mozilla/5.0');
                $geocoder = new \Geocoder\StatefulGeocoder($provider, 'fr');
                // Query geocoding from complete address
                $result = $geocoder->geocodeQuery(GeocodeQuery::create($request->get('adresse').' '.$request->get('cpostal').' '.$request->get('ville')));
                if (count($result) > 0) {
                    $coords = $result->first()->getCoordinates();
                    $return = ['lat' => $coords->getLatitude(), 'lon' => $coords->getLongitude()];
                } else {
                    $result = $geocoder->geocodeQuery(GeocodeQuery::create($request->get('cpostal').' '.$request->get('ville')));
                    if (count($result) > 0) {
                        $coords = $result->first()->getCoordinates();
                        $return = ['lat' => $coords->getLatitude(), 'lon' => $coords->getLongitude()];
                    }
                }
            } catch (\Exception $e) {
                $status = 'error';
            }
        } else {
            $status = 'error';
        }
        return new JsonResponse(array('status' => $status, 'data' => $return));
    }

    private function removeOldSessionParams()
    {
        $this->session->remove('_choixGroup');
        $this->session->remove('_groupId');
        $this->session->remove('_groupegere');
        $this->session->remove('_comptoirgere');
        $this->session->remove('_prestagere');
    }

    /**
     * Choix du groupe local géré
     * @Route("/login/groupe/choice/{usergrpid}/{grpid}", name="groupe_choice")
     * @ParamConverter("group", class="App:Usergroup", options={"mapping": {"usergrpid": "id"}})
     * @ParamConverter("groupe", class="App:Groupe", options={"mapping": {"grpid": "id"}})
     * IsGranted({"ROLE_GESTION_GROUPE", "ROLE_CONTACT", "ROLE_TRESORIER"})
     * @IsGranted("ROLE_USER")
     */
    public function groupeChoiceAction(Usergroup $group, Groupe $groupe, Request $request)
    {
        if (!(($this->getUser()->getPossiblegroups()->contains('ROLE_GESTION_GROUPE') or $this->getUser()->getPossiblegroups()->contains('ROLE_CONTACT') or $this->getUser()->getPossiblegroups()->contains('ROLE_TRESORIER')) and $this->getUser()->getGroupesgeres()->contains($groupe))) {
            $this->addFlash(
                'error',
                "Accès impossible !"
            );
            return $this->redirectToRoute('index');
        }
        $this->removeOldSessionParams();
        // On enregistre le groupe local choisit en session
        $this->session->set('_groupegere', $groupe);
        $this->getUser()->setGroups([$group]);
        $this->em->persist($this->getUser());
        $this->em->flush();
        $this->guard->authenticateUserAndHandleSuccess(
            $this->getUser(),
            $request,
            $this->authenticator,
            'main'
        );

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

    /**
     * Choix du comptoir géré
     * @Route("/login/comptoir/choice/{usergrpid}/{cptid}", name="comptoir_choice")
     * @ParamConverter("group", class="App:Usergroup", options={"mapping": {"usergrpid": "id"}})
     * @ParamConverter("comptoir", class="App:Comptoir", options={"mapping": {"cptid": "id"}})
     * @IsGranted("ROLE_USER")
     */
    public function comptoirChoiceAction(Usergroup $group, Comptoir $comptoir, Request $request)
    {
        $this->em->refresh($this->getUser());
        if (
            !($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
                return in_array('ROLE_COMPTOIR', $value->getRoles());
            }) and $this->getUser()->getComptoirsgeres()->contains($comptoir))
            and
            (!($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
                return in_array('ROLE_CAISSIER_COMPTOIR', $value->getRoles());
            }) and $this->getUser()->getCaissiercomptoirs()->contains($comptoir)))
        ) {
            $this->addFlash(
                'error',
                "Accès impossible !"
            );
            return $this->redirectToRoute('index');
        }
        $this->removeOldSessionParams();
        // On enregistre le comptoir choisit en session
        $this->session->set('_comptoirgere', $comptoir);
        $this->getUser()->setGroups([$group]);
        $this->em->persist($this->getUser());
        $this->em->flush();
        $this->guard->authenticateUserAndHandleSuccess(
            $this->getUser(),
            $request,
            $this->authenticator,
            'main'
        );

        if ($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
            return in_array('ROLE_COMPTOIR', $value->getRoles());
            }) 
        ) {
            return $this->redirectToRoute('sonata_admin_dashboard');
        } else {
            return $this->redirectToRoute('index');
        }
    }

    /**
     * Choix du presta géré
     * @Route("/login/presta/choice/{usergrpid}/{prestaid}", name="presta_choice")
     * @ParamConverter("group", class="App:Usergroup", options={"mapping": {"usergrpid": "id"}})
     * @ParamConverter("prestataire", class="App:Prestataire", options={"mapping": {"prestaid": "id"}})
     * @IsGranted("ROLE_USER")
     */
    public function prestaChoiceAction(Usergroup $group, Prestataire $prestataire, Request $request)
    {
        $this->em->refresh($this->getUser());
        if (
            (!($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
                return in_array('ROLE_PRESTATAIRE', $value->getRoles());
            }) and $this->getUser()->getPrestataires()->contains($prestataire)))
            and
            (!($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
                return in_array('ROLE_CAISSIER_PRESTATAIRE', $value->getRoles());
            }) and $this->getUser()->getCaissierprestataires()->contains($prestataire)))
        ) {
            $this->addFlash(
                'error',
                "Accès impossible !"
            );
            return $this->redirectToRoute('index');
        }
        $this->removeOldSessionParams();
        // On enregistre le presta choisit en session
        $this->session->set('_prestagere', $prestataire);
        $this->getUser()->setGroups([$group]);
        $this->em->persist($this->getUser());
        $this->em->flush();
        $this->guard->authenticateUserAndHandleSuccess(
            $this->getUser(),
            $request,
            $this->authenticator,
            'main'
        );

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

    /**
     * @Route("/login/group/choice/{id}", name="usergroup_choice")
     * @IsGranted("ROLE_USER")
     */
    public function groupChoiceAction(Usergroup $group, Request $request)
    {
        if (!$this->getUser()->getPossiblegroups()->contains($group)) {
            $this->addFlash(
                'error',
                "Accès impossible !"
            );
            return $this->redirectToRoute('index');
        }
        $this->removeOldSessionParams();
        $this->getUser()->setGroups([$group]);
        $this->em->persist($this->getUser());
        $this->em->flush();
        $this->guard->authenticateUserAndHandleSuccess(
            $this->getUser(),
            $request,
            $this->authenticator,
            'main'
        );

        // @TODO : On redirige sur l'index (ou en fonction du rôle?)
        $referer = $request->headers->get('referer');
        if ($referer && !$request->isXmlHttpRequest()) {
            return $this->redirect($referer);
        } elseif (!$request->isXmlHttpRequest()) {
            return $this->redirectToRoute('index');
        }
    }

    /**
     * @Route("/contact", name="contact")
     */
    public function contactAction(Request $request)
    {
        $form = $this->createForm(ContactFormType::class);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $name = $form['nom']->getData();
            $emailFrom = $form['email']->getData();
            $message = $form['message']->getData();

            $this->sendMail($name, $emailFrom, $message);

            $this->addFlash(
                'success',
                'Merci ! Le message a bien été envoyé !'
            );
            $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->render('contact.html.twig', array(
            'form' => $form->createView()
        ));
    }

    private function sendMail($name, $from, $message)
    {
        $subject = $this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::MLC_NAME_SMALL).' : Contact';
        $mail = (new \Swift_Message($subject))
            ->setFrom($from)
            ->setTo($this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::MLC_CONTACT_EMAIL))
            ->setBody(
                $this->templating->render(
                    'email/contact.html.twig',
                    array(
                        'subject' => $subject,
                        'from' => $from,
                        'name' => $name,
                        'message' => $message,
                    )
                ),
                'text/html'
            );
        $this->mailer->send($mail);
    }
}
