/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.installer.postinstall.ldap;

import org.jboss.as.controller.PathAddress;
import org.jboss.dmr.ModelNode;
import org.jboss.installer.postinstall.task.CredentialStoreInstallTask;
import org.jboss.installer.postinstall.task.LDAPSetupTask;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static org.jboss.as.controller.operations.common.Util.createEmptyOperation;

public class LdapSecurity {

    public static final String ALIAS_SUFFIX = "-secret";
    private static final String EMPTY_RDN = " ";

    public ModelNode createDirContext(Model config, Optional<CredentialStoreInstallTask.Config> storeConfig) {
        final String connection = config.getConnectionName();

        ModelNode op = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "elytron").append("dir-context", connection));
        op.get("url").set(config.getUrl());
        op.get("principal").set(config.getLdapUsername());
        final ModelNode credRefNode = op.get("credential-reference");
        if (storeConfig.isPresent()) {
            credRefNode.get("alias").set(config.getConnectionName() + ALIAS_SUFFIX);
            credRefNode.get("store").set(storeConfig.get().getStoreName());
        } else {
            credRefNode.get("clear-text").set(config.getLdapPassword());
        }

        return op;
    }

    public ModelNode createLdapRealm(Model config) {
        ModelNode op = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "elytron").append("ldap-realm", config.getRealmName()));
        op.get("dir-context").set(config.getConnectionName());

        final ModelNode idMapping = op.get("identity-mapping");
        idMapping.get("use-recursive-search").set(config.isRecursive());
        idMapping.get("search-base-dn").set(config.getSearchBaseDn());
        if (config.getFilterType() == LDAPSetupTask.FilterType.USERNAME) {
            idMapping.get("rdn-identifier").set(config.getUserFilter());
        } else {
            idMapping.get("rdn-identifier").set(EMPTY_RDN);
            idMapping.get("filter-name").set(config.getUserFilter());
        }
        final ModelNode pwdMapperNode = idMapping.get("user-password-mapper");
        pwdMapperNode.get("from").set(config.getPasswordAttribute());

        final ModelNode roleMapNode = new ModelNode();
        if (config.getRoleBaseDn() != null) {
            roleMapNode.get("filter-base-dn").set(config.getRoleBaseDn());
        }
        if (config.getRoleFilter() != null) {
            roleMapNode.get("filter").set(config.getRoleFilter());
        }
        if (config.getRoleAttribute() != null) {
            roleMapNode.get("from").set(config.getRoleAttribute());
        }
        roleMapNode.get("to").set("groups");
        idMapping.get("attribute-mapping").add(roleMapNode);

        return op;
    }

    public static class Model {
        // serialization keys
        public static final String LDAP_PASSWORD_KEY = "ldapPassword";
        private static final String URL = "url";
        private static final String LDAP_USERNAME = "ldapUsername";
        private static final String CONNECTION_NAME = "connectionName";
        private static final String USER_FILTER = "userFilter";
        private static final String RECURSIVE = "recursive";
        private static final String FILTER_TYPE = "filterType";
        private static final String SEARCH_BASE_DN = "searchBaseDn";
        private static final String PASSWORD_ATTRIBUTE = "passwordAttribute";
        private static final String REALM_NAME = "realmName";
        private static final String ROLE_BASE_DN = "roleBaseDn";
        private static final String ROLE_FILTER = "roleFilter";
        private static final String ROLE_ATTRIBUTE = "roleAttribute";
        // end of serialization keys
        private String url;
        private String ldapUsername;
        private String ldapPassword;
        private String connectionName;
        private String userFilter;
        private boolean recursive;
        private LDAPSetupTask.FilterType filterType;
        private String searchBaseDn;
        private String passwordAttribute;
        private String realmName;
        private String roleBaseDn;
        private String roleFilter;
        private String roleAttribute;

        public Model() {

        }

        public Model(Map<String, String> attributes) {
            url = attributes.get(URL);
            ldapUsername = attributes.get(LDAP_USERNAME);
            ldapPassword = attributes.get(LDAP_PASSWORD_KEY);
            connectionName = attributes.get(CONNECTION_NAME);
            userFilter = attributes.get(USER_FILTER);
            recursive = Boolean.parseBoolean(attributes.get(RECURSIVE));
            filterType = LDAPSetupTask.FilterType.valueOf(attributes.get(FILTER_TYPE));
            searchBaseDn = attributes.get(SEARCH_BASE_DN);
            passwordAttribute = attributes.get(PASSWORD_ATTRIBUTE);
            realmName = attributes.get(REALM_NAME);
            roleBaseDn = attributes.get(ROLE_BASE_DN);
            roleFilter = attributes.get(ROLE_FILTER);
            roleAttribute = attributes.get(ROLE_ATTRIBUTE);
        }

        public Map<String, String> toAttributes() {
            final HashMap<String, String> attrs = new HashMap<>();
            attrs.put(URL, url);
            attrs.put(LDAP_USERNAME, ldapUsername);
            attrs.put(CONNECTION_NAME, connectionName);
            attrs.put(USER_FILTER, userFilter);
            attrs.put(RECURSIVE, recursive + "");
            attrs.put(FILTER_TYPE, filterType.toString());
            attrs.put(SEARCH_BASE_DN, searchBaseDn);
            attrs.put(PASSWORD_ATTRIBUTE, passwordAttribute);
            attrs.put(REALM_NAME, realmName);
            attrs.put(ROLE_BASE_DN, roleBaseDn);
            attrs.put(ROLE_FILTER, roleFilter);
            attrs.put(ROLE_ATTRIBUTE, roleAttribute);
            return attrs;
        }

        public String getUrl() {
            return url;
        }

        public String getLdapUsername() {
            return ldapUsername;
        }

        public String getLdapPassword() {
            return ldapPassword;
        }

        public String getConnectionName() {
            return connectionName;
        }

        public String getUserFilter() {
            return userFilter;
        }

        public boolean isRecursive() {
            return recursive;
        }

        public LDAPSetupTask.FilterType getFilterType() {
            return filterType;
        }

        public String getSearchBaseDn() {
            return searchBaseDn;
        }

        public String getPasswordAttribute() {
            return passwordAttribute;
        }

        public String getRealmName() {
            return realmName;
        }

        public String getRoleBaseDn() {
            return roleBaseDn;
        }

        public String getRoleFilter() {
            return roleFilter;
        }

        public String getRoleAttribute() {
            return roleAttribute;
        }

        public void setRealmName(String realmName) {
            this.realmName = realmName;
        }

        public void setConnection(String name, String url, String username, String password) {
            this.connectionName = name;
            this.ldapUsername = username;
            this.ldapPassword = password;
            this.url = url;
        }

        public void setUserFilter(LDAPSetupTask.FilterType filterType, String userFilter, String searchBaseDn, String passwordAttribute, boolean recursive) {
            this.filterType = filterType;
            this.searchBaseDn = searchBaseDn;
            this.userFilter = userFilter;
            this.passwordAttribute = passwordAttribute;
            this.recursive = recursive;
        }

        public void setRole(String roleBaseDn, String roleFilter, String roleAttribute) {
            this.roleBaseDn = roleBaseDn;
            this.roleFilter = roleFilter;
            this.roleAttribute = roleAttribute;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Model model = (Model) o;
            return recursive == model.recursive && Objects.equals(url, model.url) && Objects.equals(ldapUsername, model.ldapUsername) && Objects.equals(ldapPassword, model.ldapPassword) && Objects.equals(connectionName, model.connectionName) && Objects.equals(userFilter, model.userFilter) && filterType == model.filterType && Objects.equals(searchBaseDn, model.searchBaseDn) && Objects.equals(passwordAttribute, model.passwordAttribute) && Objects.equals(realmName, model.realmName) && Objects.equals(roleBaseDn, model.roleBaseDn) && Objects.equals(roleFilter, model.roleFilter) && Objects.equals(roleAttribute, model.roleAttribute);
        }

        @Override
        public int hashCode() {
            return Objects.hash(url, ldapUsername, ldapPassword, connectionName, userFilter, recursive, filterType, searchBaseDn, passwordAttribute, realmName, roleBaseDn, roleFilter, roleAttribute);
        }

        @Override
        public String toString() {
            return "Model{" +
                    "url='" + url + '\'' +
                    ", ldapUsername='" + ldapUsername + '\'' +
                    ", ldapPassword='" + ldapPassword + '\'' +
                    ", connectionName='" + connectionName + '\'' +
                    ", userFilter='" + userFilter + '\'' +
                    ", recursive=" + recursive +
                    ", filterType=" + filterType +
                    ", searchBaseDn='" + searchBaseDn + '\'' +
                    ", passwordAttribute='" + passwordAttribute + '\'' +
                    ", realmName='" + realmName + '\'' +
                    ", roleBaseDn='" + roleBaseDn + '\'' +
                    ", roleFilter='" + roleFilter + '\'' +
                    ", roleAttribute='" + roleAttribute + '\'' +
                    '}';
        }
    }
}
