package io.cattle.platform.iaas.api.auth.integration.ldap;

import io.cattle.platform.iaas.api.auth.SecurityConstants;
import io.cattle.platform.iaas.api.auth.integration.ldap.interfaces.LDAPConstants;
import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException;
import io.github.ibuildthecloud.gdapi.util.ResponseCodes;

import java.net.ConnectException;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.Context;
import javax.naming.CommunicationException;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LDAPUtils {

    private static final Log logger = LogFactory.getLog(LDAPUtils.class);

    private static final String INVALID_OPEN_LDAP_CONFIG = "InvalidLDAPConfig";
    private static final Pattern LDAP_ERROR_CODE = Pattern.compile("data +[57][2307][01235e]");



    /**
     * @throws ClientVisibleException In the event that the provided configuration is invalid. This allows the user to fix the config
     * and resubmit it, without locking them out of cattle.
     */
    public static void validateConfig(LDAPConstants ldapConfig) {
        if (ldapConfig.getEnabled() == null || !ldapConfig.getEnabled() || SecurityConstants.SECURITY.get()) {
            return;
        }
        if (StringUtils.isBlank(ldapConfig.getServiceAccountUsername()) || StringUtils.isBlank(ldapConfig.getServiceAccountPassword())) {
            throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, "InvalidLDAPConfig", "Must define service account.",
                    "Cannot have a config with blank service account user name or password.");
        }
        LdapContext userContext = null;
        try {
            Hashtable<String, String> props = new Hashtable<>();
            props.put(Context.SECURITY_AUTHENTICATION, "simple");
            String username = ldapConfig.getServiceAccountUsername();
            if (StringUtils.isNotBlank(ldapConfig.getLoginDomain()) && !username.contains("\\")) {
                username = ldapConfig.getLoginDomain() + '\\' +username;
            }
            props.put(Context.SECURITY_PRINCIPAL, username);
            props.put(Context.SECURITY_CREDENTIALS, ldapConfig.getServiceAccountPassword());
            props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            props.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(ldapConfig.getConnectionTimeout()));
            String url = "ldap://" + ldapConfig.getServer() + ':' + String.valueOf(ldapConfig.getPort()) + '/';
            props.put(Context.PROVIDER_URL, url);
            if (ldapConfig.getTls()) {
                props.put(Context.SECURITY_PROTOCOL, "ssl");
            }
            userContext = new InitialLdapContext(props, null);
            try {
                userContext.getAttributes(new LdapName(ldapConfig.getDomain()));
            } catch (NamingException e) {
                throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, INVALID_OPEN_LDAP_CONFIG, "Invalid ldap search base.",
                        "Unable to get provided Ldap Search base: " + ldapConfig.getDomain());
            }
        } catch (NamingException e) {
            if (e.getRootCause() instanceof ConnectException){
                throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, INVALID_OPEN_LDAP_CONFIG,
                        "Unable to talk to ldap.", "Provided server and port refused connection.");
            }
            throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, INVALID_OPEN_LDAP_CONFIG,
                    "Unable to authenticate service account.", "Unable to create context with service account credentials." +
                    "Username(DN):" + ldapConfig.getServiceAccountUsername() + " Password:" + ldapConfig.getServiceAccountPassword());
        }finally {
            if (userContext != null) {
                try {
                    userContext.close();
                } catch (NamingException e) {
                    logger.info("Failed to close Test service context.", e);
                }
            }
        }
    }

    public static String errorCodeToDescription(NamingException code){
        String errorCode = code.getExplanation();
        Matcher m = LDAP_ERROR_CODE.matcher(errorCode);
        if (m.find()) {
            errorCode = m.group(0).substring(m.group(0).length()-3);
        }
        switch (errorCode) {
            case "525":
                return "User not found";
            case "52e":
                return "Invalid credentials";
            case "530":
                return "Not permitted to logon at this time";
            case "531":
                return "Not permitted to logon at this workstation";
            case "532":
                return "Password expired (remember to check the user set in osuser.xml also)";
            case "533":
                return "Account disabled";
            case "701":
                return "Account expired";
            case "773":
                return "User must reset password";
            case "775":
                return "User account locked";
            default:
                return errorCode;
        }
    }

    public static boolean isRecoverable(NamingException e) {
        if (e instanceof CommunicationException) {
            return false;
        }
        return true;
    }
}
