package org.keycloak.federation.ldap;

import org.keycloak.models.utils.reflection.Reflections;
import org.picketlink.idm.config.LDAPMappingConfiguration;
import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
import org.picketlink.idm.ldap.internal.LDAPOperationManager;
import org.picketlink.idm.model.AttributedType;
import org.picketlink.idm.model.basic.User;
import org.picketlink.idm.query.IdentityQuery;
import org.picketlink.idm.spi.IdentityContext;

import javax.naming.directory.BasicAttributes;
import java.lang.reflect.Method;

import static org.picketlink.common.constants.LDAPConstants.*;

/**
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {

    public static Method GET_OPERATION_MANAGER_METHOD;
    public static Method CREATE_SEARCH_FILTER_METHOD;
    public static Method EXTRACT_ATTRIBUTES_METHOD;
    public static Method GET_ENTRY_IDENTIFIER_METHOD;

    public static final String SAM_ACCOUNT_NAME = "sAMAccountName";

    static {
        GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
        CREATE_SEARCH_FILTER_METHOD = getMethodOnLDAPStore("createIdentityTypeSearchFilter", IdentityQuery.class, LDAPMappingConfiguration.class);
        EXTRACT_ATTRIBUTES_METHOD = getMethodOnLDAPStore("extractAttributes", AttributedType.class, boolean.class);
        GET_ENTRY_IDENTIFIER_METHOD = getMethodOnLDAPStore("getEntryIdentifier", AttributedType.class);
    }

    @Override
    public void addAttributedType(IdentityContext context, AttributedType attributedType) {
        LDAPMappingConfiguration userMappingConfig = getConfig().getMappingConfig(attributedType.getClass());
        String ldapUsernameAttrName = userMappingConfig.getMappedProperties().get(userMappingConfig.getIdProperty().getName());

        if (getConfig().isActiveDirectory() && SAM_ACCOUNT_NAME.equals(ldapUsernameAttrName)) {
            // TODO: pain, but everything in picketlink is private... Improve if possible...
            LDAPOperationManager operationManager = Reflections.invokeMethod(false, GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, this);

            String cn = getCommonName(attributedType);
            String bindingDN = CN + EQUAL + cn + COMMA + userMappingConfig.getBaseDN();

            BasicAttributes ldapAttributes = Reflections.invokeMethod(false, EXTRACT_ATTRIBUTES_METHOD, BasicAttributes.class, this, attributedType, true);
            ldapAttributes.put(CN, cn);

            operationManager.createSubContext(bindingDN, ldapAttributes);

            String ldapId = Reflections.invokeMethod(false, GET_ENTRY_IDENTIFIER_METHOD, String.class, this, attributedType);
            attributedType.setId(ldapId);
        } else {
            super.addAttributedType(context, attributedType);
        }
    }

    // Hack as methods are protected on LDAPIdentityStore :/
    public static Method getMethodOnLDAPStore(String methodName, Class... classes) {
        try {
            Method m = LDAPIdentityStore.class.getDeclaredMethod(methodName, classes);
            m.setAccessible(true);
            return m;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected String getCommonName(AttributedType aType) {
        User user = (User)aType;
        String fullName;
        if (user.getFirstName() != null && user.getLastName() != null) {
            fullName = user.getFirstName() + " " + user.getLastName();
        } else if (user.getFirstName() != null && user.getFirstName().trim().length() > 0) {
            fullName = user.getFirstName();
        } else {
            fullName = user.getLastName();
        }

        // Fallback to loginName
        if (fullName == null || fullName.trim().length() == 0) {
            fullName = user.getLoginName();
        }

        return fullName;
    }
}
