package org.infinispan.configuration.global;

import static org.infinispan.commons.configuration.attributes.IdentityAttributeCopier.identityCopier;

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

import org.infinispan.commons.configuration.BuiltBy;
import org.infinispan.commons.configuration.attributes.Attribute;
import org.infinispan.commons.configuration.attributes.AttributeDefinition;
import org.infinispan.commons.configuration.attributes.AttributeInitializer;
import org.infinispan.commons.configuration.attributes.AttributeSerializer;
import org.infinispan.commons.configuration.attributes.AttributeSet;
import org.infinispan.security.AuditLogger;
import org.infinispan.security.AuthorizationPermission;
import org.infinispan.security.PrincipalRoleMapper;
import org.infinispan.security.Role;
import org.infinispan.security.RolePermissionMapper;
import org.infinispan.security.audit.NullAuditLogger;
import org.infinispan.util.logging.events.Messages;

/**
 * GlobalAuthorizationConfiguration.
 *
 * @author Tristan Tarrant
 * @since 7.0
 */
@BuiltBy(GlobalAuthorizationConfigurationBuilder.class)
public class GlobalAuthorizationConfiguration {
   public static final Map<String, Role> DEFAULT_ROLES;
   public static final AttributeDefinition<Boolean> ENABLED = AttributeDefinition.builder(org.infinispan.configuration.parsing.Attribute.ENABLED, false).immutable().build();
   public static final AttributeDefinition<AuditLogger> AUDIT_LOGGER = AttributeDefinition.builder(org.infinispan.configuration.parsing.Attribute.AUDIT_LOGGER, (AuditLogger) new NullAuditLogger())
         .copier(identityCopier()).serializer(AttributeSerializer.INSTANCE_CLASS_NAME).immutable().build();
   public static final AttributeDefinition<Map<String, Role>> ROLES = AttributeDefinition.<Map<String, Role>>builder(org.infinispan.configuration.parsing.Attribute.ROLES, new HashMap<>())
         .initializer(new AttributeInitializer<>() {
            @Override
            public Map<String, Role> initialize() {
               return new HashMap<>(DEFAULT_ROLES);
            }
         }).build();
   public static final AttributeDefinition<Boolean> GROUP_ONLY_MAPPING = AttributeDefinition.builder(org.infinispan.configuration.parsing.Attribute.GROUP_ONLY_MAPPING, true, Boolean.class).immutable().build();

   static AttributeSet attributeDefinitionSet() {
      return new AttributeSet(GlobalAuthorizationConfiguration.class, ENABLED, AUDIT_LOGGER, ROLES, GROUP_ONLY_MAPPING);
   }

   static {
      DEFAULT_ROLES = Map.of(
            "admin", Role.newRole("admin", Messages.MESSAGES.adminRoleDescription(),
                  true, true,
                  AuthorizationPermission.ALL
            ),
            "application", Role.newRole("application", Messages.MESSAGES.applicationRoleDescription(),
                  true,true,
                  AuthorizationPermission.ALL_READ,
                  AuthorizationPermission.ALL_WRITE,
                  AuthorizationPermission.LISTEN,
                  AuthorizationPermission.EXEC,
                  AuthorizationPermission.MONITOR
            ),
            "deployer", Role.newRole("deployer", Messages.MESSAGES.deployerRoleDescription(), true, true,
                  AuthorizationPermission.ALL_READ,
                  AuthorizationPermission.ALL_WRITE,
                  AuthorizationPermission.LISTEN,
                  AuthorizationPermission.EXEC,
                  AuthorizationPermission.CREATE,
                  AuthorizationPermission.MONITOR
            ),
            "observer", Role.newRole("observer", Messages.MESSAGES.observerRoleDescription(),
                  true, true,
                  AuthorizationPermission.ALL_READ,
                  AuthorizationPermission.MONITOR
            ),
            "monitor", Role.newRole("monitor", Messages.MESSAGES.monitorRoleDescription(),
                  true, true,
                  AuthorizationPermission.MONITOR
            ),
            // Deprecated roles. Will be removed in Infinispan 16.0
            "___schema_manager", Role.newRole("___schema_manager", "Deprecated role for schema management.",
                  true, false,
                  AuthorizationPermission.CREATE
            ),
            "___script_manager", Role.newRole("___script_manager", "Deprecated role for script management.",
                  true, false,
                  AuthorizationPermission.CREATE
            ));
   }

