package eu.dnetlib.openaire.user.utils;

import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest;
import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedResult;
import eu.dnetlib.openaire.user.store.LDAPConnector;
import org.apache.commons.validator.routines.EmailValidator;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by kiatrop on 29/9/2017.
 */

public class LDAPActions {

    private LDAPConnector ldapConnector;

    private Logger logger = Logger.getLogger(LDAPConnector.class);

    public String getUsername(String email) throws LDAPException {
        Filter filter = Filter.createEqualityFilter("mail", email);
        SearchRequest searchRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, filter, "uid");
        SearchResult searchResult = ldapConnector.getConnection().search(searchRequest);

        if (searchResult.getSearchEntries() != null) {
            if (searchResult.getSearchEntries().size() > 1) {
                logger.warn("An email is used for two different usernames! We only keep the first one.");
            }

            if (searchResult.getSearchEntries().size() == 0) {
                return null;
            }

            if (searchResult.getSearchEntries().get(0) != null) {
                return searchResult.getSearchEntries().get(0).getAttributeValue("uid");
            }
        }

        return null;
    }

    public Entry createUser(String username, String email, String firstName, String lastName, String institution, String password) throws Exception {

        if(!InputValidator.isValidUsername(username)) {
            throw new CustomLDAPException("Invalid username!");
        }

        if(!EmailValidator.getInstance().isValid(email)){
            throw new CustomLDAPException("Invalid email!");
        }

        if(!InputValidator.isValidPassword(password)) {
            throw new CustomLDAPException("Invalid password!");
        }

        Filter uidFilter = Filter.createEqualityFilter("uid", username);
        SearchRequest uidRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, uidFilter, "cn", "mail", "uid", "objectClass");

        Filter mailFilter = Filter.createEqualityFilter("mail", email);
        SearchRequest mailRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, mailFilter, "cn", "mail", "uid", "objectClass");

        SearchResult searchResult = ldapConnector.getConnection().search(uidRequest);
        if(!searchResult.getSearchEntries().isEmpty()){
            throw new CustomLDAPException("Username " + username + " already exists!");
        }
        searchResult = ldapConnector.getConnection().search(mailRequest);
        if(!searchResult.getSearchEntries().isEmpty()){
            throw new CustomLDAPException("Email " + email + " already exists!");
        }

        Entry entry = new Entry("dn: uid=" + username + ",ou=users,dc=openaire,dc=eu",
                "objectClass: inetOrgPerson",
                "objectClass: eduPerson",
                "cn: "  + username,
                "uid: " + username,
                "displayName: " + firstName + " " + lastName,
                "mail: " + email,
                "givenName: " + firstName,
                "sn: " + lastName,
                "eduPersonPrincipalName: " + username + "@openaire.eu");

        if(institution != null && !institution.isEmpty()) {
            entry.addAttribute("o", institution);
        }

        ldapConnector.getConnection().add(entry);
        PasswordModifyExtendedRequest passwordModifyExtendedRequest = new PasswordModifyExtendedRequest(entry.getDN(), (String) null, password);
        PasswordModifyExtendedResult passwordModifyResult = (PasswordModifyExtendedResult) ldapConnector.getConnection().processExtendedOperation(passwordModifyExtendedRequest);
        logger.info("User: " + username + " was created successfully!");

        return entry;
    }

    public void updateUser(String username, String email, String firstName, String lastName, String password) throws Exception {
        SearchRequest searchRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, Filter.createEqualityFilter("uid", username), "mail", "givenName", "sn", "displayName");
        SearchResult searchResult = ldapConnector.getConnection().search(searchRequest);
        List<Modification> mods = new ArrayList<>();

        if (!searchResult.getSearchEntries().isEmpty()) {
            Entry entry = searchResult.getSearchEntries().get(0);
            if(!entry.getAttributeValue("mail").equals(email)){
                if(!EmailValidator.getInstance().isValid(email)){
                    throw new CustomLDAPException("Invalid email!");
                }
                Filter uidFilter = Filter.createEqualityFilter("uid", username);
                Filter mailFilter = Filter.createEqualityFilter("mail", email);
                SearchRequest mailRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, Filter.createANDFilter(mailFilter, Filter.createNOTFilter(uidFilter)), "mail", "givenName", "sn", "displayName");
                SearchResult mailResult = ldapConnector.getConnection().search(mailRequest);
                if(!mailResult.getSearchEntries().isEmpty()){
                    throw new CustomLDAPException("Email " + email + " already in use!");
                }
                mods.add(new Modification(ModificationType.REPLACE, "mail", email));
            }
            if(entry.getAttributeValue("givenName") == null){
                mods.add(new Modification(ModificationType.ADD, "givenName", firstName));
            } else if(!entry.getAttributeValue("givenName").equals(firstName)){
                mods.add(new Modification(ModificationType.REPLACE, "givenName", firstName));
            }
            if(entry.getAttributeValue("sn") == null){
                mods.add(new Modification(ModificationType.ADD, "sn", lastName));
            } else if(!entry.getAttributeValue("sn").equals(lastName)){
                mods.add(new Modification(ModificationType.REPLACE, "sn", lastName));
            }
            if(entry.getAttributeValue("displayName") == null) {
                mods.add(new Modification(ModificationType.ADD, "displayName", firstName + " " + lastName));
            } else if (!entry.getAttributeValue("displayName").equals(firstName + " " + lastName)) {
                mods.add(new Modification(ModificationType.REPLACE, "displayName", firstName + " " + lastName));
            }

            if(!InputValidator.isValidPassword(password)) {
                throw new CustomLDAPException("Invalid password!");
            }

            //mods.add(new Modification(ModificationType.REPLACE, "userPassword",password));
            if(!mods.isEmpty()) {
                ldapConnector.getConnection().modify(entry.getDN(), mods);
            }
            PasswordModifyExtendedRequest passwordModifyExtendedRequest = new PasswordModifyExtendedRequest(entry.getDN(), (String) null, password);
            PasswordModifyExtendedResult passwordModifyResult = (PasswordModifyExtendedResult) ldapConnector.getConnection().processExtendedOperation(passwordModifyExtendedRequest);
        } else {
            throw new CustomLDAPException("Username " + username + " not found!");
        }
    }

