<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Doctrine\Persistence\ManagerRegistry;
use Ramsey\Uuid\Uuid;
use App\Service\LdapService;

use App\Entity\User;
use App\Entity\Group;
use App\Form\LoginType;

class SecurityController extends AbstractController
{
    private $appKernel;
    private $tokenstorage;
    private $ldapservice;

    public function __construct(KernelInterface $appKernel, TokenStorageInterface $tokenstorage, LdapService $ldapservice)
    {
        $this->appKernel = $appKernel;
        $this->tokenstorage = $tokenstorage;
        $this->ldapservice = $ldapservice;
    }

    public function login(Request $request, AuthenticationUtils $authenticationUtils, ManagerRegistry $em)
    {
        switch ($this->getParameter('mode_auth')) {
            case 'SQL':
                return $this->loginSQL($request, $authenticationUtils, $em);
                break;

            case 'CAS':
                return $this->loginCAS($request, $authenticationUtils, $em);
                break;

            case 'LDAP':
                return $this->loginLDAP($request, $authenticationUtils, $em);
                break;
        }
    }

    public function loginSQL(Request $request, AuthenticationUtils $authenticationUtils, ManagerRegistry $em)
    {
        return $this->render('Security\loginSQL.html.twig', [
            'last_username' => $authenticationUtils->getLastUsername(),
            'error' => $authenticationUtils->getLastAuthenticationError(),
            'useheader' => false,
            'usemenu' => false,
            'usesidebar' => false
        ]);
    }

    public function loginLDAP(Request $request, AuthenticationUtils $authenticationUtils, ManagerRegistry $em)
    {
        // Création du formulaire
        $form = $this->createForm(LoginType::class);

        // Récupération des data du formulaire
        $form->handleRequest($request);

        // Affichage du formulaire
        return $this->render('Security/loginLDAP.html.twig', [
            'useheader' => false,
            'usemenu' => false,
            'usesidebar' => false,
            'form' => $form->createView(),
            'error' => "",
        ]);
    }

    public function ldapcheckuser(Request $request, AuthenticationUtils $authenticationUtils, ManagerRegistry $em)
    {         
        $username = $request->get('login')['username'];
        $password = $request->get('login')['password'];

        // Récupération de la cible de navigation
        $redirect = $request->getSession()->get('_security.main.target_path');

        // L'utilisateur se co à l'annuaire
        $userldap = $this->ldapservice->userconnect($username, $password);
        if ($userldap) {
            $user=$em->getRepository("App\Entity\User")->findOneBy(["username"=>$username]);

            // Il n'y a pas d'autosubmit / autoupdate : il doit se faire par synchronisation
            if($user) {
                // Autoconnexion
                return $this->autoconnexion($user, $redirect, $request);                     
            }            
        }
      
        return $this->redirect($this->generateUrl('app_core_login'));               
    }