   private final Attribute<Boolean> enabled;
   private final Attribute<AuditLogger> auditLogger;
   private final Map<String, Role> roles;
   private final PrincipalRoleMapperConfiguration roleMapperConfiguration;
   private final RolePermissionMapperConfiguration permissionMapperConfiguration;
   private final RolePermissionMapper rolePermissionMapper;
   private final boolean groupOnlyMapping;

   private final AttributeSet attributes;

   public GlobalAuthorizationConfiguration(AttributeSet attributes, PrincipalRoleMapperConfiguration roleMapperConfiguration, RolePermissionMapperConfiguration permissionMapperConfiguration) {
      this.attributes = attributes.checkProtection();
      this.enabled = attributes.attribute(ENABLED);
      this.auditLogger = attributes.attribute(AUDIT_LOGGER);
      this.roles = attributes.attribute(ROLES).get();
      this.roleMapperConfiguration = roleMapperConfiguration;
      this.permissionMapperConfiguration = permissionMapperConfiguration;
      this.rolePermissionMapper = permissionMapperConfiguration.permissionMapper();
      this.groupOnlyMapping = attributes.attribute(GROUP_ONLY_MAPPING).get();
   }

   public boolean enabled() {
      return enabled.get();
   }

   public AuditLogger auditLogger() {
      return auditLogger.get();
   }

   public PrincipalRoleMapper principalRoleMapper() {
      return roleMapperConfiguration.roleMapper();
   }

   public RolePermissionMapper rolePermissionMapper() {
      return rolePermissionMapper;
   }

   public PrincipalRoleMapperConfiguration roleMapperConfiguration() {
      return roleMapperConfiguration;
   }

   public boolean isDefaultRoles() {
      return DEFAULT_ROLES.equals(roles);
   }

   public boolean groupOnlyMapping() {
      return groupOnlyMapping;
   }

   public RolePermissionMapperConfiguration permissionMapperConfiguration() {
      return permissionMapperConfiguration;
   }

   public Map<String, Role> roles() {
      Map<String, Role> all = new HashMap<>(roles);
      if (rolePermissionMapper != null) {
         all.putAll(rolePermissionMapper.getAllRoles());
      }
      return all;
   }

   public void addRole(Role role) {
      roles.put(role.getName(), role);
   }

   public boolean hasRole(String name) {
      return roles.containsKey(name) || (rolePermissionMapper != null && rolePermissionMapper.hasRole(name));
   }

   public Role getRole(String name) {
      Role role = roles.get(name);
      if (role != null) {
         return role;
      } else if (rolePermissionMapper != null) {
         return rolePermissionMapper.getRole(name);
      } else {
         return null;
      }
   }

   public AttributeSet attributes() {
      return attributes;
   }

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      GlobalAuthorizationConfiguration that = (GlobalAuthorizationConfiguration) o;
      return Objects.equals(roleMapperConfiguration, that.roleMapperConfiguration) &&
            Objects.equals(permissionMapperConfiguration, that.permissionMapperConfiguration) &&
            Objects.equals(attributes, that.attributes);
   }

   @Override
   public int hashCode() {
      return Objects.hash(roleMapperConfiguration, permissionMapperConfiguration, attributes);
   }

   @Override
   public String toString() {
      return "GlobalAuthorizationConfiguration{" +
            "roleMapperConfiguration=" + roleMapperConfiguration +
            "permissionMapperConfiguration=" + permissionMapperConfiguration +
            ", attributes=" + attributes +
            '}';
   }
}
