<?php

namespace App\Controller;

use App\Entity\AccountComptoir;
use App\Entity\AccountGroupe;
use App\Entity\AccountPrestataire;
use App\Entity\AccountSiege;
use App\Entity\Comptoir;
use App\Entity\Geoloc;
use App\Entity\GlobalParameter;
use App\Entity\Groupe;
use App\Entity\Prestataire;
use App\Entity\Siege;
use App\Entity\User;
use App\Entity\Usergroup;
use App\Enum\CurrencyEnum;
use App\Form\Type\InstallFormType;
use App\Security\LoginAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Model\UserManagerInterface;
use Geocoder\Provider\Nominatim\Nominatim;
use Geocoder\Query\GeocodeQuery;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
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\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security as Secur;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;

class IndexController extends AbstractController
{
    private $eventDispatcher;
    private $em;
    private $userManager;
    private $tokenManager;
    private $guard;
    private $authenticator;
    private $session;
    private $tokenStorage;
    private $router;

    public function __construct(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $em, UserManagerInterface $userManager, CsrfTokenManagerInterface $tokenManager = null, GuardAuthenticatorHandler $guard, SessionInterface $session, LoginAuthenticator $authenticator, TokenStorageInterface $tokenStorage, RouterInterface $router)
    {
        $this->eventDispatcher = $eventDispatcher;
        $this->em = $em;
        $this->userManager = $userManager;
        $this->tokenManager = $tokenManager;
        $this->guard = $guard;
        $this->authenticator = $authenticator;
        $this->session = $session;
        $this->tokenStorage = $tokenStorage;
        $this->router = $router;
    }

    /**
     * @Route("/", name="index")
     */
    public function index(Request $request)
    {
        /* Pour la première installation */
        $siege = $this->em->getRepository(Siege::class)->findAll();
        if (null == $siege || 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 = 'false' != $this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::USE_WORDPRESS);

        $template = 'index.html.twig';
        if ($isWordpress) {
            $template = 'index_wordpress.html.twig';
        }

        return $this->render('@kohinos/' . $template, [
            'news' => [],
            'last_username' => $lastUsername,
            'csrf_token' => $csrfToken,
        ]);
    }

    /**
     * @Route("/manifest.json", name="manifest")
     */
    public function manifest(Request $request)
    {
        return new JsonResponse(json_decode($this->renderView('@kohinos/common/manifest.json.twig')));
    }

    /**
     * @Route("/cotisation/invalid", name="cotisation_invalid")
     */
    public function cotisationInvalidAction(Request $request)
    {
        return $this->render('@kohinos/index_cotisation_invalid.html.twig', []);
    }

