/*-
 * ~~~~~~licensing~~~~~~
 * jaas-management
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.management.jaas.impl;

import java.security.Principal;
import java.util.*;
import java.util.stream.Collectors;

import javax.management.NotCompliantMBeanException;
import javax.security.auth.login.AppConfigurationEntry;

import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.karaf.jaas.boot.principal.GroupPrincipal;
import org.apache.karaf.jaas.boot.principal.RolePrincipal;
import org.apache.karaf.jaas.modules.BackingEngine;

import ru.entaxy.esb.platform.base.management.core.api.EntaxyRuntimeTyped;
import ru.entaxy.platform.core.management.jaas.JaasLoginModuleWithBackingEngineMBean;
import ru.entaxy.platform.core.management.jaas.JaasMBean;
import ru.entaxy.platform.core.management.object.ObjectMBean;
import ru.entaxy.platform.core.management.object.factory.AbstractObjectMBean;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeRelation;
import ru.entaxy.platform.runtime.security.jaas.JaasHelper;

@EntaxyRuntimeTyped(name = JaasMBean.TYPE_LOGINMODULE)
@ObjectMBean(subQualifierName = "module")
public class JaasLoginModuleWithBackingEngineMBeanImpl
        extends AbstractObjectMBean<JaasLoginModuleWithBackingEngineMBean>
        implements JaasLoginModuleWithBackingEngineMBean {

    protected AppConfigurationEntry entry = null;
    protected BackingEngine engine = null;

    private final String SYSTEM_ROLE_PREFIX = "PROFILE_";

    protected JaasLoginModuleWithBackingEngineMBeanImpl(EntaxyRuntimeObject entaxyRuntimeObject)
            throws NotCompliantMBeanException {
        super(JaasLoginModuleWithBackingEngineMBean.class, entaxyRuntimeObject);

    }

    protected boolean init() {
        if (engine == null) {
            List<EntaxyRuntimeRelation> relations = entaxyRuntimeObject.getIncomiingSubordRelations();
            if (relations == null)
                return false;
            relations = relations.stream().filter(rel -> !rel.getMain().isGhost())
                    .filter(rel -> JaasMBean.TYPE_REALM.equals(rel.getMain().getType())).collect(Collectors.toList());
            if (relations.isEmpty())
                return false;
            String realmName = relations.get(0).getMain().getId();
            try {
                engine = JaasHelper.getInstance().getBackingEngine(realmName, entaxyRuntimeObject.getId());
            } catch (Exception ignore) {

            }
        }
        return engine != null;
    }

    @Override
    public boolean isBackingEngineDriven() {
        return true;
    }

    @Override
    public void addUser(String username, String password) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.addUser(username, password);
    }

    @Override
    public void deleteUser(String username) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.deleteUser(username);
    }

    @Override
    public String listUsers() {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        return engine.listUsers().stream().map(user -> {
            return getJsonObjectFromPrincipal(user);
        }).collect(Collectors.toList()).toString();
    }

    @Override
    public String listGroupsForUser(String username) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        return engine.listGroups(engine.lookupUser(username)).stream().map(groupPrincipal -> {
            return getJsonObjectFromPrincipal(groupPrincipal);
        }).collect(Collectors.toList()).toString();
    }

    @Override
    public String listGroups() {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        List<JsonObject> groups = new ArrayList<>();
        engine.listGroups().forEach((groupPrincipal, roles) -> {
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("name", groupPrincipal.getName());
            jsonObject.addProperty("roles", roles);
            groups.add(jsonObject);
        });
        return groups.toString();
    }

    @Override
    public void addGroup(String username, String group) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.addGroup(username, group);
    }

    @Override
    public void createGroup(String group) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.createGroup(group);
    }

    @Override
    public void deleteGroup(String username, String group) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.deleteGroup(username, group);
    }

    @Override
    public String getAssociatedProfile(String username) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        RolePrincipal role = engine.listRoles(engine.lookupUser(username)).stream().filter(rolePrincipal -> {
            return rolePrincipal.getName().startsWith(SYSTEM_ROLE_PREFIX);
        }).findAny().orElse(null);

        return role != null ? role.getName().substring(SYSTEM_ROLE_PREFIX.length(), role.getName().length()) : "";
    }

    @Override
    public void associateProfile(String username, String profileName) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        RolePrincipal role = engine.listRoles(engine.lookupUser(username)).stream().filter(rolePrincipal -> {
            return rolePrincipal.getName().startsWith(SYSTEM_ROLE_PREFIX);
        }).findAny().orElse(null);

        if (role != null) {
            engine.deleteRole(username, role.getName());
        }

        if (StringUtils.isNotEmpty(profileName)) {
            engine.addRole(username, SYSTEM_ROLE_PREFIX.concat(profileName));
        }
    }

    @Override
    public String listRolesForUser(String username) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        return engine.listRoles(engine.lookupUser(username)).stream().filter(role -> {
            return !role.getName().startsWith(SYSTEM_ROLE_PREFIX);
        }).map(role -> {
            return getJsonObjectFromPrincipal(role);
        }).collect(Collectors.toList()).toString();
    }

    @Override
    public String listRolesForGroup(String groupName) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");

        return engine.listRoles(new GroupPrincipal(groupName)).stream().map(role -> {
            return getJsonObjectFromPrincipal(role);
        }).collect(Collectors.toList()).toString();
    }

    @Override
    public void addRole(String username, String role) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.addRole(username, role);
    }

    @Override
    public void deleteRole(String username, String role) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.deleteRole(username, role);
    }

    @Override
    public void addGroupRole(String group, String role) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.addGroupRole(group, role);
    }

    @Override
    public void deleteGroupRole(String group, String role) {
        if (!init())
            throw new UnsupportedOperationException("Backing engine not bound");
        engine.deleteGroupRole(group, role);
    }

    private JsonObject getJsonObjectFromPrincipal(Principal principal) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("name", principal.getName());
        return jsonObject;
    }

}
