/*
 *  Copyright 2013-2050 Emmanuel BRUN (contact@amapj.fr)
 * 
 *  This file is part of AmapJ.
 *  
 *  AmapJ 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.

 *  AmapJ 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 AmapJ.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * 
 */
 package fr.amapj.service.services.authentification;

import org.apache.commons.codec.binary.Base64;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.vaadin.ui.UI;

import fr.amapj.common.DateUtils;
import fr.amapj.model.engine.db.DbManager;
import fr.amapj.model.engine.rdblink.RdbLink;
import fr.amapj.model.engine.transaction.DbWrite;
import fr.amapj.model.models.fichierbase.EtatUtilisateur;
import fr.amapj.model.models.fichierbase.Utilisateur;
import fr.amapj.model.models.saas.TypLog;
import fr.amapj.service.engine.sudo.SudoManager;
import fr.amapj.service.services.access.AccessManagementService;
import fr.amapj.service.services.appinstance.LogAccessDTO;
import fr.amapj.service.services.authentification.PasswordComplianceService.CheckComplianceType;
import fr.amapj.service.services.logview.LogViewService;
import fr.amapj.service.services.session.SessionManager;
import fr.amapj.service.services.session.SessionParameters;
import fr.amapj.view.engine.popup.formpopup.validator.EmailValidator;
import fr.amapj.view.engine.ui.AmapJLogManager;

/**
 * Permet à un utilisateur de s'authentifier grace à son login et son mot de passe
 */
public class AuthentificationService
{
	private static final Logger logger = LogManager.getLogger();
	
	public static final AuthentificationCounter authentificationCounter = new AuthentificationCounter();
	
	static public class CheckResult
	{
		//
		public boolean isPasswordCorrect;
		
		// Message indicatif si le mot de passe est incorrect
		public String msg;
		
		public Long idUtilisateur;
		
		// Indique si le mot de passe est assez robuste 
		public CheckComplianceType type;
		
		
		
		public CheckResult(boolean isPasswordCorrect, String msg, Long idUtilisateur,CheckComplianceType type) 
		{
			this.isPasswordCorrect = isPasswordCorrect;
			this.msg = msg;
			this.idUtilisateur = idUtilisateur;
			this.type = type;
		}

		static public CheckResult badCredentials(String msg)
		{
			return new CheckResult(false, msg, null,null);
		}
		