//    public void resetPassword(String username, String email, String password) throws Exception {
//        SearchRequest searchRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, Filter.createEqualityFilter("uid", username), "mail");
//        SearchResult searchResult = ldapConnector.getConnection().search(searchRequest);
//        List<Modification> mods = new ArrayList<>();
//
//        if (!searchResult.getSearchEntries().isEmpty()) {
//            Entry entry = searchResult.getSearchEntries().get(0);
//            if(!entry.getAttributeValue("mail").equals(email)){
//                if(!EmailValidator.getInstance().isValid(email)){
//                    throw new CustomLDAPException("Invalid email!");
//                }
//                Filter uidFilter = Filter.createEqualityFilter("uid", username);
//                Filter mailFilter = Filter.createEqualityFilter("mail", email);
//                SearchRequest mailRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, Filter.createANDFilter(mailFilter, Filter.createNOTFilter(uidFilter)), "mail", "givenName", "sn", "displayName");
//                SearchResult mailResult = ldapConnector.getConnection().search(mailRequest);
//                if(!mailResult.getSearchEntries().isEmpty()){
//                    throw new CustomLDAPException("Email " + email + " already in use!");
//                }
//                mods.add(new Modification(ModificationType.REPLACE, "mail", email));
//            }
//            //mods.add(new Modification(ModificationType.REPLACE, "userPassword",password));
//            if(!mods.isEmpty()) {
//                ldapConnector.getConnection().modify(entry.getDN(), mods);
//            }
//              if(!password.matches("(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,}")){
//                   throw new CustomLDAPException("Invalid password!");
//              }
//            PasswordModifyExtendedRequest passwordModifyExtendedRequest = new PasswordModifyExtendedRequest(entry.getDN(), (String) null, password);
//            PasswordModifyExtendedResult passwordModifyResult = (PasswordModifyExtendedResult) ldapConnector.getConnection().processExtendedOperation(passwordModifyExtendedRequest);
//        } else {
//            throw new CustomLDAPException("Username " + username + " not found!");
//        }
//    }

    public void deleteUser(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.info("User: " + username + "was deleted!");
            ldapConnector.getConnection().delete("uid=" + username + "," + ldapConnector.getUsersDN());
        } catch (Exception e) {
            logger.error("Fail to delete user.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public void deleteZombieUser(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.info("User: " + username + "was deleted!");
            ldapConnector.getConnection().delete("uid=" + username + "," + ldapConnector.getZombiesDN());
        } catch (Exception e) {
            logger.error("Fail to delete user.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public String getUsersEmail(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();
        try {
            logger.debug("getting email for user " + username);
            Filter filter = Filter.createEqualityFilter("uid", username);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getUsersDN(), 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("Fail to get user's email exists.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public Entry createZombieUser(String username, String email, String firstName, String lastName, String institution, String password) throws Exception {

        if(!InputValidator.isValidUsername(username)) {
            throw new CustomLDAPException("Invalid username!");
        }

        if(!EmailValidator.getInstance().isValid(email)){
            throw new CustomLDAPException("Invalid email!");
        }

        if(!InputValidator.isValidPassword(password)) {
            throw new CustomLDAPException("Invalid password!");
        }

        Filter uidFilter = Filter.createEqualityFilter("uid", username);
        SearchRequest uidRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, uidFilter, "cn", "mail", "uid", "objectClass");

        Filter mailFilter = Filter.createEqualityFilter("mail", email);
        SearchRequest mailRequest = new SearchRequest("dc=openaire,dc=eu", SearchScope.SUB, mailFilter, "cn", "mail", "uid", "objectClass");

        SearchResult searchResult = ldapConnector.getConnection().search(uidRequest);
        if(!searchResult.getSearchEntries().isEmpty()){
            logger.info("S" + searchResult.getSearchEntries());
            throw new CustomLDAPException("Username " + username + " already exists!");
        }
        searchResult = ldapConnector.getConnection().search(mailRequest);
        if(!searchResult.getSearchEntries().isEmpty()){
            throw new CustomLDAPException("Email " + email + " already exists!");
        }

        Entry entry = new Entry("dn: uid=" + username + ",ou=zombies,dc=openaire,dc=eu",
                "objectClass: inetOrgPerson",
                "objectClass: eduPerson",
                "cn: "  + username,
                "uid: " + username,
                "displayName: " + firstName + " " + lastName,
                "mail: " + email,
                "givenName: " + firstName,
                "sn: " + lastName,
                "eduPersonPrincipalName: " + username + "@openaire.eu");

        if(institution != null && !institution.isEmpty()) {
            entry.addAttribute("o", institution);
        }

        ldapConnector.getConnection().add(entry);
        PasswordModifyExtendedRequest passwordModifyExtendedRequest = new PasswordModifyExtendedRequest(entry.getDN(), (String) null, password);
        PasswordModifyExtendedResult passwordModifyResult = (PasswordModifyExtendedResult) ldapConnector.getConnection().processExtendedOperation(passwordModifyExtendedRequest);
        logger.info("User: " + username + " was created successfully!");

        return entry;
    }

    public void moveUser(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.info("Moving user:" +username);
            ModifyDNRequest modifyDNRequest =
                    new ModifyDNRequest("uid=" + username + ",ou=zombies,dc=openaire,dc=eu", "uid=" + username, true, "ou=users,dc=openaire,dc=eu");

            LDAPResult result = connection.modifyDN(modifyDNRequest);
        }
        catch (Exception e){
            logger.error("Fail to move user.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }

    }

    public boolean isZombieUsersEmail(String email) throws Exception{
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.debug("checking if zombie user " + email + " exists in ldap");
            Filter filter = Filter.createEqualityFilter("mail", email);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getZombiesDN(), SearchScope.SUB, filter, "mail");
            SearchResult searchResult = connection.search(searchRequest);

            if (!searchResult.getSearchEntries().isEmpty()) {
                logger.info("Zombie user with email: " + email + " exists!");
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            logger.error("Fail to check if zombie user email exists.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public boolean isZombieUsersUsername(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.debug("checking if zombie user " + username + " exists in ldap");
            Filter filter = Filter.createEqualityFilter("uid", username);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getZombiesDN(), SearchScope.SUB, filter, "uid");
            SearchResult searchResult = connection.search(searchRequest);

            if (!searchResult.getSearchEntries().isEmpty()) {
                logger.info("Zombie user with username: " + username + " exists!");
                return true;
            } else {
                return false;
            }

        } catch (Exception e) {
            logger.error("Fail to check if zombie username exists.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public String getZombieUsersEmail(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();
        try {
            logger.debug("getting email for zombie user " + username);
            Filter filter = Filter.createEqualityFilter("uid", username);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getZombiesDN(), 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("Fail to get zombie user's email exists.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public String getZombieUsersUserName(String email) throws LDAPException {
        Filter filter = Filter.createEqualityFilter("mail", email);
        SearchRequest searchRequest = new SearchRequest(ldapConnector.getZombiesDN(), SearchScope.SUB, filter, "uid");
        SearchResult searchResult = ldapConnector.getConnection().search(searchRequest);

        if (searchResult.getSearchEntries() != null) {
            if (searchResult.getSearchEntries().size() > 1) {
                logger.warn("An email is used for two different usernames! We only keep the first one.");
            }

            if (searchResult.getSearchEntries().size() == 0) {
                return null;
            }

            if (searchResult.getSearchEntries().get(0) != null) {
                return searchResult.getSearchEntries().get(0).getAttributeValue("uid");
            }
        }

        return null;
    }

    public boolean emailExists(String email) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.debug("checking if user " + email + " exists in ldap");
            Filter filter = Filter.createEqualityFilter("mail", email);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getUsersDN(), SearchScope.SUB, filter, "mail");
            SearchResult searchResult = connection.search(searchRequest);

            if (!searchResult.getSearchEntries().isEmpty()) {
                logger.info("User with email: " + email + " exists!");
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            logger.error("Fail to check if user email exists.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public boolean usernameExists(String username) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {
            logger.debug("checking if user " + username + " exists in ldap");
            Filter filter = Filter.createEqualityFilter("uid", username);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getUsersDN(), SearchScope.SUB, filter, "uid");
            SearchResult searchResult = connection.search(searchRequest);

            if (!searchResult.getSearchEntries().isEmpty()) {
                logger.info("User with username: " + username + " exists!");
                return true;
            } else {
                return false;
            }

        } catch (Exception e) {
            logger.error("Fail to check if username exists.", e);
            throw e;
        } finally {
            if (connection != null)
                connection.close();
        }
    }

    public void resetPassword(String username, String password) throws Exception {
        LDAPConnection connection = ldapConnector.getConnection();

        try {

            Filter filter = Filter.createEqualityFilter("uid", username);
            SearchRequest searchRequest = new SearchRequest(ldapConnector.getUsersDN(), SearchScope.SUB, filter, "uid");
            SearchResult searchResult = connection.search(searchRequest);
            String dn = null;
            for (SearchResultEntry entry : searchResult.getSearchEntries()) {
                dn = "uid=" + entry.getAttributeValue("uid") + "," + ldapConnector.getUsersDN();
                //logger.info("dn " + dn);
            }

            if(!InputValidator.isValidPassword(password)) {
                throw new CustomLDAPException("Invalid password!");
            }

            //Modification mod1 = new Modification(ModificationType.REPLACE, "userPassword", password);
            PasswordModifyExtendedRequest passwordModifyExtendedRequest = new PasswordModifyExtendedRequest(dn, (String) null, password);
            PasswordModifyExtendedResult passwordModifyResult = (PasswordModifyExtendedResult) ldapConnector.getConnection().processExtendedOperation(passwordModifyExtendedRequest);
            //connection.modify(dn, mod1);

        } catch (Exception e) {
            logger.error("Fail to reset password.", e);
            throw e;

        } finally {
            if (connection != null)
                connection.close();
        }
    }


    public LDAPConnector getLdapConnector() {
        return ldapConnector;
    }

    public void setLdapConnector(LDAPConnector ldapConnector) {
        this.ldapConnector = ldapConnector;
    }
}
