/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.task.secdom;

import org.jboss.as.controller.PathAddress;
import org.jboss.dmr.ModelNode;
import org.jboss.installer.postinstall.ldap.SecurityDomain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.jboss.as.controller.operations.common.Util.createEmptyOperation;
import static org.jboss.installer.postinstall.task.SecurityDomainTask.REALM_SUFFIX;

public class DatabaseSecurity {

    public List<ModelNode> toOperations(String domainName, JdbcConfig jdbcConfig) {
        final ArrayList<ModelNode> ops = new ArrayList<>();
        final String datasourceName = jdbcConfig.getDatasourceName();
        final String sqlQuery = jdbcConfig.getSqlQuery();
        final JdbcConfig.MapperType mapperType = jdbcConfig.getMapperType();
        final String passwordIndex = jdbcConfig.getPasswordIndex();
        final String groupIndex = jdbcConfig.getGroupIndex();
        final String saltIndex = jdbcConfig.getSaltIndex();
        final String encodingAlgorithm = jdbcConfig.getEncodingAlgorithm();
        final String hashEncoding = jdbcConfig.getHashEncoding();
        final String saltEncoding = jdbcConfig.getSaltEncoding();

        final ModelNode addRealmOp = createEmptyOperation("add", PathAddress.pathAddress("subsystem", "elytron")
                .append("jdbc-realm", domainName + REALM_SUFFIX));
        ModelNode queryNode = new ModelNode();
        queryNode.get("sql").set(sqlQuery);
        queryNode.get("data-source").set(datasourceName);

        if (mapperType == JdbcConfig.MapperType.Plain) {
            final ModelNode mapperNode = new ModelNode();
            mapperNode.get("password-index").set(passwordIndex);

            queryNode.get("clear-password-mapper").set(mapperNode);
        }
        if (mapperType == JdbcConfig.MapperType.Digest) {
            final ModelNode mapperNode = new ModelNode();
            mapperNode.get("password-index").set(passwordIndex);
            mapperNode.get("algorithm").set(encodingAlgorithm);
            mapperNode.get("hash-encoding").set(hashEncoding);

            queryNode.get("simple-digest-mapper").set(mapperNode);
        }
        if (mapperType == JdbcConfig.MapperType.SaltedDigest) {
            final ModelNode mapperNode = new ModelNode();
            mapperNode.get("password-index").set(passwordIndex);
            mapperNode.get("salt-index").set(saltIndex);
            mapperNode.get("algorithm").set(encodingAlgorithm);
            mapperNode.get("hash-encoding").set(hashEncoding);
            mapperNode.get("salt-encoding").set(saltEncoding);

            queryNode.get("salted-simple-digest-mapper").set(mapperNode);
        }
        ModelNode attrMappingNode = new ModelNode();
        attrMappingNode.get("index").set(groupIndex);
        attrMappingNode.get("to").set("groups");
        queryNode.get("attribute-mapping").add(attrMappingNode);

        addRealmOp.get("principal-query").add(queryNode);

        ops.add(addRealmOp);

        ModelNode addSecurityDomainOp = new SecurityDomain().createSecurityDomain(new SecurityDomain.Model(domainName, domainName + REALM_SUFFIX, true));
        ops.add(addSecurityDomainOp);

        return ops;
    }

    public static class JdbcConfig {
        // serialization keys
        private static final String DATASOURCE_NAME = "datasourceName";
        private static final String SQL_QUERY = "sqlQuery";
        private static final String MAPPER_TYPE = "mapperType";
        private static final String PASSWORD_INDEX = "passwordIndex";
        private static final String GROUP_INDEX = "groupIndex";
        private static final String SALT_INDEX = "saltIndex";
        private static final String ENCODING_ALGORITHM = "encodingAlgorithm";
        private static final String HASH_ENCODING = "hashEncoding";
        private static final String SALT_ENCODING = "saltEncoding";
        // end of serialization keys

        private String datasourceName;
        private String sqlQuery;
        private MapperType mapperType;
        private String passwordIndex;
        private String groupIndex;
        private String saltIndex;
        private String encodingAlgorithm;
        private String hashEncoding;
        private String saltEncoding;

        public JdbcConfig() {

        }

        public JdbcConfig(Map<String, String> attributes) {
            this.datasourceName = attributes.get(DATASOURCE_NAME);
            this.sqlQuery = attributes.get(SQL_QUERY);
            this.mapperType = MapperType.valueOf(attributes.get(MAPPER_TYPE));
            this.passwordIndex = attributes.get(PASSWORD_INDEX);
            this.groupIndex = attributes.get(GROUP_INDEX);
            this.saltIndex = attributes.get(SALT_INDEX);
            this.encodingAlgorithm = attributes.get(ENCODING_ALGORITHM);
            this.hashEncoding = attributes.get(HASH_ENCODING);
            this.saltEncoding = attributes.get(SALT_ENCODING);
        }

