package eu.dnetlib.users;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Random;
import java.util.UUID;

import org.apache.log4j.Logger;

import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.util.Base64;

import eu.dnetlib.domain.functionality.UserProfile;

public class UserApiLdapImpl implements UserApi {

	transient Logger logger = Logger.getLogger(UserApiLdapImpl.class);

	private int ldapPort = 0;
	private String ldapAddress;
	private String ldapUsername;
	private String ldapPassword;
	private String ldapUsersDN;

	@Override
	public boolean activateUser(String activationId) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("activating user with activationId " + activationId);
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("employeeNumber", activationId);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "uid");
			SearchResult searchResult = connection.search(searchRequest);
			String dn = null;

			if ( searchResult.getSearchEntries().size() > 0	) {
			
				for (SearchResultEntry entry : searchResult.getSearchEntries()) {
					dn = "uid=" + entry.getAttributeValue("uid") + "," + ldapUsersDN;
				}

				Modification mod1 = new Modification(ModificationType.REPLACE, "JoomlaBlockUser", "0");
				Modification mod2 = new Modification(ModificationType.REPLACE, "employeeNumber");
				connection.modify(dn, mod1, mod2);
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public String addUser(String username, String email, String password, String firstName, String lastName, String institution) throws Exception {
		logger.debug("adding user " + username + " " + email + " to ldap");
		Attribute cn = new Attribute("cn", username);
		Attribute displayName = new Attribute("displayName", firstName + " " + lastName);
		Attribute mail = new Attribute("mail", email);
		Attribute givenName = new Attribute("givenName", firstName);
		Attribute joomlaBlockUser = new Attribute("JoomlaBlockUser", "1");
		Attribute joomlaGroup = new Attribute("JoomlaGroup", "Registered");
		Attribute objectClass = new Attribute("objectClass", "top", "inetOrgPerson", "JoomlaUser");
		Attribute userPassword = new Attribute("userPassword", Joomla15PasswordHash.create(password));
		Attribute sn = new Attribute("sn", lastName);
		Attribute uid = new Attribute("uid", username);
		
			
		// Attribute joomlaUserParams = new Attribute("JoomlaUserParams", "");
		String activationId = UUID.randomUUID().toString();
		Attribute x500UniqueIdentifier = new Attribute("employeeNumber", activationId);
		LDAPConnection connection = null;
		try {
			DN dn = new DN("uid=" + username + "," + ldapUsersDN);
			Entry entry;
			if ((institution != null) && (institution.length() > 0)) {
				Attribute o = new Attribute("o", institution);
				entry = new Entry(dn.toNormalizedString(), cn, displayName, mail, givenName, joomlaBlockUser, joomlaGroup, objectClass, userPassword, sn, uid, x500UniqueIdentifier, o);
						
			} else {
				entry = new Entry(dn.toNormalizedString(), cn, displayName, mail, givenName, joomlaBlockUser, joomlaGroup, objectClass, userPassword, sn, uid/*
																																								 * ,
																																								 * ,
																																								 * ,
																																								 * joomlaUserParams
																																								 */, x500UniqueIdentifier);
			}
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			connection.add(entry);

			return activationId;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public boolean correctCreds(String email, String password) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("checking if user " + email + " entered a correct password when logging in");
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", email);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "userPassword");
			SearchResult searchResult = connection.search(searchRequest);
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				if (Joomla15PasswordHash.check(password, entry.getAttributeValue("userPassword")))
					return true;
			}
			return false;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public void editUser(UserProfile user) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("editing user " + user.getEmail());
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", user.getEmail());
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "uid");
			SearchResult searchResult = connection.search(searchRequest);
			String dn = null;
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				dn = "uid=" + entry.getAttributeValue("uid") + "," + ldapUsersDN;
			}
			Modification mod1 = new Modification(ModificationType.REPLACE, "displayName", user.getFirstname() + " " + user.getLastname());
			Modification mod2 = new Modification(ModificationType.REPLACE, "givenName", user.getFirstname());
			Modification mod3 = new Modification(ModificationType.REPLACE, "sn", user.getLastname());
			Modification mod4 = new Modification(ModificationType.REPLACE, "o", user.getInstitution());
			connection.modify(dn, mod1, mod2, mod3, mod4);
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public UserProfile getUser(String userIdentifier) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("getting user " + userIdentifier + " from ldap");
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", userIdentifier);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "mail", "givenName", "sn", "o", "uid");
			SearchResult searchResult = connection.search(searchRequest);
			UserProfile profile = new UserProfile();
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				profile.setEmail(entry.getAttributeValue("mail"));
				profile.setFirstname(entry.getAttributeValue("givenName"));
				profile.setLastname(entry.getAttributeValue("sn"));
				profile.setInstitution(entry.getAttributeValue("o"));
				profile.setUsername(entry.getAttributeValue("uid"));
			}
			return profile;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public boolean isAdmin(String email) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("checking if user " + email + " is an administrator");
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", email);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "JoomlaGroup");
			SearchResult searchResult = connection.search(searchRequest);

			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				for (String role : entry.getAttributeValues("JoomlaGroup"))
					if (role.equals("validatorAdmin"))
						return true;
			}
			logger.debug(email + " is not administrator");
			return false;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public boolean isUserActivated(String email) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("checking if user " + email + " is activated");
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", email);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "JoomlaBlockUser");
			SearchResult searchResult = connection.search(searchRequest);
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				int val = entry.getAttributeValueAsInteger("JoomlaBlockUser");
				if (val == 0)
					return true;
				else
					return false;
			}
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
		return false;
	}

	@Override
	public String prepareResetPassword(String email) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("preparing reset password for user " + email);
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", email);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "uid");
			SearchResult searchResult = connection.search(searchRequest);
			String dn = null;
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				dn = "uid=" + entry.getAttributeValue("uid") + "," + ldapUsersDN;
			}
			String uuid = UUID.randomUUID().toString();
			Modification mod = new Modification(ModificationType.REPLACE, "employeeNumber", uuid);
			connection.modify(dn, mod);
			return uuid;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public void resetPassword(String uuid, String password) throws Exception {
		LDAPConnection connection = null;
		try {
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("employeeNumber", uuid);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "uid");
			SearchResult searchResult = connection.search(searchRequest);
			String dn = null;
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				dn = "uid=" + entry.getAttributeValue("uid") + "," + ldapUsersDN;
			}
			Modification mod1 = new Modification(ModificationType.REPLACE, "userPassword", Joomla15PasswordHash.create(password));
			Modification mod2 = new Modification(ModificationType.REPLACE, "employeeNumber");
			connection.modify(dn, mod1, mod2);
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public boolean userExists(String email) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("checking if user " + email + " exists in ldap");
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("mail", email);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "mail");

			SearchResult searchResult = connection.search(searchRequest);
			if (!searchResult.getSearchEntries().isEmpty())
				return true;

			return false;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public boolean usernameExists(String username) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("checking if user " + username + " exists in ldap");
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("uid", username);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "uid");
			SearchResult searchResult = connection.search(searchRequest);

			if (!searchResult.getSearchEntries().isEmpty()) {
				return true;
			}

			return false;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	@Override
	public String getEmailFromUsername(String username) throws Exception {
		LDAPConnection connection = null;
		try {
			logger.debug("getting email for user " + username);
			connection = new LDAPConnection(ldapAddress, ldapPort, ldapUsername, ldapPassword);
			Filter filter = Filter.createEqualityFilter("uid", username);
			SearchRequest searchRequest = new SearchRequest(ldapUsersDN, SearchScope.SUB, filter, "mail");
			SearchResult searchResult = connection.search(searchRequest);
			for (SearchResultEntry entry : searchResult.getSearchEntries()) {
				return entry.getAttributeValue("mail");
			}
			return null;
		} catch (Exception e) {
			logger.error("", e);
			throw e;
		} finally {
			if (connection != null)
				connection.close();
		}
	}

	public void setLdapPort(int ldapPort) {
		this.ldapPort = ldapPort;
	}

	public void setLdapAddress(String ldapAddress) {
		this.ldapAddress = ldapAddress;
	}

	public void setLdapUsername(String ldapUsername) {
		this.ldapUsername = ldapUsername;
	}

	public void setLdapPassword(String ldapPassword) {
		this.ldapPassword = ldapPassword;
	}

	public void setLdapUsersDN(String ldapUsersDN) {
		this.ldapUsersDN = ldapUsersDN;
	}
}