    public function loginCAS(Request $request, AuthenticationUtils $authenticationUtils, ManagerRegistry $em)
    {
        // Récupération de la cible de navigation
        $redirect = $request->getSession()->get('_security.main.target_path');

        // Masteridentity
        $masteridentity=$this->getParameter("masteridentity");

        // Init Client CAS
        // \phpCAS::setDebug("/var/log/phpcas/phpCAS-ninegate.log");

        $url=$this->getHost($request);
        $url=str_replace("http://",$this->getParameter("protocole")."://",$url);
        $url=str_replace("https://",$this->getParameter("protocole")."://",$url);

        if($this->getParameter("cas_type")=="client")
            @\phpCAS::client(CAS_VERSION_2_0, $this->getParameter('cas_host'), intval($this->getParameter('cas_port')), is_null($this->getParameter('cas_path')) ? '' : $this->getParameter('cas_path'), $url, false);
        else
            @\phpCAS::proxy(CAS_VERSION_2_0, $this->getParameter('cas_host'), intval($this->getParameter('cas_port')), is_null($this->getParameter('cas_path')) ? '' : $this->getParameter('cas_path'), $url, false);
        
        \phpCAS::setNoCasServerValidation();
        

        // Authentification
        \phpCAS::forceAuthentication();

        // Récupération UID
        $username = \phpCAS::getUser();

        // Récupération Attribut
        $attributes = \phpCAS::getAttributes();

        // Init
        $email = "";
        $lastname = "";
        $firstname = "";

        // Rechercher l'utilisateur
        if(isset($attributes[$this->getParameter('user_attr_cas_username')]))
            $username = $attributes[$this->getParameter('user_attr_cas_username')];

        if(isset($attributes[$this->getParameter('user_attr_cas_mail')]))
            $email = $attributes[$this->getParameter('user_attr_cas_mail')];

        if(isset($attributes[$this->getParameter('user_attr_cas_lastname')]))
            $lastname = $attributes[$this->getParameter('user_attr_cas_lastname')];

        if(isset($attributes[$this->getParameter('user_attr_cas_firstname')]))
            $firstname = $attributes[$this->getParameter('user_attr_cas_firstname')];

        $user = $em->getRepository('App\Entity\User')->findOneBy(array("username"=>$username));
        $exists = $user ? true : false;

        if (!$exists) {
            if($masteridentity=="SQL") {
                // C'est pas normal que l'on puisse se connecter alors que l'utilisateur n'est pas connu en base
                // La base étant le maitre de l'identité
                throw $this->createNotFoundException('Permission denied');
            }

            if($masteridentity=="LDAP") {
                // Normalement la synchronisation des comptes aurait du générer le compte en base c'est donc pas normal
                // Peut-être juste relancer une synchronisation
                // On tente une synchronisation via methode SSO
                $masteridentity="SSO";
                // throw $this->createNotFoundException('Permission denied. Need to synchronize LDAP ? Contact your administrator');
            }

            if($masteridentity=="SSO") {
                if(empty($email)) $email = $username."@nomail.com";

                // On s'assure qu'il n'y a pas déjà un utilisateur avec le même mail
                $usermail = $em->getRepository('App\Entity\User')->findOneBy(array("email"=>$email));
                if($usermail) {
                    return $this->render('App\Entity\Registration:info.html.twig', [
                        'useheader'         => true,
                        'usemenu'           => false,
                        'usesidebar'        => false,                   
                        'infotitle'         => "Première connexion",
                        'info'              => "Votre compte ne peut être activé car votre adresse mel est déjà utilisée par un autre compte utilisateur.<br>Nous sommes désolés du désagrément et vous invitons à contacter un administrateur.",
                        'mode'              => "error",
                        'redirectto'         => "",
                    ]);
                }

                // Là c'est normal que potentiellement il n'existe pas il faut donc l'autogénérer
                $user = new User();

                // On calcule le niveau01 de l'utilisateur
                $niveau01=$em->getRepository('App\Entity\Niveau01')->calculateNiveau01($attributes);
                if(!$niveau01) {
                    $niveau01=$em->getRepository('App\Entity\Niveau01')->findAll()[0];
                    //throw $this->createNotFoundException('Permission denied. No Organisation Niveau 01 match');
                }

                $user->setUsername($username);
                $user->setEmail($email);
                $user->setLastname($lastname);
                $user->setFirstname($firstname);

                $user->setPassword("CASPWD-".$username);
                $user->setSalt("CASPWD-".$username);
                $user->setApikey(Uuid::uuid4());

                $user->setNiveau01($niveau01);
                $user->setSiren($niveau01->getSiren());
                $user->setSiret("");

                $user->setAvatar("noavatar.png");
                $user->setVisible(true);
                $user->setAuthlevel("simple");
                $user->setBelongingpopulation("agent");
                $user->setRole("ROLE_USER");

                if(in_array($username,$this->getParameter("ldap_usersadmin")))
                    $user->setRole("ROLE_ADMIN");

                $em->getManager()->persist($user);
                $em->getManager()->flush();

                // Génération auto des groupes
                $this->submitGroup($em,$attributes);

                // On calcule les groupes de l'utilisateur
                $user=$em->getRepository('App\Entity\Group')->calculateGroup($user,$attributes);

            }
        }
        else {
            // Mise à jour des valeurs uniquement si le maitre de l'identité est le SSO
            if($masteridentity=="SSO") {
                // On calcule le niveau01 de l'utilisateur
                $niveau01=$em->getRepository('App\Entity\Niveau01')->calculateNiveau01($attributes);
                if(!$niveau01)
                    throw $this->createNotFoundException('Permission denied. No Organisation Niveau 01 match');

                // On s'assure que le niveau 02 appartient bien au niveau 01 calculé
                $sameniveau01=(!is_null($user->getNiveau02())&&$niveau01==$user->getNiveau02()->getNiveau01());

                $user->setLastname($lastname);
                $user->setFirstname($firstname);
                $user->setEmail($email);
                if(!$sameniveau01) {
                    $user->setNiveau01($niveau01);
                    $user->setNiveau02(null);
                }
                if(in_array($username,$this->getParameter("ldap_usersadmin")))
                    $user->setRole("ROLE_ADMIN");

                // Génération auto des groupes
                $this->submitGroup($em,$attributes);

                // On calcule les groupes de l'utilisateur
                $user=$em->getRepository('App\Entity\Group')->calculateGroup($user,$attributes);

                $em->getManager()->flush();
            }
        }

        // Sauvegarde des attributes en session
        $request->getSession()->set('attributes', $attributes);

        // Sauvegarde des ssoitems en session
        $ssoitems=[];
        if($this->getParameter('ssosynchroitem')) {
            $user_attr_cas_item=$this->getParameter('user_attr_cas_item');
            if(array_key_exists($user_attr_cas_item,$attributes)) {
                if(!is_array($attributes[$user_attr_cas_item])) {
                    $attributes[$user_attr_cas_item]=[$attributes[$user_attr_cas_item]];
                }
                $ssoitems=$attributes[$user_attr_cas_item];
            }
        }
        $request->getSession()->set('ssoitems', $ssoitems);

        // Autoconnexion
        return $this->autoconnexion($user, $redirect, $request); 
    }

