/*
 * 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
 *
 *   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.apache.polaris.service.admin;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.polaris.service.catalog.common.ExceptionUtils.noSuchNamespaceException;
import static org.apache.polaris.service.catalog.common.ExceptionUtils.notFoundExceptionForTableLikeEntity;

import com.google.common.base.Strings;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.AlreadyExistsException;
import org.apache.iceberg.exceptions.BadRequestException;
import org.apache.iceberg.exceptions.NotFoundException;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.admin.model.AuthenticationParameters;
import org.apache.polaris.core.admin.model.BearerAuthenticationParameters;
import org.apache.polaris.core.admin.model.Catalog;
import org.apache.polaris.core.admin.model.CatalogGrant;
import org.apache.polaris.core.admin.model.CatalogPrivilege;
import org.apache.polaris.core.admin.model.CatalogRole;
import org.apache.polaris.core.admin.model.ConnectionConfigInfo;
import org.apache.polaris.core.admin.model.CreateCatalogRequest;
import org.apache.polaris.core.admin.model.ExternalCatalog;
import org.apache.polaris.core.admin.model.GrantResource;
import org.apache.polaris.core.admin.model.NamespaceGrant;
import org.apache.polaris.core.admin.model.NamespacePrivilege;
import org.apache.polaris.core.admin.model.OAuthClientCredentialsParameters;
import org.apache.polaris.core.admin.model.PolicyGrant;
import org.apache.polaris.core.admin.model.PolicyPrivilege;
import org.apache.polaris.core.admin.model.Principal;
import org.apache.polaris.core.admin.model.PrincipalRole;
import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
import org.apache.polaris.core.admin.model.PrincipalWithCredentialsCredentials;
import org.apache.polaris.core.admin.model.ResetPrincipalRequest;
import org.apache.polaris.core.admin.model.TableGrant;
import org.apache.polaris.core.admin.model.TablePrivilege;
import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
import org.apache.polaris.core.admin.model.ViewGrant;
import org.apache.polaris.core.admin.model.ViewPrivilege;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.auth.PolarisPrincipal;
import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.core.connection.AuthenticationParametersDpo;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.CatalogRoleEntity;
import org.apache.polaris.core.entity.NamespaceEntity;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityCore;
import org.apache.polaris.core.entity.PolarisEntitySubType;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.entity.PolarisPrivilege;
import org.apache.polaris.core.entity.PrincipalEntity;
import org.apache.polaris.core.entity.PrincipalRoleEntity;
import org.apache.polaris.core.entity.table.IcebergTableLikeEntity;
import org.apache.polaris.core.entity.table.federated.FederatedEntities;
import org.apache.polaris.core.exceptions.CommitConflictException;
import org.apache.polaris.core.identity.dpo.ServiceIdentityInfoDpo;
import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
import org.apache.polaris.core.persistence.dao.entity.BaseResult;
import org.apache.polaris.core.persistence.dao.entity.CreateCatalogResult;
import org.apache.polaris.core.persistence.dao.entity.CreatePrincipalResult;
import org.apache.polaris.core.persistence.dao.entity.DropEntityResult;
import org.apache.polaris.core.persistence.dao.entity.EntityResult;
import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
import org.apache.polaris.core.persistence.resolver.ResolverPath;
import org.apache.polaris.core.persistence.resolver.ResolverStatus;
import org.apache.polaris.core.policy.PolicyEntity;
import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException;
import org.apache.polaris.core.secrets.SecretReference;
import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.StorageLocation;
import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
import org.apache.polaris.core.storage.azure.AzureStorageConfigurationInfo;
import org.apache.polaris.service.config.ReservedProperties;
import org.apache.polaris.service.types.PolicyIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Just as an Iceberg Catalog represents the logical model of Iceberg business logic to manage
 * Namespaces, Tables and Views, abstracted away from Iceberg REST objects, this class represents
 * the logical model for managing realm-level Catalogs, Principals, Roles, and Grants.
 *
 * <p>Different API implementers could expose different REST, gRPC, etc., interfaces that delegate
 * to this logical model without being tightly coupled to a single frontend protocol, and can
 * provide different implementations of PolarisEntityManager to abstract away the implementation of
 * the persistence layer.
 */
@RequestScoped
public class PolarisAdminService {
  private static final Logger LOGGER = LoggerFactory.getLogger(PolarisAdminService.class);

  private final CallContext callContext;
  private final RealmConfig realmConfig;
  private final ResolutionManifestFactory resolutionManifestFactory;
  private final PolarisPrincipal polarisPrincipal;
  private final PolarisAuthorizer authorizer;
  private final PolarisMetaStoreManager metaStoreManager;
  private final UserSecretsManager userSecretsManager;
  private final ServiceIdentityProvider serviceIdentityProvider;
  private final ReservedProperties reservedProperties;

  @Inject
  public PolarisAdminService(
      @Nonnull CallContext callContext,
      @Nonnull ResolutionManifestFactory resolutionManifestFactory,
      @Nonnull PolarisMetaStoreManager metaStoreManager,
      @Nonnull UserSecretsManager userSecretsManager,
      @Nonnull ServiceIdentityProvider serviceIdentityProvider,
      @Nonnull PolarisPrincipal principal,
      @Nonnull PolarisAuthorizer authorizer,
      @Nonnull ReservedProperties reservedProperties) {
    this.callContext = callContext;
    this.realmConfig = callContext.getRealmConfig();
    this.resolutionManifestFactory = resolutionManifestFactory;
    this.metaStoreManager = metaStoreManager;
    this.polarisPrincipal = principal;
    this.authorizer = authorizer;
    this.userSecretsManager = userSecretsManager;
    this.serviceIdentityProvider = serviceIdentityProvider;
    this.reservedProperties = reservedProperties;
  }

  private PolarisCallContext getCurrentPolarisContext() {
    return callContext.getPolarisCallContext();
  }

  private UserSecretsManager getUserSecretsManager() {
    return userSecretsManager;
  }

  private ServiceIdentityProvider getServiceIdentityProvider() {
    return serviceIdentityProvider;
  }

  private PolarisResolutionManifest newResolutionManifest(@Nullable String catalogName) {
    return resolutionManifestFactory.createResolutionManifest(polarisPrincipal, catalogName);
  }

  private static PrincipalEntity getPrincipalByName(
      PolarisResolutionManifest resolutionManifest, String principalName) {
    return Optional.ofNullable(
            resolutionManifest.getResolvedTopLevelEntity(
                principalName, PolarisEntityType.PRINCIPAL))
        .map(PolarisResolvedPathWrapper::getRawLeafEntity)
        .map(PrincipalEntity::of)
        .orElseThrow(() -> new NotFoundException("Principal %s not found", principalName));
  }

  private static PrincipalRoleEntity getPrincipalRoleByName(
      PolarisResolutionManifest resolutionManifest, String principalRoleName) {
    return Optional.ofNullable(
            resolutionManifest.getResolvedTopLevelEntity(
                principalRoleName, PolarisEntityType.PRINCIPAL_ROLE))
        .map(PolarisResolvedPathWrapper::getRawLeafEntity)
        .map(PrincipalRoleEntity::of)
        .orElseThrow(() -> new NotFoundException("PrincipalRole %s not found", principalRoleName));
  }

  private static CatalogEntity getCatalogByName(
      PolarisResolutionManifest resolutionManifest, String catalogName) {
    return Optional.ofNullable(resolutionManifest.getResolvedCatalogEntity())
        .filter(c -> c.getName().equals(catalogName))
        .orElseThrow(() -> new NotFoundException("Catalog %s not found", catalogName));
  }

