package org.apereo.cas.support.spnego.authentication.handler.support;

import com.google.common.base.Throwables;
import jcifs.Config;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import javax.annotation.PostConstruct;
import java.net.URL;

/**
 * Configuration helper for JCIFS and the Spring framework.
 *
 * @author Marc-Antoine Garrigue
 * @author Arnaud Lesueur
 * @author Scott Battaglia
 * @since 4.2.0
 */
public class JcifsConfig {

    private static final String DEFAULT_LOGIN_CONFIG = "/login.conf";

    private static final String SYS_PROP_USE_SUBJECT_CRED_ONLY = "javax.security.auth.useSubjectCredsOnly";

    private static final String SYS_PROP_LOGIN_CONF = "java.security.auth.login.config";

    private static final String SYS_PROP_KERBEROS_DEBUG = "sun.security.krb5.debug";

    private static final String SYS_PROP_KERBEROS_CONF = "java.security.krb5.conf";

    private static final String SYS_PROP_KERBEROS_REALM = "java.security.krb5.realm";

    private static final String SYS_PROP_KERBEROS_KDC = "java.security.krb5.kdc";

    private static final String JCIFS_PROP_DOMAIN_CONTROLLER = "jcifs.http.domainController";

    private static final String JCIFS_PROP_NETBIOS_WINS = "jcifs.netbios.wins";

    private static final String JCIFS_PROP_CLIENT_DOMAIN = "jcifs.smb.client.domain";

    private static final String JCIFS_PROP_CLIENT_USERNAME = "jcifs.smb.client.username";

    private static final String JCIFS_PROP_CLIENT_PASSWORD = "jcifs.smb.client.password";

    private static final String JCIFS_PROP_CLIENT_SOTIMEOUT = "jcifs.smb.client.soTimeout";

    private static final String JCIFS_PROP_NETBIOS_CACHE_POLICY = "jcifs.netbios.cachePolicy";

    /**
     * -- the service principal you just created. Using the previous example,
     * this would be "HTTP/mybox at DOMAIN.COM".
     */
    private static final String JCIFS_PROP_SERVICE_PRINCIPAL = "jcifs.spnego.servicePrincipal";

    /**
     * The password for the service principal account, required only if you
     * decide not to use keytab.
     */
    private static final String JCIFS_PROP_SERVICE_PASSWORD = "jcifs.spnego.servicePassword";

    private static final Logger LOGGER = LoggerFactory.getLogger(JcifsConfig.class);

    private String loginConf;
    
    @Autowired
    private ResourceLoader resourceLoader;

    /**
     * Instantiates a new jCIFS config.
     */
    public JcifsConfig() {
        Config.setProperty(JCIFS_PROP_CLIENT_SOTIMEOUT, "300000");
        Config.setProperty(JCIFS_PROP_NETBIOS_CACHE_POLICY, "600");
    }

    /**
     * Init.
     */
    @PostConstruct
    public void init() {
        configureJaasLoginConfig();
    }