    public function logout(Request $request)
    {
        $auth_mode = $this->getParameter('mode_auth');
        switch ($auth_mode) {
            case 'SQL':
                return $this->logoutSQL($request);
                break;

            case 'CAS':
                return $this->logoutCAS($request);
                break;

            case 'LDAP':
                return $this->logoutLDAP($request);
                break;

            case 'OPENID':
                return $this->logoutOPENID($request);
                break;
        }
    }

    public function logoutSQL(Request $request)
    {
        $this->tokenstorage->setToken(null);
        $request->getSession()->invalidate();

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

    public function logoutCAS(Request $request)
    {

        // Init Client CAS
        // \phpCAS::setDebug("/var/log/phpcas/phpCAS-ninegate.log");
        \phpCAS::client(CAS_VERSION_2_0, $this->getParameter('cas_host'), intval($this->getParameter('cas_port')), is_null($this->getParameter('cas_path')) ? '' : $this->getParameter('cas_path'), $this->getHost($request), false);
        \phpCAS::setNoCasServerValidation();

        // Logout
        $url = $this->generateUrl('app_core_home', [], UrlGeneratorInterface::ABSOLUTE_URL);
        \phpCAS::logout(['service' => $url]);

        $this->tokenstorage->setToken(null);
        $request->getSession()->invalidate();

        return true;
    }

    public function logoutLDAP(Request $request)
    {
        $this->tokenstorage->setToken(null);
        $request->getSession()->invalidate();

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

    public function checkuser(Request $request) 
    {
        $userapp = $this->getUser();
        $fgforceconnect = $request->getSession()->get('fgforceconnect');
        $modeauth=$this->getParameter('mode_auth');

        if(is_null($userapp) && $fgforceconnect) {
            return new Response("<script>window.location.href='".$this->generateUrl("app_core_login")."';</script>");
        }
        else 
        {
            // Mode d'authentification
            switch($modeauth) {
                case "CAS":
                    // Init Client CAS
                    \phpCAS::client(CAS_VERSION_2_0, $this->getParameter('cas_host'), intval($this->getParameter('cas_port')), is_null($this->getParameter('cas_path')) ? '' : $this->getParameter('cas_path'), $this->getHost($request), false);
                    \phpCAS::setNoCasServerValidation();

                    if(\phpCAS::checkAuthentication()) {
                        $usercas = \phpCAS::getUser();

                        // si on a un usercas mais pas de userapp c'est qu'il faut s'autoconnect
                        if(!$userapp) {
                            $url=$this->generateUrl('app_core_login');
                            return new Response(
                                '<script>document.location.replace("'.$url.'");</script>'
                            );
                        }
                    } 
                break;
            }
        }
        return new Response();
    }

    private function autoconnexion($user, $redirect, Request $request)
    {
        // Récupérer le token de l'utilisateur
        $token = new UsernamePasswordToken($user, 'main', $user->getRoles());
        $this->tokenstorage->setToken($token);
        $request->getSession()->set('_security_main', serialize($token));

        // Simuler l'evenement de connexion
        $event = new InteractiveLoginEvent($request, $token);
        $dispatcher = new EventDispatcher();
        $dispatcher->dispatch($event);

        // Redirection
        if ($redirect) {
            return $this->redirect($redirect);
        } else {
            return $this->redirect($this->generateUrl('app_core_home'));
        }
    }

    private function submitGroup($em,$attributes) {
        if(!$this->getParameter('ssosynchrogroup'))
            return null;

        $user_attr_cas_group=$this->getParameter('user_attr_cas_group');

        // Si l'utilisateur possège l'attribut groupe dans ses attributs
        if(array_key_exists($user_attr_cas_group,$attributes)) {
            if(!is_array($attributes[$user_attr_cas_group])) {
                $attributes[$user_attr_cas_group]=[$attributes[$user_attr_cas_group]];
            }

            foreach($attributes[$user_attr_cas_group] as $ssogroup) {
                $basedn=$this->getParameter('ldap_basedn');
                $name=$ssogroup;
                if($basedn!="") {
                    // Si présence du basedn dans le nom du groupe = nous sommes en présence d'un DN = on récupere donc comme nom que son cn
                    if(stripos($name,$basedn)!==false) {
                        $tbname=explode(",",$name);
                        $tbname=explode("=",$tbname[0]);
                        $name=$tbname[1];
                    }
                }
                

                // Recherche du groupe
                $group=$em->getRepository("App\Entity\Group")->findOneBy(["label"=>$name]);
                if(!$group) {
                    $group=new Group();
                    $group->setLabel($name);
                    $group->setFgcancreatepage(false);
                    $group->setFgcancreateblog(false);
                    $group->setFgcancreatecalendar(false);
                    $group->setFgcancreateproject(false);
                    $group->setFgcanshare(false);
                    $group->setFgopen(false);
                    $group->setFgall(false);
                }

                $group->setAttributes('{"'.$user_attr_cas_group.'":"'.$ssogroup.'"}');
                $group->setFgtemplate(false);

                $em->getManager()->persist($group);
                $em->getManager()-> flush();

            }
        }

    }

    public function imapunread(Request $request) {
        if($this->getParameter("active_imapunread")&&$this->getParameter("cas_type")=="proxy") {
            $ip=$this->getParameter("imapundread_ip");

            // Init Client CAS
            // \phpCAS::setDebug("/var/log/phpcas/phpCAS-ninegate.log");
            @\phpCAS::proxy(CAS_VERSION_2_0, $this->getParameter('cas_host'), intval($this->getParameter('cas_port')), is_null($this->getParameter('cas_path')) ? '' : $this->getParameter('cas_path'), $this->getHost($request), false);
            \phpCAS::setNoCasServerValidation();
            \phpCAS::forceAuthentication();
        
            
            $pt= \phpCAS::retrievePT('imap://'.$ip,$t,$f);
            $a = \phpCAS::serviceMail("{".$ip.":993/imap/ssl/novalidate-cert}","imap://".$ip,0, $errc,$err,$pt);
            $unseen = imap_status($a, "{".$ip.":993/imap/ssl/novalidate-cert}INBOX", SA_UNSEEN);
            $count=$unseen->unseen;
            $response = new JsonResponse($count);    
        }
        else 
            $response = new JsonResponse("");    

        return $response;
    }    

    public function redirectroute(Request $request, $route, $id) {
        if($route=="app_core_home")
            return $this->redirectToRoute($route,["id"=>$id]);
        else 
            return $this->redirectToRoute("app_core_home",["gotoroute"=>$route,"gotoid"=>$id]);
    }

    private function getHost($request) {
        $host = $request->getHost();
        $protocol = $request->getScheme();
        $port = $request->getPort();
        return $protocol."://".$host.($port!=80&&$port!=443?":".$port:"");
    }
}