  private static CatalogRoleEntity getCatalogRoleByName(
      PolarisResolutionManifest resolutionManifest, String catalogRoleName) {
    return Optional.ofNullable(
            resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)))
        .map(PolarisResolvedPathWrapper::getRawLeafEntity)
        .map(CatalogRoleEntity::of)
        .orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName));
  }

  private void authorizeBasicRootOperationOrThrow(PolarisAuthorizableOperation op) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(null);
    resolutionManifest.resolveAll();
    PolarisResolvedPathWrapper rootContainerWrapper =
        resolutionManifest.getResolvedRootContainerEntityAsPath();
    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedPrincipalRoleEntities(),
        op,
        rootContainerWrapper,
        null /* secondary */);
  }

  private PolarisResolutionManifest authorizeBasicTopLevelEntityOperationOrThrow(
      PolarisAuthorizableOperation op, String topLevelEntityName, PolarisEntityType entityType) {
    String referenceCatalogName =
        entityType == PolarisEntityType.CATALOG ? topLevelEntityName : null;
    return authorizeBasicTopLevelEntityOperationOrThrow(
        op, topLevelEntityName, entityType, referenceCatalogName);
  }

  private PolarisResolutionManifest authorizeBasicTopLevelEntityOperationOrThrow(
      PolarisAuthorizableOperation op,
      String topLevelEntityName,
      PolarisEntityType entityType,
      @Nullable String referenceCatalogName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(referenceCatalogName);
    resolutionManifest.addTopLevelName(topLevelEntityName, entityType, false /* isOptional */);
    ResolverStatus status = resolutionManifest.resolveAll();
    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException(
          "TopLevelEntity of type %s does not exist: %s", entityType, topLevelEntityName);
    }
    PolarisResolvedPathWrapper topLevelEntityWrapper =
        resolutionManifest.getResolvedTopLevelEntity(topLevelEntityName, entityType);

    PolarisEntity entity = topLevelEntityWrapper.getResolvedLeafEntity().getEntity();
    if (isSelfEntity(entity) && isSelfOperation(op)) {
      LOGGER
          .atDebug()
          .addKeyValue("principalName", topLevelEntityName)
          .log("Allowing rotate own credentials");
    } else {
      authorizer.authorizeOrThrow(
          polarisPrincipal,
          resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
          op,
          topLevelEntityWrapper,
          null /* secondary */);
    }
    return resolutionManifest;
  }

  /**
   * Returns true if the target entity is the same as the current authenticated {@link
   * PolarisPrincipal}.
   */
  private boolean isSelfEntity(PolarisEntity entity) {
    // Entity name is unique for (realm_id, catalog_id, parent_id, type_code),
    // which is reduced to (realm_id, type_code) for top-level entities;
    // so there can be only one principal with a given name inside any realm.
    return entity.getType() == PolarisEntityType.PRINCIPAL
        && entity.getName().equals(polarisPrincipal.getName());
  }

  /**
   * Returns true if the operation is a "self" operation, that is, an operation that is being
   * performed by the principal on itself.
   *
   * <p>TODO: If we do add more "self" privilege operations for PRINCIPAL targets this should be
   * extracted into an EnumSet and/or pushed down into PolarisAuthorizer.
   */
  private static boolean isSelfOperation(PolarisAuthorizableOperation op) {
    return op.equals(PolarisAuthorizableOperation.ROTATE_CREDENTIALS);
  }

  private PolarisResolutionManifest authorizeBasicCatalogRoleOperationOrThrow(
      PolarisAuthorizableOperation op, String catalogName, String catalogRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
    resolutionManifest.resolveAll();
    PolarisResolvedPathWrapper target =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
    if (target == null) {
      throw new NotFoundException("CatalogRole does not exist: %s", catalogRoleName);
    }
    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        target,
        null /* secondary */);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnRootContainerToPrincipalRoleOperationOrThrow(
      PolarisAuthorizableOperation op, String principalRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(null);
    resolutionManifest.addTopLevelName(
        principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
    ResolverStatus status = resolutionManifest.resolveAll();

    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException(
          "Entity %s not found when trying to grant on root to %s",
          status.getFailedToResolvedEntityName(), principalRoleName);
    }

    PolarisResolvedPathWrapper rootContainerWrapper =
        resolutionManifest.getResolvedRootContainerEntityAsPath();
    PolarisResolvedPathWrapper principalRoleWrapper =
        resolutionManifest.getResolvedTopLevelEntity(
            principalRoleName, PolarisEntityType.PRINCIPAL_ROLE);

    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        rootContainerWrapper,
        principalRoleWrapper);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnPrincipalRoleToPrincipalOperationOrThrow(
      PolarisAuthorizableOperation op, String principalRoleName, String principalName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(null);
    resolutionManifest.addTopLevelName(
        principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
    resolutionManifest.addTopLevelName(
        principalName, PolarisEntityType.PRINCIPAL, false /* isOptional */);
    ResolverStatus status = resolutionManifest.resolveAll();

    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException(
          "Entity %s not found when trying to assign %s to %s",
          status.getFailedToResolvedEntityName(), principalRoleName, principalName);
    }

    PolarisResolvedPathWrapper principalRoleWrapper =
        resolutionManifest.getResolvedTopLevelEntity(
            principalRoleName, PolarisEntityType.PRINCIPAL_ROLE);
    PolarisResolvedPathWrapper principalWrapper =
        resolutionManifest.getResolvedTopLevelEntity(principalName, PolarisEntityType.PRINCIPAL);

    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        principalRoleWrapper,
        principalWrapper);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnCatalogRoleToPrincipalRoleOperationOrThrow(
      PolarisAuthorizableOperation op,
      String catalogName,
      String catalogRoleName,
      String principalRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
    resolutionManifest.addTopLevelName(
        principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
    ResolverStatus status = resolutionManifest.resolveAll();

    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException(
          "Entity %s not found when trying to assign %s.%s to %s",
          status.getFailedToResolvedEntityName(), catalogName, catalogRoleName, principalRoleName);
    } else if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
      throw new NotFoundException(
          "Entity %s not found when trying to assign %s.%s to %s",
          status.getFailedToResolvePath(), catalogName, catalogRoleName, principalRoleName);
    }

    PolarisResolvedPathWrapper principalRoleWrapper =
        resolutionManifest.getResolvedTopLevelEntity(
            principalRoleName, PolarisEntityType.PRINCIPAL_ROLE);
    PolarisResolvedPathWrapper catalogRoleWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);

    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        catalogRoleWrapper,
        principalRoleWrapper);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnCatalogOperationOrThrow(
      PolarisAuthorizableOperation op, String catalogName, String catalogRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
    resolutionManifest.addTopLevelName(
        catalogName, PolarisEntityType.CATALOG, false /* isOptional */);
    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
    ResolverStatus status = resolutionManifest.resolveAll();

    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException("Catalog not found: %s", catalogName);
    } else if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
      throw new NotFoundException("CatalogRole not found: %s.%s", catalogName, catalogRoleName);
    }

    PolarisResolvedPathWrapper catalogWrapper =
        resolutionManifest.getResolvedTopLevelEntity(catalogName, PolarisEntityType.CATALOG);
    PolarisResolvedPathWrapper catalogRoleWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        catalogWrapper,
        catalogRoleWrapper);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnNamespaceOperationOrThrow(
      PolarisAuthorizableOperation op,
      String catalogName,
      Namespace namespace,
      String catalogRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
    resolutionManifest.addPassthroughPath(
        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
    ResolverStatus status = resolutionManifest.resolveAll();

    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException("Catalog not found: %s", catalogName);
    } else if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
      if (status.getFailedToResolvePath().lastEntityType() == PolarisEntityType.NAMESPACE) {
        throw noSuchNamespaceException(
            Namespace.of(status.getFailedToResolvePath().entityNames().toArray(new String[0])));
      } else {
        throw new NotFoundException("CatalogRole not found: %s.%s", catalogName, catalogRoleName);
      }
    }

    PolarisResolvedPathWrapper namespaceWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace), true);
    PolarisResolvedPathWrapper catalogRoleWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);

    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        namespaceWrapper,
        catalogRoleWrapper);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnTableLikeOperationOrThrow(
      PolarisAuthorizableOperation op,
      String catalogName,
      List<PolarisEntitySubType> subTypes,
      TableIdentifier identifier,
      String catalogRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
    resolutionManifest.addPassthroughPath(
        new ResolverPath(
            Arrays.asList(identifier.namespace().levels()), PolarisEntityType.NAMESPACE));
    resolutionManifest.addPassthroughPath(
        new ResolverPath(
            PolarisCatalogHelpers.tableIdentifierToList(identifier), PolarisEntityType.TABLE_LIKE));
    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
    ResolverStatus status = resolutionManifest.resolveAll();

    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException("Catalog not found: %s", catalogName);
    } else if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
      if (status.getFailedToResolvePath().lastEntityType() == PolarisEntityType.TABLE_LIKE) {
        throw notFoundExceptionForTableLikeEntity(identifier, subTypes);
      } else {
        throw new NotFoundException("CatalogRole not found: %s.%s", catalogName, catalogRoleName);
      }
    }

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    PolarisResolvedPathWrapper tableLikeWrapper =
        resolutionManifest.getResolvedPath(
            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE, true);
    boolean rbacForFederatedCatalogsEnabled =
        realmConfig.getConfig(
            FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS, catalogEntity);
    if (!(resolutionManifest.getIsPassthroughFacade() && rbacForFederatedCatalogsEnabled)
        && !subTypes.contains(tableLikeWrapper.getRawLeafEntity().getSubType())) {
      throw notFoundExceptionForTableLikeEntity(identifier, subTypes);
    }

    PolarisResolvedPathWrapper catalogRoleWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);

    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        tableLikeWrapper,
        catalogRoleWrapper);
    return resolutionManifest;
  }

  private PolarisResolutionManifest authorizeGrantOnPolicyOperationOrThrow(
      PolarisAuthorizableOperation op,
      String catalogName,
      PolicyIdentifier identifier,
      String catalogRoleName) {
    PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
    resolutionManifest.addPath(
        new ResolverPath(
            PolarisCatalogHelpers.identifierToList(identifier.namespace(), identifier.name()),
            PolarisEntityType.POLICY));
    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
    ResolverStatus status = resolutionManifest.resolveAll();
    if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
      throw new NotFoundException("Catalog not found: %s", catalogName);
    } else if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
      if (status.getFailedToResolvePath().lastEntityType() == PolarisEntityType.POLICY) {
        throw new NoSuchPolicyException(String.format("Policy does not exist: %s", identifier));
      } else {
        throw new NotFoundException("CatalogRole not found: %s.%s", catalogName, catalogRoleName);
      }
    }

    PolarisResolvedPathWrapper policyWrapper =
        resolutionManifest.getResolvedPath(
            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()), true);
    PolarisResolvedPathWrapper catalogRoleWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);

    authorizer.authorizeOrThrow(
        polarisPrincipal,
        resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
        op,
        policyWrapper,
        catalogRoleWrapper);
    return resolutionManifest;
  }

  /** Get all locations where data for a `CatalogEntity` may be stored */
  private Set<String> getCatalogLocations(CatalogEntity catalogEntity) {
    HashSet<String> catalogLocations = new HashSet<>();
    catalogLocations.add(terminateWithSlash(catalogEntity.getBaseLocation()));
    if (catalogEntity.getStorageConfigurationInfo() != null) {
      catalogLocations.addAll(
          catalogEntity.getStorageConfigurationInfo().getAllowedLocations().stream()
              .map(this::terminateWithSlash)
              .toList());
    }
    return catalogLocations;
  }

  /** Ensure a path is terminated with a `/` */
  private String terminateWithSlash(String path) {
    if (path == null) {
      return null;
    } else if (path.endsWith("/")) {
      return path;
    }
    return path + "/";
  }

  /**
   * True if the `CatalogEntity` has a default base location or allowed location that overlaps with
   * that of any existing catalog. If `ALLOW_OVERLAPPING_CATALOG_URLS` is set to true, this check
   * will be skipped.
   */
  private boolean catalogOverlapsWithExistingCatalog(CatalogEntity catalogEntity) {
    boolean allowOverlappingCatalogUrls =
        realmConfig.getConfig(FeatureConfiguration.ALLOW_OVERLAPPING_CATALOG_URLS);
    if (allowOverlappingCatalogUrls) {
      return false;
    }

    Set<String> newCatalogLocations = getCatalogLocations(catalogEntity);
    return listCatalogsUnsafe()
        .anyMatch(
            existingCatalog -> {
              if (existingCatalog.getName().equals(catalogEntity.getName())) {
                return false;
              }
              return getCatalogLocations(existingCatalog).stream()
                  .map(StorageLocation::of)
                  .anyMatch(
                      existingLocation -> {
                        return newCatalogLocations.stream()
                            .anyMatch(
                                newLocationString -> {
                                  StorageLocation newLocation =
                                      StorageLocation.of(newLocationString);
                                  return newLocation.isChildOf(existingLocation)
                                      || existingLocation.isChildOf(newLocation);
                                });
                      });
            });
  }

  /**
   * Secrets embedded *or* simply referenced through the API model will require separate processing
   * for normalizing into resolved/verified/offloaded SecretReference objects which are then placed
   * appropriately into persistence objects.
   *
   * <p>If secrets are already direct URIs/URNs to an external secret store, we may need to validate
   * the URI/URN and/or transform into a polaris-internal URN format along with type-information or
   * other secrets-manager metadata in the referencePayload.
   *
   * <p>If secrets reference first-class Polaris-stored secrets, we must resolve the associated
   * polaris persistence entities defining access to those secrets and perform authorization.
   *
   * <p>If secrets are provided inline as part of the request, we must explicitly offload the
   * secrets into a Polaris service-level secrets manager and return the associated internal
   * references to the stored secret.
   */
  private Map<String, SecretReference> extractSecretReferences(
      CreateCatalogRequest catalogRequest, PolarisEntity forEntity) {
    Map<String, SecretReference> secretReferences = new HashMap<>();
    Catalog catalog = catalogRequest.getCatalog();
    UserSecretsManager secretsManager = getUserSecretsManager();
    if (catalog instanceof ExternalCatalog externalCatalog) {
      if (externalCatalog.getConnectionConfigInfo() != null) {
        ConnectionConfigInfo connectionConfig = externalCatalog.getConnectionConfigInfo();
        AuthenticationParameters authenticationParameters =
            connectionConfig.getAuthenticationParameters();

        switch (authenticationParameters.getAuthenticationType()) {
          case OAUTH:
            {
              OAuthClientCredentialsParameters oauthClientCredentialsModel =
                  (OAuthClientCredentialsParameters) authenticationParameters;
              String inlineClientSecret = oauthClientCredentialsModel.getClientSecret();
              SecretReference secretReference =
                  secretsManager.writeSecret(inlineClientSecret, forEntity);
              secretReferences.put(
                  AuthenticationParametersDpo.INLINE_CLIENT_SECRET_REFERENCE_KEY, secretReference);
              break;
            }
          case BEARER:
            {
              BearerAuthenticationParameters bearerAuthenticationParametersModel =
                  (BearerAuthenticationParameters) authenticationParameters;
              String inlineBearerToken = bearerAuthenticationParametersModel.getBearerToken();
              SecretReference secretReference =
                  secretsManager.writeSecret(inlineBearerToken, forEntity);
              secretReferences.put(
                  AuthenticationParametersDpo.INLINE_BEARER_TOKEN_REFERENCE_KEY, secretReference);
              break;
            }
          case SIGV4:
            {
              // SigV4 authentication is not based on users provided secrets but based on the
              // service identity managed by Polaris. Nothing to do here.
              break;
            }
          default:
            throw new IllegalStateException(
                "Unsupported authentication type: "
                    + authenticationParameters.getAuthenticationType());
        }
      }
    }
    return secretReferences;
  }

  /**
   * @see #extractSecretReferences
   */
  private boolean requiresSecretReferenceExtraction(
      @Nonnull ConnectionConfigInfo connectionConfigInfo) {
    return connectionConfigInfo.getAuthenticationParameters().getAuthenticationType()
        != AuthenticationParameters.AuthenticationTypeEnum.IMPLICIT;
  }

  public PolarisEntity createCatalog(CreateCatalogRequest catalogRequest) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.CREATE_CATALOG;
    authorizeBasicRootOperationOrThrow(op);

    CatalogEntity entity = CatalogEntity.fromCatalog(realmConfig, catalogRequest.getCatalog());

    checkArgument(entity.getId() == -1, "Entity to be created must have no ID assigned");

    if (catalogOverlapsWithExistingCatalog((CatalogEntity) entity)) {
      throw new ValidationException(
          "Cannot create Catalog %s. One or more of its locations overlaps with an existing catalog",
          entity.getName());
    }

    // After basic validations, now populate id and creation timestamp.
    entity =
        new CatalogEntity.Builder(entity)
            .setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
            .setCreateTimestamp(System.currentTimeMillis())
            .setProperties(reservedProperties.removeReservedProperties(entity.getPropertiesAsMap()))
            .build();

    Catalog catalog = catalogRequest.getCatalog();
    if (catalog instanceof ExternalCatalog externalCatalog) {
      ConnectionConfigInfo connectionConfigInfo = externalCatalog.getConnectionConfigInfo();

      if (connectionConfigInfo != null) {
        LOGGER
            .atDebug()
            .addKeyValue("catalogName", entity.getName())
            .log("Creating a federated catalog");
        FeatureConfiguration.enforceFeatureEnabledOrThrow(
            realmConfig, FeatureConfiguration.ENABLE_CATALOG_FEDERATION);
        Map<String, SecretReference> processedSecretReferences = Map.of();
        List<String> supportedAuthenticationTypes =
            realmConfig
                .getConfig(FeatureConfiguration.SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES)
                .stream()
                .map(s -> s.toUpperCase(Locale.ROOT))
                .toList();
        if (requiresSecretReferenceExtraction(connectionConfigInfo)) {
          // For fields that contain references to secrets, we'll separately process the secrets
          // from the original request first, and then populate those fields with the extracted
          // secret references as part of the construction of the internal persistence entity.
          checkState(
              supportedAuthenticationTypes.contains(
                  connectionConfigInfo
                      .getAuthenticationParameters()
                      .getAuthenticationType()
                      .name()),
              "Authentication type %s is not supported.",
              connectionConfigInfo.getAuthenticationParameters().getAuthenticationType());
          processedSecretReferences = extractSecretReferences(catalogRequest, entity);
        } else {
          // Support no-auth catalog federation only when the feature is enabled.
          checkState(
              supportedAuthenticationTypes.contains(
                  AuthenticationParameters.AuthenticationTypeEnum.IMPLICIT.name()),
              "Implicit authentication based catalog federation is not supported.");
        }

        // Allocate service identity if needed for the authentication type.
        // The provider will determine if a service identity is required based on the connection
        // config.
        Optional<ServiceIdentityInfoDpo> serviceIdentityInfoDpoOptional =
            serviceIdentityProvider.allocateServiceIdentity(connectionConfigInfo);

        entity =
            new CatalogEntity.Builder(entity)
                .setConnectionConfigInfoDpoWithSecrets(
                    connectionConfigInfo,
                    processedSecretReferences,
                    serviceIdentityInfoDpoOptional.orElse(null))
                .build();
      }
    }

    CreateCatalogResult catalogResult =
        metaStoreManager.createCatalog(getCurrentPolarisContext(), entity, List.of());
    if (catalogResult.alreadyExists()) {
      // TODO: Proactive garbage-collection of any inline secrets that were written to the
      // secrets manager, here and on any other unexpected exception as well.
      throw new AlreadyExistsException(
          "Cannot create Catalog %s. Catalog already exists or resolution failed",
          entity.getName());
    } else if (!catalogResult.isSuccess()) {
      throw new IllegalStateException(
          String.format(
              "Cannot create Catalog %s: %s with extraInfo %s",
              entity.getName(),
              catalogResult.getReturnStatus(),
              catalogResult.getExtraInformation()));
    }
    return PolarisEntity.of(catalogResult.getCatalog());
  }

  public void deleteCatalog(String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DELETE_CATALOG;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.CATALOG);

    CatalogEntity entity = getCatalogByName(resolutionManifest, name);
    // TODO: Handle return value in case of concurrent modification
    boolean cleanup = realmConfig.getConfig(FeatureConfiguration.CLEANUP_ON_CATALOG_DROP);
    DropEntityResult dropEntityResult =
        metaStoreManager.dropEntityIfExists(
            getCurrentPolarisContext(), null, entity, Map.of(), cleanup);

    // at least some handling of error
    if (!dropEntityResult.isSuccess()) {
      if (dropEntityResult.failedBecauseNotEmpty()) {
        throw new BadRequestException(
            "Catalog '%s' cannot be dropped, it is not empty", entity.getName());
      } else {
        throw new BadRequestException(
            "Catalog '%s' cannot be dropped, concurrent modification detected. Please try "
                + "again",
            entity.getName());
      }
    }
  }

  public @Nonnull CatalogEntity getCatalog(String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.GET_CATALOG;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.CATALOG);

    return getCatalogByName(resolutionManifest, name);
  }

  /**
   * Helper to validate business logic of what is allowed to be updated or throw a
   * BadRequestException.
   */
  private void validateUpdateCatalogDiffOrThrow(
      CatalogEntity currentEntity, CatalogEntity newEntity) {
    // TODO: Expand the set of validations if there are other fields for other cloud providers
    // that we can't successfully apply changes to.
    PolarisStorageConfigurationInfo currentStorageConfig =
        currentEntity.getStorageConfigurationInfo();
    PolarisStorageConfigurationInfo newStorageConfig = newEntity.getStorageConfigurationInfo();

    if (currentStorageConfig == null || newStorageConfig == null) {
      return;
    }

    if (!currentStorageConfig.getClass().equals(newStorageConfig.getClass())) {
      throw new BadRequestException(
          "Cannot modify storage type of storage config from %s to %s",
          currentStorageConfig, newStorageConfig);
    }

    if (currentStorageConfig instanceof AwsStorageConfigurationInfo currentAwsConfig
        && newStorageConfig instanceof AwsStorageConfigurationInfo newAwsConfig) {

      if (!Objects.equals(currentAwsConfig.getAwsAccountId(), newAwsConfig.getAwsAccountId())) {
        throw new BadRequestException(
            "Cannot modify Role ARN in storage config from %s to %s",
            currentStorageConfig, newStorageConfig);
      }

      if ((currentAwsConfig.getExternalId() != null
              && !currentAwsConfig.getExternalId().equals(newAwsConfig.getExternalId()))
          || (newAwsConfig.getExternalId() != null
              && !newAwsConfig.getExternalId().equals(currentAwsConfig.getExternalId()))) {
        throw new BadRequestException(
            "Cannot modify ExternalId in storage config from %s to %s",
            currentStorageConfig, newStorageConfig);
      }
    } else if (currentStorageConfig instanceof AzureStorageConfigurationInfo currentAzureConfig
        && newStorageConfig instanceof AzureStorageConfigurationInfo newAzureConfig) {

      if (!currentAzureConfig.getTenantId().equals(newAzureConfig.getTenantId())
          || !newAzureConfig.getTenantId().equals(currentAzureConfig.getTenantId())) {
        throw new BadRequestException(
            "Cannot modify TenantId in storage config from %s to %s",
            currentStorageConfig, newStorageConfig);
      }
    }
  }

  public @Nonnull CatalogEntity updateCatalog(String name, UpdateCatalogRequest updateRequest) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.UPDATE_CATALOG;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.CATALOG);

    CatalogEntity currentCatalogEntity = getCatalogByName(resolutionManifest, name);

    if (currentCatalogEntity.getEntityVersion() != updateRequest.getCurrentEntityVersion()) {
      throw new CommitConflictException(
          "Failed to update Catalog; currentEntityVersion '%s', expected '%s'",
          currentCatalogEntity.getEntityVersion(), updateRequest.getCurrentEntityVersion());
    }

    CatalogEntity.Builder updateBuilder = new CatalogEntity.Builder(currentCatalogEntity);
    String defaultBaseLocation = currentCatalogEntity.getBaseLocation();
    if (updateRequest.getProperties() != null) {
      Map<String, String> updateProperties =
          reservedProperties.removeReservedPropertiesFromUpdate(
              currentCatalogEntity.getPropertiesAsMap(), updateRequest.getProperties());
      updateBuilder.setProperties(updateProperties);
      String newDefaultBaseLocation =
          updateRequest.getProperties().get(CatalogEntity.DEFAULT_BASE_LOCATION_KEY);
      // Since defaultBaseLocation is a required field during construction of a catalog, and the
      // syntax of the Catalog API model splits default-base-location out from other keys in
      // additionalProperties, it's easy for client libraries to focus on adding/merging
      // additionalProperties while neglecting to "echo" the default-base-location from the
      // fetched catalog, it's most user-friendly to treat a null or empty default-base-location
      // as meaning no intended change to the default-base-location.
      if (Strings.isNullOrEmpty(newDefaultBaseLocation)) {
        // No default-base-location present at all in the properties of the update request,
        // so we must restore it explicitly in the updateBuilder.
        updateBuilder.setDefaultBaseLocation(defaultBaseLocation);
      } else {
        // New base location is already in the updated properties; we'll also potentially
        // plumb it into the logic for setting an updated StorageConfigurationInfo.
        defaultBaseLocation = newDefaultBaseLocation;
      }
    }
    if (updateRequest.getStorageConfigInfo() != null) {
      updateBuilder.setStorageConfigurationInfo(
          realmConfig, updateRequest.getStorageConfigInfo(), defaultBaseLocation);
    }
    CatalogEntity updatedEntity = updateBuilder.build();

    validateUpdateCatalogDiffOrThrow(currentCatalogEntity, updatedEntity);

    if (catalogOverlapsWithExistingCatalog(updatedEntity)) {
      throw new ValidationException(
          "Cannot update Catalog %s. One or more of its new locations overlaps with an existing catalog",
          updatedEntity.getName());
    }

    CatalogEntity returnedEntity =
        Optional.ofNullable(
                CatalogEntity.of(
                    PolarisEntity.of(
                        metaStoreManager.updateEntityPropertiesIfNotChanged(
                            getCurrentPolarisContext(), null, updatedEntity))))
            .orElseThrow(
                () ->
                    new CommitConflictException(
                        "Concurrent modification on Catalog '%s'; retry later", name));
    return returnedEntity;
  }

  /** List all catalogs after checking for permission. */
  public List<Catalog> listCatalogs() {
    authorizeBasicRootOperationOrThrow(PolarisAuthorizableOperation.LIST_CATALOGS);
    return listCatalogsUnsafe()
        .map(catalogEntity -> catalogEntity.asCatalog(getServiceIdentityProvider()))
        .toList();
  }

  /** List all catalogs without checking for permission. */
  private Stream<CatalogEntity> listCatalogsUnsafe() {
    return metaStoreManager
        .listFullEntitiesAll(
            getCurrentPolarisContext(),
            null,
            PolarisEntityType.CATALOG,
            PolarisEntitySubType.ANY_SUBTYPE)
        .stream()
        .map(CatalogEntity::of);
  }

  public PrincipalWithCredentials createPrincipal(PrincipalEntity entity) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.CREATE_PRINCIPAL;
    authorizeBasicRootOperationOrThrow(op);

    // the API should prevent this from happening
    if (FederatedEntities.isFederated(entity)) {
      throw new ValidationException("Cannot create a federated principal");
    }
    checkArgument(entity.getId() == -1, "Entity to be created must have no ID assigned");

    CreatePrincipalResult principalResult =
        metaStoreManager.createPrincipal(
            getCurrentPolarisContext(),
            new PrincipalEntity.Builder(entity)
                .setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
                .setCreateTimestamp(System.currentTimeMillis())
                .build());
    if (principalResult.alreadyExists()) {
      throw new AlreadyExistsException(
          "Cannot create Principal %s. Principal already exists or resolution failed",
          entity.getName());
    }
    return new PrincipalWithCredentials(
        principalResult.getPrincipal().asPrincipal(),
        new PrincipalWithCredentialsCredentials(
            principalResult.getPrincipalSecrets().getPrincipalClientId(),
            principalResult.getPrincipalSecrets().getMainSecret()));
  }

  public void deletePrincipal(String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DELETE_PRINCIPAL;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.PRINCIPAL);

    PrincipalEntity entity = getPrincipalByName(resolutionManifest, name);
    // TODO: Handle return value in case of concurrent modification
    DropEntityResult dropEntityResult =
        metaStoreManager.dropEntityIfExists(
            getCurrentPolarisContext(), null, entity, Map.of(), false);

    // at least some handling of error
    if (!dropEntityResult.isSuccess()) {
      if (dropEntityResult.isEntityUnDroppable()) {
        throw new BadRequestException("Root principal cannot be dropped");
      } else {
        throw new BadRequestException(
            "Root principal cannot be dropped, concurrent modification "
                + "detected. Please try again");
      }
    }
  }

  public @Nonnull PrincipalEntity getPrincipal(String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.GET_PRINCIPAL;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.PRINCIPAL);

    return getPrincipalByName(resolutionManifest, name);
  }

  public @Nonnull PrincipalEntity updatePrincipal(
      String name, UpdatePrincipalRequest updateRequest) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.UPDATE_PRINCIPAL;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.PRINCIPAL);

    PrincipalEntity currentPrincipalEntity = getPrincipalByName(resolutionManifest, name);

    if (FederatedEntities.isFederated(currentPrincipalEntity)) {
      throw new ValidationException(
          "Cannot update a federated principal: %s", currentPrincipalEntity.getName());
    }
    if (currentPrincipalEntity.getEntityVersion() != updateRequest.getCurrentEntityVersion()) {
      throw new CommitConflictException(
          "Failed to update Principal; currentEntityVersion '%s', expected '%s'",
          currentPrincipalEntity.getEntityVersion(), updateRequest.getCurrentEntityVersion());
    }

    PrincipalEntity.Builder updateBuilder = new PrincipalEntity.Builder(currentPrincipalEntity);
    if (updateRequest.getProperties() != null) {
      Map<String, String> updateProperties =
          reservedProperties.removeReservedPropertiesFromUpdate(
              currentPrincipalEntity.getPropertiesAsMap(), updateRequest.getProperties());
      updateBuilder.setProperties(updateProperties);
    }
    PrincipalEntity updatedEntity = updateBuilder.build();
    PrincipalEntity returnedEntity =
        Optional.ofNullable(
                PrincipalEntity.of(
                    PolarisEntity.of(
                        metaStoreManager.updateEntityPropertiesIfNotChanged(
                            getCurrentPolarisContext(), null, updatedEntity))))
            .orElseThrow(
                () ->
                    new CommitConflictException(
                        "Concurrent modification on Principal '%s'; retry later", name));
    return returnedEntity;
  }

  private @Nonnull PrincipalWithCredentials resetCredentialsHelper(
      PolarisResolutionManifest resolutionManifest,
      String principalName,
      String customClientId,
      String customClientSecret) {
    PrincipalEntity currentPrincipalEntity = getPrincipalByName(resolutionManifest, principalName);

    if (FederatedEntities.isFederated(currentPrincipalEntity)) {
      throw new ValidationException(
          "Cannot reset credentials for a federated principal: %s", principalName);
    }
    PolarisPrincipalSecrets currentSecrets =
        metaStoreManager
            .loadPrincipalSecrets(getCurrentPolarisContext(), currentPrincipalEntity.getClientId())
            .getPrincipalSecrets();
    // delete the existing creds if present
    if (currentSecrets != null) {
      metaStoreManager.deletePrincipalSecrets(
          getCurrentPolarisContext(),
          currentPrincipalEntity.getClientId(),
          currentPrincipalEntity.getId());
    }
    PrincipalEntity newPrincipalEntity = currentPrincipalEntity;
    // update the clientId tied to the principal entity
    if (customClientId != null) {
      PrincipalEntity.Builder updateBuilder = new PrincipalEntity.Builder(newPrincipalEntity);
      updateBuilder.setClientId(customClientId);
      PrincipalEntity updatedNewPrincipalEntity = updateBuilder.build();
      newPrincipalEntity =
          Optional.ofNullable(
                  PrincipalEntity.of(
                      PolarisEntity.of(
                          metaStoreManager.updateEntityPropertiesIfNotChanged(
                              getCurrentPolarisContext(), null, updatedNewPrincipalEntity))))
              .orElseThrow(
                  () ->
                      new CommitConflictException(
                          "Concurrent modification on Principal '%s'; retry later", principalName));
    }

    String resolvedClientId =
        (customClientId != null) ? customClientId : currentPrincipalEntity.getClientId();

    // generate new secrets
    PolarisPrincipalSecrets newSecrets =
        metaStoreManager
            .resetPrincipalSecrets(
                getCurrentPolarisContext(),
                currentPrincipalEntity.getId(),
                resolvedClientId,
                customClientSecret)
            .getPrincipalSecrets();

    if (newSecrets == null) {
      throw new IllegalStateException(
          String.format("Failed to %s secrets for principal '%s'", "reset", principalName));
    }

    return new PrincipalWithCredentials(
        newPrincipalEntity.asPrincipal(),
        new PrincipalWithCredentialsCredentials(
            newSecrets.getPrincipalClientId(), newSecrets.getMainSecret()));
  }

  private @Nonnull PrincipalWithCredentials rotateOrResetCredentialsHelper(
      PolarisResolutionManifest resolutionManifest, String principalName, boolean shouldReset) {
    PrincipalEntity currentPrincipalEntity = getPrincipalByName(resolutionManifest, principalName);

    if (FederatedEntities.isFederated(currentPrincipalEntity)) {
      throw new ValidationException(
          "Cannot rotate/reset credentials for a federated principal: %s", principalName);
    }
    PolarisPrincipalSecrets currentSecrets =
        metaStoreManager
            .loadPrincipalSecrets(getCurrentPolarisContext(), currentPrincipalEntity.getClientId())
            .getPrincipalSecrets();
    if (currentSecrets == null) {
      throw new IllegalArgumentException(
          String.format("Failed to load current secrets for principal '%s'", principalName));
    }
    PolarisPrincipalSecrets newSecrets =
        metaStoreManager
            .rotatePrincipalSecrets(
                getCurrentPolarisContext(),
                currentPrincipalEntity.getClientId(),
                currentPrincipalEntity.getId(),
                shouldReset,
                currentSecrets.getMainSecretHash())
            .getPrincipalSecrets();
    if (newSecrets == null) {
      throw new IllegalStateException(
          String.format(
              "Failed to %s secrets for principal '%s'",
              shouldReset ? "reset" : "rotate", principalName));
    }
    Optional<PrincipalEntity> updatedPrincipalEntity =
        metaStoreManager.findPrincipalById(
            getCurrentPolarisContext(), currentPrincipalEntity.getId());
    if (updatedPrincipalEntity.isEmpty()) {
      throw new IllegalStateException(
          String.format(
              "Failed to reload principal '%s' by id: %s",
              principalName, currentPrincipalEntity.getId()));
    }
    return new PrincipalWithCredentials(
        updatedPrincipalEntity.get().asPrincipal(),
        new PrincipalWithCredentialsCredentials(
            newSecrets.getPrincipalClientId(), newSecrets.getMainSecret()));
  }

  public @Nonnull PrincipalWithCredentials rotateCredentials(String principalName) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.ROTATE_CREDENTIALS;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(
            op, principalName, PolarisEntityType.PRINCIPAL);

    return rotateOrResetCredentialsHelper(resolutionManifest, principalName, false);
  }

  public @Nonnull PrincipalWithCredentials resetCredentials(
      String principalName, ResetPrincipalRequest resetPrincipalRequest) {
    FeatureConfiguration.enforceFeatureEnabledOrThrow(
        realmConfig, FeatureConfiguration.ENABLE_CREDENTIAL_RESET);
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.RESET_CREDENTIALS;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(
            op, principalName, PolarisEntityType.PRINCIPAL);

    var customClientId = resetPrincipalRequest.getClientId();
    var customClientSecret = resetPrincipalRequest.getClientSecret();
    return resetCredentialsHelper(
        resolutionManifest, principalName, customClientId, customClientSecret);
  }

  public List<Principal> listPrincipals() {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_PRINCIPALS;
    authorizeBasicRootOperationOrThrow(op);

    return metaStoreManager
        .listFullEntitiesAll(
            getCurrentPolarisContext(),
            null,
            PolarisEntityType.PRINCIPAL,
            PolarisEntitySubType.NULL_SUBTYPE)
        .stream()
        .map(PrincipalEntity::of)
        .map(PrincipalEntity::asPrincipal)
        .toList();
  }

  public PolarisEntity createPrincipalRole(PolarisEntity entity) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.CREATE_PRINCIPAL_ROLE;
    authorizeBasicRootOperationOrThrow(op);

    checkArgument(entity.getId() == -1, "Entity to be created must have no ID assigned");

    PolarisEntity returnedEntity =
        PolarisEntity.of(
            metaStoreManager.createEntityIfNotExists(
                getCurrentPolarisContext(),
                null,
                new PolarisEntity.Builder(entity)
                    .setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
                    .setCreateTimestamp(System.currentTimeMillis())
                    .build()));
    if (returnedEntity == null) {
      throw new AlreadyExistsException(
          "Cannot create PrincipalRole %s. PrincipalRole already exists or resolution failed",
          entity.getName());
    }
    return returnedEntity;
  }

  public void deletePrincipalRole(String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DELETE_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.PRINCIPAL_ROLE);

    PrincipalRoleEntity entity = getPrincipalRoleByName(resolutionManifest, name);
    // TODO: Handle return value in case of concurrent modification
    DropEntityResult dropEntityResult =
        metaStoreManager.dropEntityIfExists(
            getCurrentPolarisContext(), null, entity, Map.of(), true); // cleanup grants

    // at least some handling of error
    if (!dropEntityResult.isSuccess()) {
      if (dropEntityResult.isEntityUnDroppable()) {
        throw new BadRequestException("Polaris service admin principal role cannot be dropped");
      } else {
        throw new BadRequestException(
            "Polaris service admin principal role cannot be dropped, "
                + "concurrent modification detected. Please try again");
      }
    }
  }

  public @Nonnull PrincipalRoleEntity getPrincipalRole(String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.GET_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.PRINCIPAL_ROLE);

    return getPrincipalRoleByName(resolutionManifest, name);
  }

  public @Nonnull PrincipalRoleEntity updatePrincipalRole(
      String name, UpdatePrincipalRoleRequest updateRequest) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.UPDATE_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, name, PolarisEntityType.PRINCIPAL_ROLE);

    PrincipalRoleEntity currentPrincipalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, name);

    if (currentPrincipalRoleEntity.getEntityVersion() != updateRequest.getCurrentEntityVersion()) {
      throw new CommitConflictException(
          "Failed to update PrincipalRole; currentEntityVersion '%s', expected '%s'",
          currentPrincipalRoleEntity.getEntityVersion(), updateRequest.getCurrentEntityVersion());
    }

    PrincipalRoleEntity.Builder updateBuilder =
        new PrincipalRoleEntity.Builder(currentPrincipalRoleEntity);
    if (updateRequest.getProperties() != null) {
      Map<String, String> updateProperties =
          reservedProperties.removeReservedPropertiesFromUpdate(
              currentPrincipalRoleEntity.getPropertiesAsMap(), updateRequest.getProperties());
      updateBuilder.setProperties(updateProperties);
    }
    PrincipalRoleEntity updatedEntity = updateBuilder.build();
    PrincipalRoleEntity returnedEntity =
        Optional.ofNullable(
                PrincipalRoleEntity.of(
                    PolarisEntity.of(
                        metaStoreManager.updateEntityPropertiesIfNotChanged(
                            getCurrentPolarisContext(), null, updatedEntity))))
            .orElseThrow(
                () ->
                    new CommitConflictException(
                        "Concurrent modification on PrincipalRole '%s'; retry later", name));
    return returnedEntity;
  }

  public List<PrincipalRole> listPrincipalRoles() {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_PRINCIPAL_ROLES;
    authorizeBasicRootOperationOrThrow(op);

    return metaStoreManager
        .listFullEntitiesAll(
            getCurrentPolarisContext(),
            null,
            PolarisEntityType.PRINCIPAL_ROLE,
            PolarisEntitySubType.NULL_SUBTYPE)
        .stream()
        .map(PrincipalRoleEntity::of)
        .map(PrincipalRoleEntity::asPrincipalRole)
        .toList();
  }

  public PolarisEntity createCatalogRole(String catalogName, PolarisEntity entity) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.CREATE_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, catalogName, PolarisEntityType.CATALOG);

    checkArgument(entity.getId() == -1, "Entity to be created must have no ID assigned");

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);

    PolarisEntity returnedEntity =
        PolarisEntity.of(
            metaStoreManager.createEntityIfNotExists(
                getCurrentPolarisContext(),
                PolarisEntity.toCoreList(List.of(catalogEntity)),
                new PolarisEntity.Builder(entity)
                    .setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
                    .setCatalogId(catalogEntity.getId())
                    .setParentId(catalogEntity.getId())
                    .setCreateTimestamp(System.currentTimeMillis())
                    .build()));
    if (returnedEntity == null) {
      throw new AlreadyExistsException(
          "Cannot create CatalogRole %s in %s. CatalogRole already exists or resolution failed",
          entity.getName(), catalogName);
    }
    return returnedEntity;
  }

  public void deleteCatalogRole(String catalogName, String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DELETE_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicCatalogRoleOperationOrThrow(op, catalogName, name);

    PolarisResolvedPathWrapper resolvedCatalogRoleEntity =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(name));
    if (resolvedCatalogRoleEntity == null) {
      throw new NotFoundException("CatalogRole %s not found in catalog %s", name, catalogName);
    }
    // TODO: Handle return value in case of concurrent modification
    DropEntityResult dropEntityResult =
        metaStoreManager.dropEntityIfExists(
            getCurrentPolarisContext(),
            PolarisEntity.toCoreList(resolvedCatalogRoleEntity.getRawParentPath()),
            resolvedCatalogRoleEntity.getRawLeafEntity(),
            Map.of(),
            true); // cleanup grants

    // at least some handling of error
    if (!dropEntityResult.isSuccess()) {
      if (dropEntityResult.isEntityUnDroppable()) {
        throw new BadRequestException("Catalog admin role cannot be dropped");
      } else {
        throw new BadRequestException(
            "Catalog admin role cannot be dropped, concurrent "
                + "modification detected. Please try again");
      }
    }
  }

  public @Nonnull CatalogRoleEntity getCatalogRole(String catalogName, String name) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.GET_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicCatalogRoleOperationOrThrow(op, catalogName, name);

    return getCatalogRoleByName(resolutionManifest, name);
  }

  public @Nonnull CatalogRoleEntity updateCatalogRole(
      String catalogName, String name, UpdateCatalogRoleRequest updateRequest) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.UPDATE_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicCatalogRoleOperationOrThrow(op, catalogName, name);

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity currentCatalogRoleEntity = getCatalogRoleByName(resolutionManifest, name);

    if (currentCatalogRoleEntity.getEntityVersion() != updateRequest.getCurrentEntityVersion()) {
      throw new CommitConflictException(
          "Failed to update CatalogRole; currentEntityVersion '%s', expected '%s'",
          currentCatalogRoleEntity.getEntityVersion(), updateRequest.getCurrentEntityVersion());
    }

    CatalogRoleEntity.Builder updateBuilder =
        new CatalogRoleEntity.Builder(currentCatalogRoleEntity);
    if (updateRequest.getProperties() != null) {
      Map<String, String> updateProperties =
          reservedProperties.removeReservedPropertiesFromUpdate(
              currentCatalogRoleEntity.getPropertiesAsMap(), updateRequest.getProperties());
      updateBuilder.setProperties(updateProperties);
    }
    CatalogRoleEntity updatedEntity = updateBuilder.build();
    CatalogRoleEntity returnedEntity =
        Optional.ofNullable(
                CatalogRoleEntity.of(
                    PolarisEntity.of(
                        metaStoreManager.updateEntityPropertiesIfNotChanged(
                            getCurrentPolarisContext(),
                            PolarisEntity.toCoreList(List.of(catalogEntity)),
                            updatedEntity))))
            .orElseThrow(
                () ->
                    new CommitConflictException(
                        "Concurrent modification on CatalogRole '%s'; retry later", name));
    return returnedEntity;
  }

  public List<CatalogRole> listCatalogRoles(String catalogName) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_CATALOG_ROLES;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(op, catalogName, PolarisEntityType.CATALOG);

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    List<PolarisEntityCore> catalogPath = PolarisEntity.toCoreList(List.of(catalogEntity));
    return metaStoreManager
        .listFullEntitiesAll(
            getCurrentPolarisContext(),
            catalogPath,
            PolarisEntityType.CATALOG_ROLE,
            PolarisEntitySubType.NULL_SUBTYPE)
        .stream()
        .map(CatalogRoleEntity::of)
        .map(CatalogRoleEntity::asCatalogRole)
        .toList();
  }

  public PrivilegeResult assignPrincipalRole(String principalName, String principalRoleName) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.ASSIGN_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnPrincipalRoleToPrincipalOperationOrThrow(
            op, principalRoleName, principalName);

    PrincipalEntity principalEntity = getPrincipalByName(resolutionManifest, principalName);
    if (FederatedEntities.isFederated(principalEntity)) {
      throw new ValidationException("Cannot assign a role to a federated principal");
    }
    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);
    if (FederatedEntities.isFederated(principalRoleEntity)) {
      throw new ValidationException("Cannot assign a federated role to a principal");
    }
    return metaStoreManager.grantUsageOnRoleToGrantee(
        getCurrentPolarisContext(), null, principalRoleEntity, principalEntity);
  }

  public PrivilegeResult revokePrincipalRole(String principalName, String principalRoleName) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.REVOKE_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnPrincipalRoleToPrincipalOperationOrThrow(
            op, principalRoleName, principalName);

    PrincipalEntity principalEntity = getPrincipalByName(resolutionManifest, principalName);
    if (FederatedEntities.isFederated(principalEntity)) {
      throw new ValidationException("Cannot revoke a role from a federated principal");
    }
    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);
    if (FederatedEntities.isFederated(principalRoleEntity)) {
      throw new ValidationException("Cannot revoke a federated role from a principal");
    }
    return metaStoreManager.revokeUsageOnRoleFromGrantee(
        getCurrentPolarisContext(), null, principalRoleEntity, principalEntity);
  }

  public List<PolarisEntity> listPrincipalRolesAssigned(String principalName) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_PRINCIPAL_ROLES_ASSIGNED;

    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(
            op, principalName, PolarisEntityType.PRINCIPAL);

    PrincipalEntity principalEntity = getPrincipalByName(resolutionManifest, principalName);
    LoadGrantsResult grantList =
        metaStoreManager.loadGrantsToGrantee(getCurrentPolarisContext(), principalEntity);
    return buildEntitiesFromGrantResults(grantList, false, PolarisEntityType.PRINCIPAL_ROLE, null);
  }

  public PrivilegeResult assignCatalogRoleToPrincipalRole(
      String principalRoleName, String catalogName, String catalogRoleName) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.ASSIGN_CATALOG_ROLE_TO_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnCatalogRoleToPrincipalRoleOperationOrThrow(
            op, catalogName, catalogRoleName, principalRoleName);

    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);
    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    return metaStoreManager.grantUsageOnRoleToGrantee(
        getCurrentPolarisContext(), catalogEntity, catalogRoleEntity, principalRoleEntity);
  }

  public PrivilegeResult revokeCatalogRoleFromPrincipalRole(
      String principalRoleName, String catalogName, String catalogRoleName) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_CATALOG_ROLE_FROM_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnCatalogRoleToPrincipalRoleOperationOrThrow(
            op, catalogName, catalogRoleName, principalRoleName);

    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);
    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
    return metaStoreManager.revokeUsageOnRoleFromGrantee(
        getCurrentPolarisContext(), catalogEntity, catalogRoleEntity, principalRoleEntity);
  }

  public List<PolarisEntity> listAssigneePrincipalsForPrincipalRole(String principalRoleName) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.LIST_ASSIGNEE_PRINCIPALS_FOR_PRINCIPAL_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(
            op, principalRoleName, PolarisEntityType.PRINCIPAL_ROLE);

    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);
    LoadGrantsResult grantList =
        metaStoreManager.loadGrantsOnSecurable(getCurrentPolarisContext(), principalRoleEntity);
    return buildEntitiesFromGrantResults(grantList, true, PolarisEntityType.PRINCIPAL, null);
  }

  /**
   * Build the list of entities matching the set of grant records returned by a grant lookup
   * request.
   *
   * @param grantList result of a load grants on a securable or to a grantee
   * @param grantees if true, return the list of grantee entities, else the list of securable
   *     entities
   * @param grantFilter filter on the grant records, use null for all
   * @return list of grantees or securables matching the filter
   */
  private List<PolarisEntity> buildEntitiesFromGrantResults(
      @Nonnull LoadGrantsResult grantList,
      boolean grantees,
      PolarisEntityType entityType,
      @Nullable Function<PolarisGrantRecord, Boolean> grantFilter) {
    Map<Long, PolarisBaseEntity> granteeMap = grantList.getEntitiesAsMap();
    List<PolarisEntity> toReturn = new ArrayList<>(grantList.getGrantRecords().size());
    for (PolarisGrantRecord grantRecord : grantList.getGrantRecords()) {
      if (grantFilter == null || grantFilter.apply(grantRecord)) {
        long catalogId =
            grantees ? grantRecord.getGranteeCatalogId() : grantRecord.getSecurableCatalogId();
        long entityId = grantees ? grantRecord.getGranteeId() : grantRecord.getSecurableId();
        // get the entity associated with the grantee
        PolarisBaseEntity entity =
            this.getOrLoadEntity(granteeMap, catalogId, entityId, entityType);
        if (entity != null) {
          toReturn.add(PolarisEntity.of(entity));
        }
      }
    }
    return toReturn;
  }

  public List<PolarisEntity> listCatalogRolesForPrincipalRole(
      String principalRoleName, String catalogName) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.LIST_CATALOG_ROLES_FOR_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicTopLevelEntityOperationOrThrow(
            op, principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, catalogName);

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);
    LoadGrantsResult grantList =
        metaStoreManager.loadGrantsToGrantee(getCurrentPolarisContext(), principalRoleEntity);
    return buildEntitiesFromGrantResults(
        grantList,
        false,
        PolarisEntityType.CATALOG_ROLE,
        grantRec -> grantRec.getSecurableCatalogId() == catalogEntity.getId());
  }

  /** Adds a grant on the root container of this realm to {@code principalRoleName}. */
  public PrivilegeResult grantPrivilegeOnRootContainerToPrincipalRole(
      String principalRoleName, PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.ADD_ROOT_GRANT_TO_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnRootContainerToPrincipalRoleOperationOrThrow(op, principalRoleName);

    PolarisEntity rootContainerEntity =
        resolutionManifest.getResolvedRootContainerEntityAsPath().getRawLeafEntity();
    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);

    return metaStoreManager.grantPrivilegeOnSecurableToRole(
        getCurrentPolarisContext(), principalRoleEntity, null, rootContainerEntity, privilege);
  }

  /** Revokes a grant on the root container of this realm from {@code principalRoleName}. */
  public PrivilegeResult revokePrivilegeOnRootContainerFromPrincipalRole(
      String principalRoleName, PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_ROOT_GRANT_FROM_PRINCIPAL_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnRootContainerToPrincipalRoleOperationOrThrow(op, principalRoleName);

    PolarisEntity rootContainerEntity =
        resolutionManifest.getResolvedRootContainerEntityAsPath().getRawLeafEntity();
    PrincipalRoleEntity principalRoleEntity =
        getPrincipalRoleByName(resolutionManifest, principalRoleName);

    return metaStoreManager.revokePrivilegeOnSecurableFromRole(
        getCurrentPolarisContext(), principalRoleEntity, null, rootContainerEntity, privilege);
  }

  /**
   * Adds a catalog-level grant on {@code catalogName} to {@code catalogRoleName} which resides
   * within the same catalog on which it is being granted the privilege.
   */
  public PrivilegeResult grantPrivilegeOnCatalogToRole(
      String catalogName, String catalogRoleName, PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.ADD_CATALOG_GRANT_TO_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnCatalogOperationOrThrow(op, catalogName, catalogRoleName);

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    return metaStoreManager.grantPrivilegeOnSecurableToRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(List.of(catalogEntity)),
        catalogEntity,
        privilege);
  }

  /** Removes a catalog-level grant on {@code catalogName} from {@code catalogRoleName}. */
  public PrivilegeResult revokePrivilegeOnCatalogFromRole(
      String catalogName, String catalogRoleName, PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_CATALOG_GRANT_FROM_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnCatalogOperationOrThrow(op, catalogName, catalogRoleName);

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    return metaStoreManager.revokePrivilegeOnSecurableFromRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(List.of(catalogEntity)),
        catalogEntity,
        privilege);
  }

  /** Adds a namespace-level grant on {@code namespace} to {@code catalogRoleName}. */
  public PrivilegeResult grantPrivilegeOnNamespaceToRole(
      String catalogName, String catalogRoleName, Namespace namespace, PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.ADD_NAMESPACE_GRANT_TO_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnNamespaceOperationOrThrow(op, catalogName, namespace, catalogRoleName);

    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
    if (resolvedPathWrapper == null
        || !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
      boolean rbacForFederatedCatalogsEnabled =
          realmConfig.getConfig(
              FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS, catalogEntity);
      if (resolutionManifest.getIsPassthroughFacade() && rbacForFederatedCatalogsEnabled) {
        resolvedPathWrapper =
            createSyntheticNamespaceEntities(
                resolutionManifest, catalogEntity, namespace, resolvedPathWrapper);
        if (resolvedPathWrapper == null
            || !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
          // TODO: update the exception thrown as we refine the possible retry scenarios
          throw new RuntimeException(
              String.format(
                  "Failed to create synthetic namespace entities for namespace %s in catalog %s",
                  namespace, catalogName));
        }
      } else {
        throw new NotFoundException("Namespace %s not found", namespace);
      }
    }
    List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
    PolarisEntity namespaceEntity = resolvedPathWrapper.getRawLeafEntity();

    return metaStoreManager.grantPrivilegeOnSecurableToRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(catalogPath),
        namespaceEntity,
        privilege);
  }

  /** Removes a namespace-level grant on {@code namespace} from {@code catalogRoleName}. */
  public PrivilegeResult revokePrivilegeOnNamespaceFromRole(
      String catalogName, String catalogRoleName, Namespace namespace, PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_NAMESPACE_GRANT_FROM_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnNamespaceOperationOrThrow(op, catalogName, namespace, catalogRoleName);

    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
    if (resolvedPathWrapper == null
        || !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
      throw new NotFoundException("Namespace %s not found", namespace);
    }
    List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
    PolarisEntity namespaceEntity = resolvedPathWrapper.getRawLeafEntity();

    return metaStoreManager.revokePrivilegeOnSecurableFromRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(catalogPath),
        namespaceEntity,
        privilege);
  }

  /**
   * Creates and persists the missing synthetic namespace entities for external catalogs.
   *
   * @param catalogEntity the external passthrough facade catalog entity.
   * @param namespace the expected fully resolved namespace to be created.
   * @param existingPath the partially resolved path currently stored in the metastore.
   * @return the fully resolved path wrapper.
   */
  private PolarisResolvedPathWrapper createSyntheticNamespaceEntities(
      PolarisResolutionManifest resolutionManifest,
      CatalogEntity catalogEntity,
      Namespace namespace,
      PolarisResolvedPathWrapper existingPath) {

    if (existingPath == null) {
      throw new IllegalStateException(
          String.format("Catalog entity %s does not exist.", catalogEntity.getName()));
    }

    List<PolarisEntity> completePath = new ArrayList<>(existingPath.getRawFullPath());
    PolarisEntity currentParent = existingPath.getRawLeafEntity();

    String[] allNamespaceLevels = namespace.levels();
    int numMatchingLevels = 0;
    // Find parts of the complete path that match the namespace levels.
    // We skip index 0 because it is the CatalogEntity.
    for (PolarisEntity entity : completePath.subList(1, completePath.size())) {
      if (!entity.getName().equals(allNamespaceLevels[numMatchingLevels])) {
        break;
      }
      numMatchingLevels++;
    }

    for (int i = numMatchingLevels; i < allNamespaceLevels.length; i++) {
      String[] namespacePart = Arrays.copyOfRange(allNamespaceLevels, 0, i + 1);
      String leafNamespace = namespacePart[namespacePart.length - 1];
      Namespace currentNamespace = Namespace.of(namespacePart);

      // TODO: Instead of creating synthetic entitties, rely on external catalog mediated backfill.
      PolarisEntity syntheticNamespace =
          new NamespaceEntity.Builder(currentNamespace)
              .setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
              .setCatalogId(catalogEntity.getId())
              .setParentId(currentParent.getId())
              .setCreateTimestamp(System.currentTimeMillis())
              .build();

      EntityResult result =
          metaStoreManager.createEntityIfNotExists(
              getCurrentPolarisContext(),
              PolarisEntity.toCoreList(completePath),
              syntheticNamespace);

      if (result.isSuccess()) {
        syntheticNamespace = PolarisEntity.of(result.getEntity());
      } else if (result.getReturnStatus() == BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS) {
        PolarisResolvedPathWrapper partialPath =
            resolutionManifest.getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(namespace));
        PolarisEntity partialLeafEntity =
            partialPath != null ? partialPath.getRawLeafEntity() : null;
        if (partialLeafEntity == null
            || !(partialLeafEntity.getName().equals(leafNamespace)
                && partialLeafEntity.getType() == PolarisEntityType.NAMESPACE)) {
          throw new RuntimeException(
              String.format(
                  "Failed to create or find namespace entity '%s' in federated catalog '%s'",
                  leafNamespace, catalogEntity.getName()));
        }
        syntheticNamespace = partialLeafEntity;
      } else {
        throw new RuntimeException(
            String.format(
                "Failed to create or find namespace entity '%s' in federated catalog '%s'",
                leafNamespace, catalogEntity.getName()));
      }
      completePath.add(syntheticNamespace);
      currentParent = syntheticNamespace;
    }
    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(namespace));
    return resolvedPathWrapper;
  }

  public PrivilegeResult grantPrivilegeOnTableToRole(
      String catalogName,
      String catalogRoleName,
      TableIdentifier identifier,
      PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.ADD_TABLE_GRANT_TO_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnTableLikeOperationOrThrow(
            op,
            catalogName,
            List.of(PolarisEntitySubType.GENERIC_TABLE, PolarisEntitySubType.ICEBERG_TABLE),
            identifier,
            catalogRoleName);

    return grantPrivilegeOnTableLikeToRole(
        resolutionManifest,
        catalogName,
        catalogRoleName,
        identifier,
        List.of(PolarisEntitySubType.GENERIC_TABLE, PolarisEntitySubType.ICEBERG_TABLE),
        privilege);
  }

  public PrivilegeResult revokePrivilegeOnTableFromRole(
      String catalogName,
      String catalogRoleName,
      TableIdentifier identifier,
      PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_TABLE_GRANT_FROM_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnTableLikeOperationOrThrow(
            op,
            catalogName,
            List.of(PolarisEntitySubType.GENERIC_TABLE, PolarisEntitySubType.ICEBERG_TABLE),
            identifier,
            catalogRoleName);

    return revokePrivilegeOnTableLikeFromRole(
        resolutionManifest,
        catalogName,
        catalogRoleName,
        identifier,
        List.of(PolarisEntitySubType.GENERIC_TABLE, PolarisEntitySubType.ICEBERG_TABLE),
        privilege);
  }

  public PrivilegeResult grantPrivilegeOnViewToRole(
      String catalogName,
      String catalogRoleName,
      TableIdentifier identifier,
      PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.ADD_VIEW_GRANT_TO_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnTableLikeOperationOrThrow(
            op,
            catalogName,
            List.of(PolarisEntitySubType.ICEBERG_VIEW),
            identifier,
            catalogRoleName);

    return grantPrivilegeOnTableLikeToRole(
        resolutionManifest,
        catalogName,
        catalogRoleName,
        identifier,
        List.of(PolarisEntitySubType.ICEBERG_VIEW),
        privilege);
  }

  public PrivilegeResult revokePrivilegeOnViewFromRole(
      String catalogName,
      String catalogRoleName,
      TableIdentifier identifier,
      PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_VIEW_GRANT_FROM_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnTableLikeOperationOrThrow(
            op,
            catalogName,
            List.of(PolarisEntitySubType.ICEBERG_VIEW),
            identifier,
            catalogRoleName);

    return revokePrivilegeOnTableLikeFromRole(
        resolutionManifest,
        catalogName,
        catalogRoleName,
        identifier,
        List.of(PolarisEntitySubType.ICEBERG_VIEW),
        privilege);
  }

  public PrivilegeResult grantPrivilegeOnPolicyToRole(
      String catalogName,
      String catalogRoleName,
      PolicyIdentifier identifier,
      PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.ADD_POLICY_GRANT_TO_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnPolicyOperationOrThrow(op, catalogName, identifier, catalogRoleName);

    return grantPrivilegeOnPolicyEntityToRole(
        resolutionManifest, catalogName, catalogRoleName, identifier, privilege);
  }

  public PrivilegeResult revokePrivilegeOnPolicyFromRole(
      String catalogName,
      String catalogRoleName,
      PolicyIdentifier identifier,
      PolarisPrivilege privilege) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.REVOKE_POLICY_GRANT_FROM_CATALOG_ROLE;

    PolarisResolutionManifest resolutionManifest =
        authorizeGrantOnPolicyOperationOrThrow(op, catalogName, identifier, catalogRoleName);

    return revokePrivilegeOnPolicyEntityFromRole(
        resolutionManifest, catalogName, catalogRoleName, identifier, privilege);
  }

  public List<PolarisEntity> listAssigneePrincipalRolesForCatalogRole(
      String catalogName, String catalogRoleName) {
    PolarisAuthorizableOperation op =
        PolarisAuthorizableOperation.LIST_ASSIGNEE_PRINCIPAL_ROLES_FOR_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicCatalogRoleOperationOrThrow(op, catalogName, catalogRoleName);

    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
    LoadGrantsResult grantList =
        metaStoreManager.loadGrantsOnSecurable(getCurrentPolarisContext(), catalogRoleEntity);
    return buildEntitiesFromGrantResults(grantList, true, PolarisEntityType.PRINCIPAL_ROLE, null);
  }

  /**
   * Lists all grants on Catalog-level resources (Catalog/Namespace/Table/View) granted to the
   * specified catalogRole.
   */
  public List<GrantResource> listGrantsForCatalogRole(String catalogName, String catalogRoleName) {
    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_GRANTS_FOR_CATALOG_ROLE;
    PolarisResolutionManifest resolutionManifest =
        authorizeBasicCatalogRoleOperationOrThrow(op, catalogName, catalogRoleName);

    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
    LoadGrantsResult grantList =
        metaStoreManager.loadGrantsToGrantee(getCurrentPolarisContext(), catalogRoleEntity);
    List<CatalogGrant> catalogGrants = new ArrayList<>();
    List<NamespaceGrant> namespaceGrants = new ArrayList<>();
    List<TableGrant> tableGrants = new ArrayList<>();
    List<ViewGrant> viewGrants = new ArrayList<>();
    List<PolicyGrant> policyGrants = new ArrayList<>();
    Map<Long, PolarisBaseEntity> entityMap = grantList.getEntitiesAsMap();
    for (PolarisGrantRecord record : grantList.getGrantRecords()) {
      PolarisPrivilege privilege = PolarisPrivilege.fromCode(record.getPrivilegeCode());
      PolarisBaseEntity baseEntity = this.getOrLoadEntityForGrant(entityMap, record);
      if (baseEntity != null) {
        switch (baseEntity.getType()) {
          case CATALOG:
            {
              CatalogGrant grant =
                  new CatalogGrant(
                      CatalogPrivilege.valueOf(privilege.toString()),
                      GrantResource.TypeEnum.CATALOG);
              catalogGrants.add(grant);
              break;
            }
          case NAMESPACE:
            {
              NamespaceGrant grant =
                  new NamespaceGrant(
                      List.of(NamespaceEntity.of(baseEntity).asNamespace().levels()),
                      NamespacePrivilege.valueOf(privilege.toString()),
                      GrantResource.TypeEnum.NAMESPACE);
              namespaceGrants.add(grant);
              break;
            }
          case TABLE_LIKE:
            {
              if (baseEntity.getSubType() == PolarisEntitySubType.ICEBERG_TABLE
                  || baseEntity.getSubType() == PolarisEntitySubType.GENERIC_TABLE) {
                TableIdentifier identifier =
                    IcebergTableLikeEntity.of(baseEntity).getTableIdentifier();
                TableGrant grant =
                    new TableGrant(
                        List.of(identifier.namespace().levels()),
                        identifier.name(),
                        TablePrivilege.valueOf(privilege.toString()),
                        GrantResource.TypeEnum.TABLE);
                tableGrants.add(grant);
              } else if (baseEntity.getSubType() == PolarisEntitySubType.ICEBERG_VIEW) {
                TableIdentifier identifier =
                    IcebergTableLikeEntity.of(baseEntity).getTableIdentifier();
                ViewGrant grant =
                    new ViewGrant(
                        List.of(identifier.namespace().levels()),
                        identifier.name(),
                        ViewPrivilege.valueOf(privilege.toString()),
                        GrantResource.TypeEnum.VIEW);
                viewGrants.add(grant);
              } else {
                throw new IllegalStateException(
                    "Unrecognized entity subtype " + baseEntity.getSubType());
              }
              break;
            }
          case POLICY:
            {
              PolicyEntity policyEntity = PolicyEntity.of(baseEntity);
              PolicyGrant grant =
                  new PolicyGrant(
                      Arrays.asList(policyEntity.getParentNamespace().levels()),
                      policyEntity.getName(),
                      PolicyPrivilege.valueOf(privilege.toString()),
                      GrantResource.TypeEnum.POLICY);
              policyGrants.add(grant);
              break;
            }
          default:
            throw new IllegalArgumentException(
                String.format(
                    "Unexpected entity type '%s' listing grants for catalogRole '%s' in catalog '%s'",
                    baseEntity.getType(), catalogRoleName, catalogName));
        }
      }
    }
    // Assemble these at the end so that they're grouped by type.
    List<GrantResource> allGrants = new ArrayList<>();
    allGrants.addAll(catalogGrants);
    allGrants.addAll(namespaceGrants);
    allGrants.addAll(tableGrants);
    allGrants.addAll(viewGrants);
    allGrants.addAll(policyGrants);
    return allGrants;
  }

  /**
   * Get the specified entity from the input map or load it from backend if the input map is null.
   * Normally the input map is not expected to be null, except for backward compatibility issue.
   *
   * @param entitiesMap map of entities
   * @param catalogId the id of the catalog of the entity we are looking for
   * @param id id of the entity we are looking for
   * @param entityType
   * @return null if the entity does not exist
   */
  private @Nullable PolarisBaseEntity getOrLoadEntity(
      @Nullable Map<Long, PolarisBaseEntity> entitiesMap,
      long catalogId,
      long id,
      PolarisEntityType entityType) {
    return (entitiesMap == null)
        ? metaStoreManager
            .loadEntity(getCurrentPolarisContext(), catalogId, id, entityType)
            .getEntity()
        : entitiesMap.get(id);
  }

  private @Nullable PolarisBaseEntity getOrLoadEntityForGrant(
      @Nullable Map<Long, PolarisBaseEntity> entitiesMap, PolarisGrantRecord record) {
    if (entitiesMap != null) {
      return entitiesMap.get(record.getSecurableId());
    }

    for (PolarisEntityType type : PolarisEntityType.values()) {
      EntityResult entityResult =
          metaStoreManager.loadEntity(
              getCurrentPolarisContext(),
              record.getSecurableCatalogId(),
              record.getSecurableId(),
              type);
      if (entityResult.isSuccess()) {
        return entityResult.getEntity();
      }
    }

    return null;
  }

  /** Adds a table-level or view-level grant on {@code identifier} to {@code catalogRoleName}. */
  private PrivilegeResult grantPrivilegeOnTableLikeToRole(
      PolarisResolutionManifest resolutionManifest,
      String catalogName,
      String catalogRoleName,
      TableIdentifier identifier,
      List<PolarisEntitySubType> subTypes,
      PolarisPrivilege privilege) {
    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getResolvedPath(
            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE);
    if (resolvedPathWrapper == null
        || !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
      boolean rbacForFederatedCatalogsEnabled =
          realmConfig.getConfig(
              FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS, catalogEntity);
      if (resolutionManifest.getIsPassthroughFacade() && rbacForFederatedCatalogsEnabled) {
        resolvedPathWrapper =
            createSyntheticTableLikeEntities(
                resolutionManifest, catalogEntity, identifier, subTypes, resolvedPathWrapper);
        if (resolvedPathWrapper == null
            || !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
          // TODO: update the exception thrown as we refine the possible retry scenarios
          throw new RuntimeException(
              String.format(
                  "Failed to create synthetic table-like entity for table %s in catalog %s",
                  identifier.name(), catalogEntity.getName()));
        }
      } else {
        throw notFoundExceptionForTableLikeEntity(identifier, subTypes);
      }
    }
    List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
    PolarisEntity tableLikeEntity = resolvedPathWrapper.getRawLeafEntity();

    return metaStoreManager.grantPrivilegeOnSecurableToRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(catalogPath),
        tableLikeEntity,
        privilege);
  }

  /**
   * Creates and persists the missing synthetic table-like entity and its parent namespace entities
   * for external catalogs.
   *
   * @param catalogEntity the external passthrough facade catalog entity.
   * @param identifier the path of the table-like entity(including the namespace).
   * @param subTypes the expected subtypes of the table-like entity
   * @param existingPathWrapper the partially resolved path currently stored in the metastore.
   * @return the resolved path wrapper
   */
  private PolarisResolvedPathWrapper createSyntheticTableLikeEntities(
      PolarisResolutionManifest resolutionManifest,
      CatalogEntity catalogEntity,
      TableIdentifier identifier,
      List<PolarisEntitySubType> subTypes,
      PolarisResolvedPathWrapper existingPathWrapper) {

    Namespace namespace = identifier.namespace();
    PolarisResolvedPathWrapper resolvedNamespacePathWrapper =
        !namespace.isEmpty()
            ? createSyntheticNamespaceEntities(
                resolutionManifest, catalogEntity, namespace, existingPathWrapper)
            : existingPathWrapper;

    if (resolvedNamespacePathWrapper == null
        || (!namespace.isEmpty()
            && !resolvedNamespacePathWrapper.isFullyResolvedNamespace(
                catalogEntity.getName(), namespace))) {
      throw new RuntimeException(
          String.format(
              "Failed to create synthetic namespace entities for namespace %s in catalog %s",
              namespace.toString(), catalogEntity.getName()));
    }

    PolarisEntity parentNamespaceEntity = resolvedNamespacePathWrapper.getRawLeafEntity();

    // TODO: Once we support GENERIC_TABLE federation, select the intended type depending on the
    // callsite; if it is instantiated via an Iceberg RESTCatalog factory or a different factory
    // for GenericCatalogs.
    PolarisEntitySubType syntheticEntitySubType = selectEntitySubType(subTypes);

    // TODO: Instead of creating a synthetic table-like entity, rely on external catalog mediated
    // backfill and use the metadata location from the external catalog.
    PolarisEntity syntheticTableEntity =
        new IcebergTableLikeEntity.Builder(syntheticEntitySubType, identifier, "")
            .setParentId(parentNamespaceEntity.getId())
            .setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
            .setCatalogId(parentNamespaceEntity.getCatalogId())
            .setCreateTimestamp(System.currentTimeMillis())
            .build();
    // We will re-resolve later anyway, so
    metaStoreManager.createEntityIfNotExists(
        getCurrentPolarisContext(),
        PolarisEntity.toCoreList(resolvedNamespacePathWrapper.getRawFullPath()),
        syntheticTableEntity);

    PolarisResolvedPathWrapper completePathWrapper =
        resolutionManifest.getPassthroughResolvedPath(ResolvedPathKey.ofTableLike(identifier));
    PolarisEntity leafEntity =
        completePathWrapper != null ? completePathWrapper.getRawLeafEntity() : null;
    if (completePathWrapper == null
        || leafEntity == null
        || !(leafEntity.getType() == PolarisEntityType.TABLE_LIKE
            && leafEntity.getSubType() == PolarisEntitySubType.ICEBERG_TABLE
            && Objects.equals(leafEntity.getName(), identifier.name()))) {
      throw new RuntimeException(
          String.format(
              "Failed to create or find table entity '%s' in federated catalog '%s'",
              identifier.name(), catalogEntity.getName()));
    }
    return completePathWrapper;
  }

  /**
   * Removes a table-level or view-level grant on {@code identifier} from {@code catalogRoleName}.
   */
  private PrivilegeResult revokePrivilegeOnTableLikeFromRole(
      PolarisResolutionManifest resolutionManifest,
      String catalogName,
      String catalogRoleName,
      TableIdentifier identifier,
      List<PolarisEntitySubType> subTypes,
      PolarisPrivilege privilege) {
    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getResolvedPath(
            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE);
    if (resolvedPathWrapper == null
        || !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
      throw notFoundExceptionForTableLikeEntity(identifier, subTypes);
    }
    PolarisEntity tableLikeEntity = resolvedPathWrapper.getRawLeafEntity();

    return metaStoreManager.revokePrivilegeOnSecurableFromRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(List.of(catalogEntity)),
        tableLikeEntity,
        privilege);
  }

  private PrivilegeResult grantPrivilegeOnPolicyEntityToRole(
      PolarisResolutionManifest resolutionManifest,
      String catalogName,
      String catalogRoleName,
      PolicyIdentifier identifier,
      PolarisPrivilege privilege) {
    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getResolvedPath(
            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()));
    if (resolvedPathWrapper == null) {
      throw new NoSuchPolicyException(String.format("Policy not exists: %s", identifier));
    }

    PolarisEntity policyEntity = resolvedPathWrapper.getRawLeafEntity();

    return metaStoreManager.grantPrivilegeOnSecurableToRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(List.of(catalogEntity)),
        policyEntity,
        privilege);
  }

  private PrivilegeResult revokePrivilegeOnPolicyEntityFromRole(
      PolarisResolutionManifest resolutionManifest,
      String catalogName,
      String catalogRoleName,
      PolicyIdentifier identifier,
      PolarisPrivilege privilege) {
    CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
    CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);

    PolarisResolvedPathWrapper resolvedPathWrapper =
        resolutionManifest.getResolvedPath(
            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()));
    if (resolvedPathWrapper == null) {
      throw new NoSuchPolicyException(String.format("Policy not exists: %s", identifier));
    }

    PolarisEntity policyEntity = resolvedPathWrapper.getRawLeafEntity();

    return metaStoreManager.revokePrivilegeOnSecurableFromRole(
        getCurrentPolarisContext(),
        catalogRoleEntity,
        PolarisEntity.toCoreList(List.of(catalogEntity)),
        policyEntity,
        privilege);
  }

  /**
   * Selects the appropriate entity subtype for synthetic entities in external catalogs.
   *
   * @param subTypes list of candidate subtypes
   * @return the selected subtype for the synthetic entity
   * @throws IllegalStateException if no supported subtype is found
   */
  private static PolarisEntitySubType selectEntitySubType(List<PolarisEntitySubType> subTypes) {
    if (subTypes.contains(PolarisEntitySubType.ICEBERG_TABLE)) {
      return PolarisEntitySubType.ICEBERG_TABLE;
    } else if (subTypes.contains(PolarisEntitySubType.ICEBERG_VIEW)) {
      return PolarisEntitySubType.ICEBERG_VIEW;
    } else {
      throw new IllegalStateException(
          String.format(
              "No supported subtype found in %s. Only ICEBERG_TABLE and ICEBERG_VIEW are"
                  + " supported for synthetic entities in external catalogs.",
              subTypes));
    }
  }
}