		static public CheckResult credentialsOK(CheckComplianceType type, Long id)
		{
			return new CheckResult(true,null, id,type);
		}
		
	}
	
	
	/**
	 * Permet à un utilisateur de s'authentifier grace à son login et son mot de passe
	 */
	@DbWrite
	public CheckResult authenticateUser(String email,String password, String sudo)
	{
		RdbLink em = RdbLink.get();
		
		if (((password==null) || password.equals("")) && (sudo==null) )
		{
			return CheckResult.badCredentials("Vous n'avez pas saisi le mot de passe");
		}
		
		Utilisateur u = findUser(email, em);
		
		if (u==null)
		{
			authentificationCounter.addUnknow();
			return CheckResult.badCredentials("Adresse e-mail ou mot de passe incorrect");
		}
		
		if (u.etatUtilisateur==EtatUtilisateur.INACTIF)
		{
			return CheckResult.badCredentials("Votre compte a été désactivé car vous n'êtes plus membre de l'AMAP.");
		}
		
		if (u.password==null)
		{
			return CheckResult.badCredentials("Vous devez d'abord activer votre compte en utilisant le lien qui vous a été envoyé par e mail.");
		}
		
		
		
		String msg = checkCredential(u,password,sudo);
		
		// Récupération d'un ensemble d'information
		String ip = UI.getCurrent().getPage().getWebBrowser().getAddress();
		String browser = SessionManager.getAgentName(UI.getCurrent());
		String dbName = DbManager.get().getCurrentDb().getDbName();
		
		// Si authentification incorrect
		if (msg!=null)
		{
			authentificationCounter.addBadPassword();
			logger.info("Authentification en echec pour ip={} browser={} dbName={} msg={}",ip,browser,dbName,msg);
			return CheckResult.badCredentials(msg);
		}
		
		
		// Si password ok :
		
		// On memorise la date de connexion , sauf dans le cas d'un sudo
		if (sudo==null)
		{
			u.lastConnexion = DateUtils.getDate();
		}

		// On verifie si le password est conforme aux normes des mots de passe
		 CheckComplianceType type = new PasswordComplianceService().checkComplianceOnLogging(u,password,sudo);
		
		// On mémorise l'accès dans la base de données du master
		LogAccessDTO logAccessDTO = new LogViewService().saveAccess(u.nom,u.prenom,u.getId(),ip,browser,dbName,TypLog.USER,(sudo!=null));
						
		// On sauvegarde les paramètres de session
		SessionParameters p = new SessionParameters();
		p.userId = u.getId();
		p.userRole = new AccessManagementService().getUserRole(u,em);
		p.userNom = u.nom;
		p.userPrenom = u.prenom;
		p.userEmail = email;
		p.dateConnexion = logAccessDTO.dateIn;
		p.logId = logAccessDTO.id;
		p.isSudo = (sudo!=null);
		p.logFileName = logAccessDTO.logFileName;
		SessionManager.setSessionParameters(p);
		
		return CheckResult.credentialsOK(type,u.id);
	
	}
	
	
	private String checkCredential(Utilisateur u, String password, String sudo)
	{
		if (sudo!=null)
		{
			return checkCredentialBySudo(u,sudo);
		}
		else
		{
			return checkCredentialByPassword(u,password);
		}
	}


	private String checkCredentialBySudo(Utilisateur u, String sudo)
	{
		String nomInstance = DbManager.get().getCurrentDb().getDbName();
		String session = UI.getCurrent().getSession().getCsrfToken();
		
		if (SudoManager.authenticate(sudo,u.getId(),nomInstance,session)==false)
		{
			return "Incorrect";
		}
		else
		{
			return null;
		}
	}


	private String checkCredentialByPassword(Utilisateur u, String password)
	{
		byte[] encryptedPassword = toByteArray(u.password);
		byte[] salt = toByteArray(u.salt);

		
		// Verification du password
		if (new PasswordEncryptionService().authenticate(password, encryptedPassword, salt)==false)
		{
			return "Adresse e-mail ou mot de passe incorrect";
		}
		else
		{
			return null;
		}
	}


	/**
	 * Retrouve l'utilisateur avec cet e-mail
	 * Retourne null si non trouvé ou autre problème
	 */
	public Utilisateur findUser(String email,RdbLink em)
	{	
		if ((email==null) || email.equals("") || email.length()>150 || EmailValidator.isValidEmail(email)==false)
		{
			return null;
		}
		
		em.createQuery("select u from Utilisateur u where u.email=:email");
		em.setParameter("email", email);
		return em.result().oneOrZero(Utilisateur.class);
	}
	
	



	/**
	 * Permet de signifier la deconnexion d'un utilisateur
	 * 
	 */
	public void disconnect()
	{
		SessionParameters p = SessionManager.getSessionParameters();
		if (p==null)
		{
			return ;
		}
		
		logger.info("Déconnexion réussie pour {} {} {}",p.userNom,p.userPrenom,p.userId);
		
		SessionManager.setSessionParameters(null);
		new LogViewService().endAccess(p.logId,p.getNbError());
		AmapJLogManager.endLog(true,p.logFileName);
		
		
	}	
	
	
	// OUTILS  
	
	/**
	 * Permet de transformer une chaine base 64 en tableau de byte
	 */
	private byte[] toByteArray(String base64str)
	{
		return Base64.decodeBase64(base64str.getBytes());
	}
	
}