        public enum MapperType {
            Plain("plain"), Digest("simple-digest"), SaltedDigest("salted-simple-digest");

            private final String text;

            MapperType(String text) {
                this.text = text;
            }

            public String getText() {
                return text;
            }

            public static MapperType from(String text) {
                for (MapperType type : MapperType.values()) {
                    if (text.equals(type.getText())) {
                        return type;
                    }
                }

                throw new IllegalArgumentException("Unsupported mapper type");
            }
        }

        public String getDatasourceName() {
            return datasourceName;
        }

        public void setDatasourceName(String datasourceName) {
            this.datasourceName = datasourceName;
        }

        public String getSqlQuery() {
            return sqlQuery;
        }

        public void setSqlQuery(String sqlQuery) {
            this.sqlQuery = sqlQuery;
        }

        public MapperType getMapperType() {
            return mapperType;
        }

        public void setMapperType(MapperType mapperType) {
            this.mapperType = mapperType;
        }

        public String getPasswordIndex() {
            return passwordIndex;
        }

        public void setPasswordIndex(String passwordIndex) {
            this.passwordIndex = passwordIndex;
        }

        public String getGroupIndex() {
            return groupIndex;
        }

        public void setGroupIndex(String groupIndex) {
            this.groupIndex = groupIndex;
        }

        public String getSaltIndex() {
            return saltIndex;
        }

        public void setSaltIndex(String saltIndex) {
            this.saltIndex = saltIndex;
        }

        public String getEncodingAlgorithm() {
            return encodingAlgorithm;
        }

        public void setEncodingAlgorithm(String encodingAlgorithm) {
            this.encodingAlgorithm = encodingAlgorithm;
        }

        public String getHashEncoding() {
            return hashEncoding;
        }

        public void setHashEncoding(String hashEncoding) {
            this.hashEncoding = hashEncoding;
        }

        public String getSaltEncoding() {
            return saltEncoding;
        }

        public void setSaltEncoding(String saltEncoding) {
            this.saltEncoding = saltEncoding;
        }

        public Map<String, String> toAttributes() {
            final HashMap<String, String> attrs = new HashMap<>();
            attrs.put(DATASOURCE_NAME, datasourceName);
            attrs.put(SQL_QUERY, sqlQuery);
            attrs.put(MAPPER_TYPE, mapperType.toString());
            attrs.put(PASSWORD_INDEX, passwordIndex);
            attrs.put(GROUP_INDEX, groupIndex);
            attrs.put(SALT_INDEX, saltIndex);
            attrs.put(ENCODING_ALGORITHM, encodingAlgorithm);
            attrs.put(HASH_ENCODING, hashEncoding);
            attrs.put(SALT_ENCODING, saltEncoding);
            return attrs;
        }

        @Override
        public String toString() {
            return "JdbcConfig{" +
                    "datasourceName='" + datasourceName + '\'' +
                    ", sqlQuery='" + sqlQuery + '\'' +
                    ", mapperType='" + mapperType + '\'' +
                    ", passwordIndex='" + passwordIndex + '\'' +
                    ", groupIndex='" + groupIndex + '\'' +
                    ", saltIndex='" + saltIndex + '\'' +
                    ", encodingAlgorithm='" + encodingAlgorithm + '\'' +
                    ", hashEncoding='" + hashEncoding + '\'' +
                    ", saltEncoding='" + saltEncoding + '\'' +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            JdbcConfig that = (JdbcConfig) o;
            return Objects.equals(datasourceName, that.datasourceName) && Objects.equals(sqlQuery, that.sqlQuery) && mapperType == that.mapperType && Objects.equals(passwordIndex, that.passwordIndex) && Objects.equals(groupIndex, that.groupIndex) && Objects.equals(saltIndex, that.saltIndex) && Objects.equals(encodingAlgorithm, that.encodingAlgorithm) && Objects.equals(hashEncoding, that.hashEncoding) && Objects.equals(saltEncoding, that.saltEncoding);
        }

        @Override
        public int hashCode() {
            return Objects.hash(datasourceName, sqlQuery, mapperType, passwordIndex, groupIndex, saltIndex, encodingAlgorithm, hashEncoding, saltEncoding);
        }
    }
}