class Joomla15PasswordHash 
{
   public static boolean check(String passwd,String dbEntry) {
      String hashed = "{MD5}"+Base64.encode(pack(Joomla15PasswordHash.md5(passwd)));
      if(dbEntry.equals(hashed))
    	  return true;
      else
    	  return false;
   }

   static Random _rnd;

   public static String create(String passwd) {
	   return "{MD5}"+Base64.encode(pack(Joomla15PasswordHash.md5(passwd)));
   }

   /** Takes the MD5 hash of a sequence of ASCII or LATIN1 characters,
    *  and returns it as a 32-character lowercase hex string.
    *
    *  Equivalent to MySQL's MD5() function 
    *  and to perl's Digest::MD5::md5_hex(),
    *  and to PHP's md5().
    *
    *  Does no error-checking of the input,  but only uses the low 8 bits
    *  from each input character.
    */
   public static String md5(String data) {
      byte[] bdata = new byte[data.length()]; int i; byte[] hash;

      for (i=0;i<data.length();i++) bdata[i]=(byte)(data.charAt(i)&0xff );

      try {
         MessageDigest md5er = MessageDigest.getInstance("MD5");
         hash = md5er.digest(bdata);
      } catch (GeneralSecurityException e) { throw new RuntimeException(e); }

      StringBuffer r = new StringBuffer(32);
      for (i=0;i<hash.length;i++) {
         String x = Integer.toHexString(hash[i]&0xff);
         if (x.length()<2) r.append("0");
         r.append(x);
      }
      return r.toString();      
   }
   
   public static byte[] pack(String md5) {
	    byte[] bytes = new byte[16];
	    int j = 0;
	    for(int i=0; i < 31; i+=2) {
	    	bytes[j] = (byte) Integer.parseInt(md5.charAt(i)+""+md5.charAt(i+1),16);
	    	j++;
	    }
	    return bytes;
	}
}