    /**
     * @Route("/installation", name="installation")
     */
    public function installationAction(Request $request)
    {
        $siege = $this->em->getRepository(Siege::class)->findAll();
        if (!empty($siege) && count($siege) > 0) {
            // Installation déjà effectuée !
            return $this->redirectToRoute('index');
        }
        $repogroup = $this->em->getRepository(Usergroup::class);
        $group = $repogroup->findOneBy(['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();
            $configs = $form['config']->getData();
            $iban = $form['iban']->getData();
            $website = $form['website']->getData();
            $groupe->setSiege($siege);

            //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($iban);
            if (!empty($website)) {
                $presta->setWeb($website);
            }
            $presta->setSiret(' ');
            $groupePresta = $repogroup->findOneBy(['name' => 'Prestataire']);
            $user->addPossiblegroup($groupePresta);
            $presta->addUser($user);
            $presta->setGroupe($groupe);

            $this->em->persist($siege);

            if (null == $siege->getAccountWithCurrency(CurrencyEnum::CURRENCY_EMLC)) {
                $account = new AccountSiege();
                $account
                    ->setBalance($siege->getEcompteNantie())
                    ->setCurrency(CurrencyEnum::CURRENCY_EMLC) // compte de monnaie numérique nantie
                ;
                $siege->addAccount($account);
                $this->em->persist($account);
            }
            if (null == $siege->getAccountWithCurrency(CurrencyEnum::CURRENCY_MLC)) {
                $accountMLC = new AccountSiege();
                $accountMLC
                    ->setBalance($siege->getCompte())
                    ->setCurrency(CurrencyEnum::CURRENCY_MLC) // compte de monnaie papier au siege non nantie
                ;
                $siege->addAccount($accountMLC);
                $this->em->persist($accountMLC);
            }
            // if ($siege->getAccountWithCurrency(CurrencyEnum::CURRENCY_EURO) == null) {
            //     $accountEuro = new AccountSiege();
            //     $accountEuro
            //         ->setCurrency(CurrencyEnum::CURRENCY_EURO) // compte réel de nantissement // @TODO : offrir la possibilité de voir les opérations du compte euro du fond de garanti ? Ou plutôt de suivre les euros à récupérés aux comptoirs
            //     ;
            //     $siege->addAccount($accountEuro);
            //     $this->em->persist($accountEuro);
            // }
            if (null == $siege->getAccountWithCurrency(CurrencyEnum::CURRENCY_MLC_NANTIE)) {
                $accountMlcNantie = new AccountSiege();
                $accountMlcNantie
                    ->setBalance($siege->getCompteNantie())
                    ->setCurrency(CurrencyEnum::CURRENCY_MLC_NANTIE) // compte de monnaie papier nantie
                ;
                $siege->addAccount($accountMlcNantie);
                $this->em->persist($accountMlcNantie);
                $this->em->persist($siege);
            }

            $this->em->persist($groupe);
            if (null == $groupe->getAccountWithCurrency(CurrencyEnum::CURRENCY_MLC)) {
                $account = new AccountGroupe();
                $account
                    ->setBalance($groupe->getCompte())
                    ->setCurrency(CurrencyEnum::CURRENCY_MLC)
                ;
                $groupe->addAccount($account);
                $this->em->persist($account);
                $this->em->persist($groupe);
            }
            $comptoirData = $form['comptoir']->getData();
            $comptoirName = $comptoirData['name'];
            $comptoirCompte = $comptoirData['compte'];
            $comptoirGeoloc = $comptoirData['geoloc'];
            if (!empty($comptoir)) {
                $comptoir = new Comptoir();
                $comptoir->setName($comptoirName);
                $comptoir->setEnabled(true);
                $comptoir->setGeoloc($comptoirGeoloc);
                $comptoir->setGroupe($groupe);
                $this->em->persist($comptoir);
                if (null == $comptoir->getAccountWithCurrency(CurrencyEnum::CURRENCY_MLC)) {
                    $account = new AccountComptoir();
                    $account
                        ->setBalance(empty($comptoirCompte) ? $comptoirCompte : 0)
                        ->setCurrency(CurrencyEnum::CURRENCY_MLC)
                    ;
                    $comptoir->addAccount($account);
                    $this->em->persist($account);
                    $this->em->persist($comptoir);
                }
            }
            $this->userManager->updateUser($user);
            $this->em->persist($presta);
            if (null == $presta->getAccountWithCurrency(CurrencyEnum::CURRENCY_EMLC)) {
                $account = new AccountPrestataire();
                $account
                    ->setBalance($presta->getEcompte())
                    ->setCurrency(CurrencyEnum::CURRENCY_EMLC)
                ;
                $presta->addAccount($account);
                $this->em->persist($account);
            }
            if ($presta->isMlc() && null == $presta->getAccountWithCurrency(CurrencyEnum::CURRENCY_EURO)) {
                // Ajout du compte de fonctionnement euro pour le prestataire MLC
                $accountEuro = new AccountPrestataire();
                $accountEuro
                        ->setCurrency(CurrencyEnum::CURRENCY_EURO)
                    ;
                $presta->addAccount($accountEuro);
                $this->em->persist($accountEuro);
            }
            $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('@kohinos/installation.html.twig', [
            '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(['status' => $status, 'data' => $return]);
    }

    /**
     * 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_USER")
     */
    public function groupeChoiceAction(Usergroup $group, Groupe $groupe, Request $request)
    {
        $this->em->refresh($this->getUser());
        if (!($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
            return in_array('ROLE_GESTION_GROUPE', $value->getRoles()) || in_array('ROLE_CONTACT', $value->getRoles()) || in_array('ROLE_TRESORIER', $value->getRoles());
        }) 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->reloadUserTokenFromGroup($group, $request);

        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))) {
            $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->reloadUserTokenFromGroup($group, $request);

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

    /**
     * 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)) || (($this->getUser()->getPossiblegroups()->exists(function ($key, $value) {
            return in_array('ROLE_CAISSIER', $value->getRoles());
        }) and $this->getUser()->getCaissiers()->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->reloadUserTokenFromGroup($group, $request);

        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->reloadUserTokenFromGroup($group, $request);

        if (in_array('ROLE_SUPER_ADMIN', $this->getUser()->getRoles(), true)) {
            // c'est un administrateur
            return new RedirectResponse($this->router->generate('sonata_admin_dashboard'));
        }

        return new RedirectResponse($this->router->generate('index'));
    }

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

    private function reloadUserTokenFromGroup($group, $request)
    {
        $groups = [$group];
        if ($this->getUser()->getGroups() !== $groups) {
            $this->getUser()->setGroups($groups);
            $this->em->persist($this->getUser());
            $this->em->flush();
        }
        $token = new PostAuthenticationGuardToken(
            $this->getUser(),
            'main',
            $this->getUser()->getRoles()
        );
        $this->tokenStorage->setToken($token);
        $this->guard->authenticateUserAndHandleSuccess(
            $this->getUser(),
            $request,
            $this->authenticator,
            'main'
        );
        $this->session->set('_security_main', serialize($token));
    }
}