    /**
     * Configure jaas login config location and set it as a system property.
     */
    protected void configureJaasLoginConfig() {
        try {
            final String propValue = System.getProperty(SYS_PROP_LOGIN_CONF);
            if (StringUtils.isNotBlank(propValue)) {
                LOGGER.info("Found login config [{}] in system property [{}]", propValue, SYS_PROP_LOGIN_CONF);
                if (StringUtils.isNotBlank(this.loginConf)) {
                    LOGGER.warn("Configured login config for CAS under [{}] will be ignored", this.loginConf);
                }
            } else {
                final String loginConf = StringUtils.isBlank(this.loginConf) ? DEFAULT_LOGIN_CONFIG : this.loginConf;
                LOGGER.debug("Attempting to load login config from [{}]", loginConf);

                final Resource res = this.resourceLoader.getResource(loginConf);
                if (res != null && res.exists()) {
                    final String urlPath = res.getURL().toExternalForm();
                    LOGGER.debug("Located login config [{}] and configured it under [{}]", urlPath, SYS_PROP_LOGIN_CONF);
                    System.setProperty(SYS_PROP_LOGIN_CONF, urlPath);
                } else {
                    final URL url = getClass().getResource("/jcifs/http/login.conf");
                    if (url != null) {
                        LOGGER.debug("Falling back unto default login config [{}] under [{}]", url.toExternalForm(), SYS_PROP_LOGIN_CONF);
                        System.setProperty(SYS_PROP_LOGIN_CONF, url.toExternalForm());
                    }
                }
                LOGGER.debug("configured login configuration path : [{}]", propValue);
            }
        } catch (final Exception e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Sets the jcifs service password.
     *
     * @param jcifsServicePassword the new jcifs service password
     */
    public void setJcifsServicePassword(final String jcifsServicePassword) {
        if (StringUtils.isNotBlank(jcifsServicePassword)) {
            LOGGER.debug("jcifsServicePassword is set to *****");
            Config.setProperty(JCIFS_PROP_SERVICE_PASSWORD, jcifsServicePassword);
        }
    }

    /**
     * Sets the jcifs service principal.
     *
     * @param jcifsServicePrincipal the new jcifs service principal
     */
    public void setJcifsServicePrincipal(final String jcifsServicePrincipal) {
        if (StringUtils.isNotBlank(jcifsServicePrincipal)) {
            LOGGER.debug("jcifsServicePrincipal is set to [{}]", jcifsServicePrincipal);
            Config.setProperty(JCIFS_PROP_SERVICE_PRINCIPAL, jcifsServicePrincipal);
        }
    }

    /**
     * Sets the kerberos conf.
     *
     * @param kerberosConf the new kerberos conf
     */
    public void setKerberosConf(final String kerberosConf) {
        if (StringUtils.isNotBlank(kerberosConf)) {

            LOGGER.debug("kerberosConf is set to :[{}]", kerberosConf);
            System.setProperty(SYS_PROP_KERBEROS_CONF, kerberosConf);
        }
    }

    /**
     * Sets the kerberos kdc.
     *
     * @param kerberosKdc the new kerberos kdc
     */
    public void setKerberosKdc(final String kerberosKdc) {
        if (StringUtils.isNotBlank(kerberosKdc)) {
            LOGGER.debug("kerberosKdc is set to : [{}]", kerberosKdc);
            System.setProperty(SYS_PROP_KERBEROS_KDC, kerberosKdc);
        }
    }

    /**
     * Sets the kerberos realm.
     *
     * @param kerberosRealm the new kerberos realm
     */
    public void setKerberosRealm(final String kerberosRealm) {
        if (StringUtils.isNotBlank(kerberosRealm)) {
            LOGGER.debug("kerberosRealm is set to :[{}]", kerberosRealm);
            System.setProperty(SYS_PROP_KERBEROS_REALM, kerberosRealm);
        }
    }
    
    public void setLoginConf(final String loginConf) {
        this.loginConf = loginConf;
    }

    /**
     * Sets the use subject creds only.
     *
     * @param useSubjectCredsOnly the new use subject creds only
     */
    public void setUseSubjectCredsOnly(final boolean useSubjectCredsOnly) {
        LOGGER.debug("useSubjectCredsOnly is set to [{}]", useSubjectCredsOnly);
        System.setProperty(SYS_PROP_USE_SUBJECT_CRED_ONLY, Boolean.toString(useSubjectCredsOnly));
    }

    /**
     * Sets the kerberos debug.
     *
     * @param kerberosDebug the new kerberos debug
     */
    public void setKerberosDebug(final String kerberosDebug) {
        if (StringUtils.isNotBlank(kerberosDebug)) {
            LOGGER.debug("kerberosDebug is set to : [{}]", kerberosDebug);
            System.setProperty(SYS_PROP_KERBEROS_DEBUG, kerberosDebug);
        }
    }

    /**
     * @param jcifsDomain the jcifsDomain to set
     */
    public void setJcifsDomain(final String jcifsDomain) {
        if (StringUtils.isNotBlank(jcifsDomain)) {
            LOGGER.debug("jcifsDomain is set to [{}]", jcifsDomain);
            Config.setProperty(JCIFS_PROP_CLIENT_DOMAIN, jcifsDomain);
        }
    }

    /**
     * @param jcifsDomainController the jcifsDomainController to set
     */
    public void setJcifsDomainController(final String jcifsDomainController) {
        if (StringUtils.isNotBlank(jcifsDomainController)) {
            LOGGER.debug("jcifsDomainController is set to [{}]", jcifsDomainController);
            Config.setProperty(JCIFS_PROP_DOMAIN_CONTROLLER, jcifsDomainController);
        }
    }

    /**
     * @param jcifsPassword the jcifsPassword to set
     */
    public void setJcifsPassword(final String jcifsPassword) {
        if (StringUtils.isNotBlank(jcifsPassword)) {
            Config.setProperty(JCIFS_PROP_CLIENT_PASSWORD, jcifsPassword);
            LOGGER.debug("jcifsPassword is set to *****");
        }
    }

    /**
     * @param jcifsUsername the jcifsUsername to set
     */
    public void setJcifsUsername(final String jcifsUsername) {
        if (StringUtils.isNotBlank(jcifsUsername)) {
            LOGGER.debug("jcifsUsername is set to [{}]", jcifsUsername);
            Config.setProperty(JCIFS_PROP_CLIENT_USERNAME, jcifsUsername);
        }
    }

    /**
     * @param jcifsNetbiosWins the jcifsNetbiosWins to set
     */
    public void setJcifsNetbiosWins(final String jcifsNetbiosWins) {
        if (StringUtils.isNotBlank(jcifsNetbiosWins)) {
            LOGGER.debug("jcifsNetbiosWins is set to [{}]", jcifsNetbiosWins);
            Config.setProperty(JCIFS_PROP_NETBIOS_WINS, jcifsNetbiosWins);
        }
    }

    /**
     * Sets jcifs netbios cache policy.
     *
     * @param policy the policy
     */
    public void setJcifsNetbiosCachePolicy(final long policy) {
        if (policy > 0) {
            LOGGER.debug("jcifsNetbiosCachePolicy is set to [{}]", policy);
            Config.setProperty(JCIFS_PROP_NETBIOS_CACHE_POLICY, String.valueOf(policy));
        }
    }

    /**
     * Sets jcifs socket timeout.
     *
     * @param timeout the timeout
     */
    public void setJcifsSocketTimeout(final long timeout) {
        if (timeout > 0) {
            LOGGER.debug("jcifsSocketTimeout is set to [{}]", timeout);
            Config.setProperty(JCIFS_PROP_CLIENT_SOTIMEOUT, String.valueOf(timeout));
        }
    }
}
