package es.uv.saic.service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import es.uv.saic.SaicApplication;
import es.uv.saic.domain.Usuari;

@Component
public class AuthProvider implements AuthenticationProvider {

	@Autowired
	private UsuariService us;
	@Autowired
    private UsuarisRolService urs;
		
	private static final Logger logger = LoggerFactory.getLogger(SaicApplication.class);
	
	@Override
	public Authentication authenticate(Authentication auth) throws AuthenticationException {
		String username = auth.getName().toLowerCase().trim();
        String password = auth.getCredentials().toString(); 
        
        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
    	authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        
        Usuari u = this.us.findByUsername(username);
        
        if(u != null) {
        	u.setGranted(this.urs.isGrantedUser(u));
            u.setAdmin(this.urs.isAdminUser(u));
            u.setDataTest(this.urs.isDataTestUser(u));
        	boolean vigent = this.us.hasActiveRol(u);
        	
	        if(!u.getLdap() && vigent) {
	            if (u.getUsuari().equals(username) && u.getClau().equals(password)) {
	            	logger.info("Autenticación LOCAL correcta: "+username);
	            	if(u.isAdmin()) {
	            		authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
	            	}
	            	if(u.isGranted()) {
	            		authorities.add(new SimpleGrantedAuthority("ROLE_MANAGER"));
	            	}
	            	if(u.isDataTest()) {
	            		authorities.add(new SimpleGrantedAuthority("ROLE_TESTER"));
	            	}
	            	UsernamePasswordAuthenticationToken authUser = new UsernamePasswordAuthenticationToken(u, password, authorities);
	            	authUser.setDetails(u);
	                return authUser ;
	            } 
	            else {
	            	logger.info("Error de autenticación LOCAL ["+username+"]: el usuario o la contraseña no coinciden");
	                throw new BadCredentialsException("Error de autenticación LOCAL ["+username+"]: el usuario o la contraseña no coinciden");
	            }
	        }
	        else if(vigent) {
	        	
	        	Hashtable<String, String> env = new Hashtable<String, String>();

                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                env.put(Context.PROVIDER_URL, "ldap://ldap.uv.es/");
                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                env.put(Context.SECURITY_PRINCIPAL, "uid=" + username.toLowerCase().trim() + ", dc=uv, dc=es ");
                env.put(Context.SECURITY_CREDENTIALS, password);
                
                try {
                    DirContext dc = new InitialDirContext(env);
                    String base = "dc=uv,dc=es";
                    String filter = "(&(uid=" + username + "))";
                    int state = 0;

                    SearchControls ctls = new SearchControls();
                    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
                    ctls.setReturningAttributes(new String[] { "uid" });

                    NamingEnumeration<SearchResult> resultEnum = dc.search(base, filter, ctls);

                    while (resultEnum.hasMore() && state < 2) {
                        SearchResult result = resultEnum.next();

                        Attributes attrs = result.getAttributes();
                        if (attrs.size() != 1) {
                            logger.info("Error de autenticación LDAP ["+username+"]: el usuario o la contraseña no coinciden");
                            throw new BadCredentialsException("Error de autenticación LDAP ["+username+"]: el usuario o la contraseña no coinciden");
                        }
                        NamingEnumeration<?> e = attrs.getAll();
                        while (e.hasMore()) {
                            Attribute attr = (Attribute) e.next();
                            if (!((String) attr.get()).equals(username)) {
                                logger.info("Error de autenticación LDAP ["+username+"]: el usuario no coincide con el devuelto por el servidor");
                                throw new BadCredentialsException("Error de autenticación LDAP ["+username+"]: el usuario no coincide con el devuelto por el servidor");
                            }
                        }
                        state++;
                    }

                    dc.close();

                    if (state < 1 || state > 1) {
                        logger.info("Error de autenticación LDAP ["+username+"]: -> el servidor LDAP ha devuelto un estado incorrecto.");
                        throw new BadCredentialsException("Error de autenticación LDAP ["+username+"]: -> el servidor LDAP ha devuelto un estado incorrecto.");
                    }
                    logger.info("Autenticación LDAP correcta: " + u.getUsuari());
                    u.setDataUltim(LocalDateTime.now());
                    this.us.save(u);
	            	if(u.isAdmin()) {
	            		authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
	            	}
	            	if(u.isGranted()) {
	            		authorities.add(new SimpleGrantedAuthority("ROLE_MANAGER"));
	            	}
	            	if(u.isDataTest()) {
	            		authorities.add(new SimpleGrantedAuthority("ROLE_TESTER"));
	            	}
	            	UsernamePasswordAuthenticationToken authUser = new UsernamePasswordAuthenticationToken(u, password, authorities);
	            	authUser.setDetails(u);
	                return authUser ;
                } 
                catch (NamingException ex) {
                    logger.info("Error de autenticación ["+username+"]: " + ex.getMessage());
                    throw new AuthenticationServiceException("Error de autenticación ["+username+"]: " + ex.getMessage());
                }
	        }
        }
        
        throw new BadCredentialsException("Error general en el sistema de autenticación");
 

	}

	@Override
	public boolean supports(Class<?> auth) {
		return auth.equals(UsernamePasswordAuthenticationToken.class);
	}

}
