/*
 * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.com).
 *
 * WSO2 Inc. 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.wso2.carbon.is.migration.service.v710.migrator;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.balana.AbstractPolicy;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.entitlement.PDPConstants;
import org.wso2.carbon.identity.entitlement.dto.PolicyDTO;
import org.wso2.carbon.identity.entitlement.dto.PolicyStoreDTO;
import org.wso2.carbon.identity.entitlement.dto.PublisherDataHolder;
import org.wso2.carbon.identity.entitlement.dto.PublisherPropertyDTO;
import org.wso2.carbon.identity.entitlement.dto.StatusHolder;
import org.wso2.carbon.identity.entitlement.pap.PAPPolicyReader;
import org.wso2.carbon.identity.entitlement.policy.PolicyAttributeBuilder;
import org.wso2.carbon.is.migration.service.Migrator;
import org.wso2.carbon.is.migration.service.v710.dao.XACMLDAO;
import org.wso2.carbon.is.migration.service.v710.model.PolicyMigrationDTO;
import org.wso2.carbon.is.migration.util.Utility;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.RegistryConstants;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.user.api.Tenant;

import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import static org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.ACTIVE;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.CONFIG_BASE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.GLOBAL_POLICY_COMBINING_ALGORITHM;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.ORDER;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.PAP_BASE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.PAP_VERSION_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.PATH_SEPARATOR;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.PDP_BASE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.POLICY;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.STATUS_BASE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.SUBSCRIBER;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.SUBSCRIBER_BASE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.XACMLRegistryConstants.SUBSCRIBER_ID;

/**
 * Migration implementation to migrate residual XACML resources.
 */
public class XACMLMigrator extends Migrator {

    private static final Logger LOG = LoggerFactory.getLogger(XACMLMigrator.class);
    private static final String WAIT_TIME = "waitTime";
    private static final long DEFAULT_WAIT_TIME = 2;
    private final XACMLDAO xacmlDAO = new XACMLDAO();
    private static final String WAIT_TILL_COOL_DOWN_MSG = "RDM-XACML>> Waiting {}s to cool down...";
    private static final String ERROR_WHILE_WAITING_FOR_NEXT_BATCH = "RDM-XACML>> Error while waiting for the next " +
            "batch.";
    private static final String COMBINED_POLICIES = "combinedPolicies";
    private static final String PDP_ONLY_POLICIES = "pdpOnlyPolicies";

    long waitTime = DEFAULT_WAIT_TIME;

    @Override
    public void dryRun() throws MigrationClientException {

        LOG.info("RDM-XACML-DR>> Residual data migration (dry run) of notification templates started.");
        migrateXACMLResources(true);
    }

    @Override
    public void migrate() throws MigrationClientException {

        LOG.info("RDM-XACML>> Residual data migration of XACML resources started.");
        migrateXACMLResources(false);
    }

    private void migrateXACMLResources(boolean isDryRun) throws MigrationClientException {

        Properties migrationProperties = getMigratorConfig().getParameters();

        if (migrationProperties.containsKey(WAIT_TIME)) {
            waitTime = (int) migrationProperties.get(WAIT_TIME);
        }

        LOG.info("RDM-XACML>> Parameters: Wait time {}s.", waitTime);

        int successTenantCount = 0;
        int errorTenantCount = 0;
        int inactiveTenantCount = 0;

        Set<Tenant> tenants = Utility.getTenants();
        try {
            // Migrate super tenant.
            migrateXACMLResources(isDryRun, SUPER_TENANT_DOMAIN_NAME);
            successTenantCount++;

            // Migrate other tenants.
            List<Integer> inactiveTenants = Utility.getInactiveTenants();
            boolean ignoreForInactiveTenants = isIgnoreForInactiveTenants();

            for (Tenant tenant : tenants) {
                int tenantId = tenant.getId();
                String tenantDomain = tenant.getDomain();

                if (ignoreForInactiveTenants && inactiveTenants.contains(tenantId)) {
                    inactiveTenantCount++;
                    LOG.info("RDM-XACML>> Tenant {} is inactive. Skipping claim data migration!", tenantDomain);
                    continue;
                }

                try {
                    PrivilegedCarbonContext.startTenantFlow();
                    PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
                    carbonContext.setTenantId(tenantId);
                    carbonContext.setTenantDomain(tenantDomain);
                    IdentityTenantUtil.getTenantRegistryLoader().loadTenantRegistry(tenantId);
                    migrateXACMLResources(isDryRun, tenantDomain);
                    successTenantCount++;
                } catch (RegistryException | MigrationClientException e) {
                    if (!isContinueOnError()) {
                        throw new MigrationClientException(e.getMessage(), e);
                    }
                    errorTenantCount++;
                    LOG.error("RDM-XACML>> Error while migrating XACML resources in tenant {}.", tenantDomain, e);
                } finally {
                    PrivilegedCarbonContext.endTenantFlow();
                }
            }
        } catch (MigrationClientException e) {
            errorTenantCount++;
            LOG.error("RDM-XACML>> Error while migrating XACML resources.", e);
        }

        if (errorTenantCount == 0) {
            LOG.info("RDM-XACML>> XACML residual data migration successfully completed.");
        } else {
            LOG.info("RDM-XACML>> XACML residual data migration completed with errors in {} tenants.",
                    errorTenantCount);
        }
        LOG.info("RDM-XACML>> Migrated {} tenants successfully.", successTenantCount);
        LOG.info("RDM-XACML>> Skipped {} inactive tenants.", inactiveTenantCount);
        LOG.info("RDM-XACML>> Total {} tenants selected for migration.", tenants.size() + 1);
    }

    private void migrateXACMLResources(boolean isDryRun, String tenantDomain) throws MigrationClientException {

        Registry registry = getRegistry(IdentityTenantUtil.getTenantId(tenantDomain));
        // Migrate configs
        migrateXACMLConfigs(isDryRun, registry, tenantDomain);
        // Migrate xacml policies
        migrateXACMLPolicies(isDryRun, registry, tenantDomain);
        // Migrate subscribers
        migrateXACMLSubscribers(isDryRun, registry, tenantDomain);
        // Migrate audit trail
        migrateXACMLStatuses(isDryRun, registry, tenantDomain);
    }

    private void migrateXACMLConfigs(boolean isDryRun, Registry registry, String tenantDomain)
            throws MigrationClientException {

        try {
            if (!registry.resourceExists(CONFIG_BASE_PATH)) {
                LOG.info("RDM-XACML>> No XACML configs found in tenant {}.", tenantDomain);
                return;
            }
        } catch (RegistryException e) {
            String msg =
                    String.format("Error while checking the existence of xacml configs in tenant %s.", tenantDomain);
            throw new MigrationClientException(msg, e);
        }

        LOG.info("RDM-XACML>> Listing XACML configs in tenant {}.", tenantDomain);
        String xacmlConfig = getConfigsFromRegistry(registry);

        if (StringUtils.isBlank(xacmlConfig)) {
            LOG.info("RDM-XACML>> No xacml configs found in tenant {}.", tenantDomain);
            return;
        }

        try {
            int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            xacmlDAO.migrateXACMLConfigs(xacmlConfig, isDryRun, tenantId);

            try {
                LOG.info("RDM-XACML>> Cleaning xacml configs in registry in tenant {}.", tenantDomain);
                if (!isDryRun) {
                    registry.delete(CONFIG_BASE_PATH);
                } else {
                    LOG.info(
                            "RDM-XACML>> xacml configs in registry in tenant {} deletion will be skipped due to dry run.",
                            tenantDomain);
                }
                LOG.info("RDM-XACML>> xacml configs migration in tenant {} successfully completed.", tenantDomain);
            } catch (RegistryException e) {
                String msg = String.format("Error while cleaning xacml configs in tenant %s.", tenantDomain);
                throw new MigrationClientException(msg, e);
            }
        } finally {
            if (waitTime > 0) {
                try {
                    LOG.info(WAIT_TILL_COOL_DOWN_MSG, waitTime);
                    Thread.sleep(waitTime * 1000);
                } catch (InterruptedException e) {
                    LOG.error(ERROR_WHILE_WAITING_FOR_NEXT_BATCH, e);
                }
            }
        }
    }

    private void migrateXACMLSubscribers(boolean isDryRun, Registry registry, String tenantDomain)
            throws MigrationClientException {

        try {
            if (!registry.resourceExists(SUBSCRIBER_BASE_PATH)) {
                LOG.info("RDM-XACML>> No XACML subscribers found in tenant {}.", tenantDomain);
                return;
            }
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while checking the existence of xacml subscribers in tenant {}.", tenantDomain,
                    e);
            return;
        }

        LOG.info("RDM-XACML>> Listing XACML subscribers in tenant {}.", tenantDomain);
        List<PublisherDataHolder> xacmlSubscribers = getSubscribersFromRegistry(registry);

        if (xacmlSubscribers.isEmpty()) {
            LOG.info("RDM-XACML>> No xacml subscribers found in tenant {}.", tenantDomain);
            return;
        }

        try {
            int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            xacmlDAO.migrateXACMLSubscribers(xacmlSubscribers, isDryRun, tenantId);

            try {
                LOG.info("RDM-XACML>> Cleaning xacml subscribers in registry in tenant {}.", tenantDomain);
                if (!isDryRun) {
                    registry.delete(SUBSCRIBER_BASE_PATH);
                } else {
                    LOG.info(
                            "RDM-XACML>> xacml subscribers in registry in tenant {} deletion will be skipped due to dry run.",
                            tenantDomain);
                }
                LOG.info("RDM-XACML>> xacml subscriber migration in tenant {} successfully completed.", tenantDomain);
            } catch (RegistryException e) {
                String msg = String.format("Error while cleaning xacml subscribers in tenant %s.", tenantDomain);
                throw new MigrationClientException(msg, e);
            }
        } finally {
            if (waitTime > 0) {
                try {
                    LOG.info(WAIT_TILL_COOL_DOWN_MSG, waitTime);
                    Thread.sleep(waitTime * 1000);
                } catch (InterruptedException e) {
                    LOG.error(ERROR_WHILE_WAITING_FOR_NEXT_BATCH, e);
                }
            }
        }
    }

    private void migrateXACMLStatuses(boolean isDryRun, Registry registry, String tenantDomain)
            throws MigrationClientException {

        try {
            if (!registry.resourceExists(STATUS_BASE_PATH)) {
                LOG.info("RDM-XACML>> No XACML statuses found in tenant {}.", tenantDomain);
                return;
            }
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while checking the existence of xacml statuses in tenant {}.", tenantDomain,
                    e);
            return;
        }

        LOG.info("RDM-XACML>> Listing XACML statuses in tenant {}.", tenantDomain);
        Map<String, List<StatusHolder>> policyStatusHolders = getPolicyStatusesFromRegistry(registry);
        Map<String, List<StatusHolder>> subscriberStatusHolders = getSubscriberStatusesFromRegistry(registry);

        try {
            int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            xacmlDAO.migrateXACMLPolicyStatuses(policyStatusHolders, isDryRun, tenantId);
            xacmlDAO.migrateXACMLSubscriberStatuses(subscriberStatusHolders, isDryRun, tenantId);

            try {
                LOG.info("RDM-XACML>> Cleaning xacml subscribers in registry in tenant {}.", tenantDomain);
                if (!isDryRun) {
                    registry.delete(STATUS_BASE_PATH);
                } else {
                    LOG.info("RDM-XACML>> xacml subscribers in registry in tenant {} deletion will be skipped due to " +
                            "dry run.", tenantDomain);
                }
                LOG.info("RDM-XACML>> xacml subscriber migration in tenant {} successfully completed.", tenantDomain);
            } catch (RegistryException e) {
                String msg = String.format("Error while cleaning xacml subscribers in tenant %s.", tenantDomain);
                throw new MigrationClientException(msg, e);
            }
        } finally {
            if (waitTime > 0) {
                try {
                    LOG.info(WAIT_TILL_COOL_DOWN_MSG, waitTime);
                    Thread.sleep(waitTime * 1000);
                } catch (InterruptedException e) {
                    LOG.error(ERROR_WHILE_WAITING_FOR_NEXT_BATCH, e);
                }
            }
        }
    }

    private void migrateXACMLPolicies(boolean isDryRun, Registry registry, String tenantDomain)
            throws MigrationClientException {

        // TODO: PDP path is customizable in the registry. Need to find a way to get the correct path.
        try {
            if (!registry.resourceExists(PAP_BASE_PATH) && !registry.resourceExists(PDP_BASE_PATH)) {
                LOG.info("RDM-XACML>> No XACML policies found in tenant {}.", tenantDomain);
                return;
            }
        } catch (RegistryException e) {
            String msg = String.format("Error while checking the existence of xacml policies in tenant %s.",
                    tenantDomain);
            throw new MigrationClientException(msg, e);
        }

        LOG.info("RDM-XACML>> Listing XACML policies in tenant {}.", tenantDomain);
        List<PolicyDTO> xacmlPAPPolicies = getPAPPoliciesFromRegistry(registry);
        List<PolicyStoreDTO> xacmlPDPPolicies = getPDPPoliciesFromRegistry(registry, tenantDomain);

        if (xacmlPAPPolicies.isEmpty() && xacmlPDPPolicies.isEmpty()) {
            LOG.info("RDM-XACML>> No xacml policies found in tenant {}.", tenantDomain);
            return;
        }

        // Merge PAP and PDP policies into single PolicyDTO objects.
        List<PolicyMigrationDTO> xacmlPolicies = combinePAPAndPDPToPolicyDTOs(xacmlPAPPolicies, xacmlPDPPolicies);

        try {
            int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            xacmlDAO.migrateXACMLPolicies(xacmlPolicies, isDryRun, tenantId);
            try {
                LOG.info("RDM-XACML>> Cleaning xacml policies in registry in tenant {}.", tenantDomain);
                if (!isDryRun) {
                    registry.delete(PAP_BASE_PATH);
                    registry.delete(PDP_BASE_PATH);
                } else {
                    LOG.info(
                            "RDM-XACML>> xacml policy in registry in tenant {} deletion will be skipped due to dry run.",
                            tenantDomain);
                }
                LOG.info("RDM-XACML>> xacml policy migration in tenant {} successfully completed.", tenantDomain);
            } catch (RegistryException e) {
                String msg = String.format("Error while cleaning xacml policies in tenant %s.", tenantDomain);
                throw new MigrationClientException(msg, e);
            }
        } finally {
            if (waitTime > 0) {
                try {
                    LOG.info(WAIT_TILL_COOL_DOWN_MSG, waitTime);
                    Thread.sleep(waitTime * 1000);
                } catch (InterruptedException e) {
                    LOG.error(ERROR_WHILE_WAITING_FOR_NEXT_BATCH, e);
                }
            }
        }
    }

    private String getConfigsFromRegistry(Registry registry) {

        try {
            Collection collection = (Collection) registry.get(CONFIG_BASE_PATH);
            return collection.getProperty(GLOBAL_POLICY_COMBINING_ALGORITHM);
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while listing xacml configs in registry.", e);
            return null;
        }
    }

    private List<PublisherDataHolder> getSubscribersFromRegistry(Registry registry) {

        List<PublisherDataHolder> subscribers = new ArrayList<>();
        try {
            List<String> subscriberIds = getSubscriberIds(registry);
            for (String subscriberId : subscriberIds) {
                if (registry.resourceExists(SUBSCRIBER_BASE_PATH + subscriberId)) {
                    Resource resource = registry.get(SUBSCRIBER_BASE_PATH + subscriberId);
                    subscribers.add(getPublisherDataHolder(resource));
                }
            }
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while listing xacml subscribers in registry.");
            return new ArrayList<>();
        }
        return subscribers;
    }

    private static List<String> getSubscriberIds(Registry registry)
            throws RegistryException {

        List<String> subscriberIds = new ArrayList<>();
        Collection collection = (Collection) registry.get(SUBSCRIBER_BASE_PATH);
        if (collection.getChildCount() > 0) {
            for (String path : collection.getChildren()) {
                Resource childResource = registry.get(path);
                if (childResource != null && childResource.getProperty(SUBSCRIBER_ID) != null) {
                    String subscriberId = childResource.getProperty(SUBSCRIBER_ID);
                    subscriberIds.add(subscriberId);
                }
            }
        }
        return subscriberIds;
    }

    private PublisherDataHolder getPublisherDataHolder(Resource resource) {

        List<PublisherPropertyDTO> propertyDTOs = new ArrayList<>();
        String moduleName = null;
        if (resource != null && resource.getProperties() != null) {
            Properties properties = resource.getProperties();
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                PublisherPropertyDTO dto = new PublisherPropertyDTO();
                dto.setId((String) entry.getKey());
                Object value = entry.getValue();
                if (value instanceof ArrayList) {
                    List list = (ArrayList) entry.getValue();
                    if (!list.isEmpty() && list.get(0) != null) {
                        dto.setValue((String) list.get(0));

                        if (list.size() > 1 && list.get(1) != null) {
                            dto.setDisplayName((String) list.get(1));
                        }
                        if (list.size() > 2 && list.get(2) != null) {
                            dto.setDisplayOrder(Integer.parseInt((String) list.get(2)));
                        }
                        if (list.size() > 3 && list.get(3) != null) {
                            dto.setRequired(Boolean.parseBoolean((String) list.get(3)));
                        }
                        if (list.size() > 4 && list.get(4) != null) {
                            dto.setSecret(Boolean.parseBoolean((String) list.get(4)));
                        }
                    }
                }
                if (PublisherDataHolder.MODULE_NAME.equals(dto.getId())) {
                    moduleName = dto.getValue();
                    continue;
                }

                propertyDTOs.add(dto);
            }
        }
        return new PublisherDataHolder(propertyDTOs, moduleName);
    }

    private Map<String, List<StatusHolder>> getPolicyStatusesFromRegistry(Registry registry) {

        Map<String, List<StatusHolder>> policyStatusHolders = new HashMap<>();
        try {
            List<String> policyIds = new ArrayList<>();
            if (registry.resourceExists(PAP_BASE_PATH)) {
                policyIds = getPolicyIds(registry);
            }
            for (String policyId : policyIds) {
                String path = STATUS_BASE_PATH + "policy" + PATH_SEPARATOR + policyId;
                policyStatusHolders.put(policyId, readStatus(path, POLICY, registry));
            }
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while listing xacml statuses in registry.");
            return new HashMap<>();
        }
        return policyStatusHolders;
    }

    private Map<String, List<StatusHolder>> getSubscriberStatusesFromRegistry(Registry registry) {

        Map<String, List<StatusHolder>> subscriberStatusHolders = new HashMap<>();
        try {
            List<String> subscriberIds = new ArrayList<>();
            if (registry.resourceExists(SUBSCRIBER_BASE_PATH)) {
                subscriberIds = getSubscriberIds(registry);
            }
            for (String subscriberId : subscriberIds) {
                String path = STATUS_BASE_PATH + "publisher" + PATH_SEPARATOR + subscriberId;
                subscriberStatusHolders.put(subscriberId, readStatus(path, SUBSCRIBER, registry));
            }
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while listing xacml statuses in registry.");
            return new HashMap<>();
        }
        return subscriberStatusHolders;
    }

    private List<StatusHolder> readStatus(String path, String about, Registry registry) throws RegistryException {

        if (!registry.resourceExists(path)) {
            return Collections.emptyList();
        }
        Resource resource = registry.get(path);
        List<StatusHolder> statusHolders = new ArrayList<>();
        if (resource != null && resource.getProperties() != null) {
            Properties properties = resource.getProperties();
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                PublisherPropertyDTO dto = new PublisherPropertyDTO();
                dto.setId((String) entry.getKey());
                Object value = entry.getValue();
                if (value instanceof ArrayList) {
                    List list = (ArrayList) entry.getValue();
                    if (!list.isEmpty() && list.get(0) != null) {
                        StatusHolder statusHolder = new StatusHolder(about);
                        if (list.get(0) != null) {
                            statusHolder.setType((String) list.get(0));
                        }
                        if (list.size() > 1 && list.get(1) != null) {
                            statusHolder.setTimeInstance((String) list.get(1));
                        } else {
                            continue;
                        }
                        if (list.size() > 2 && list.get(2) != null) {
                            String user = (String) list.get(2);
                            statusHolder.setUser(user);
                        } else {
                            continue;
                        }
                        if (list.size() > 3 && list.get(3) != null) {
                            statusHolder.setKey((String) list.get(3));
                        }
                        if (list.size() > 4 && list.get(4) != null) {
                            statusHolder.setSuccess(Boolean.parseBoolean((String) list.get(4)));
                        }
                        if (list.size() > 5 && list.get(5) != null) {
                            statusHolder.setMessage((String) list.get(5));
                        }
                        if (list.size() > 6 && list.get(6) != null) {
                            statusHolder.setTarget((String) list.get(6));
                        }
                        if (list.size() > 7 && list.get(7) != null) {
                            statusHolder.setTargetAction((String) list.get(7));
                        }
                        if (list.size() > 8 && list.get(8) != null) {
                            statusHolder.setVersion((String) list.get(8));
                        }
                        statusHolders.add(statusHolder);
                    }
                }
            }
        }
        return statusHolders;
    }

    private List<PolicyDTO> getPAPPoliciesFromRegistry(Registry registry)
            throws MigrationClientException {

        List<PolicyDTO> policies = new ArrayList<>();
        try {
            List<String> policyIds = getPolicyIds(registry);
            for (String policyId : policyIds) {
                String versionPath = PAP_VERSION_PATH + policyId;
                Collection versionCollection = (Collection) registry.get(versionPath);
                if (versionCollection != null && versionCollection.getChildren() != null) {
                    String[] versionChildren = versionCollection.getChildren();
                    for (String versionChild : versionChildren) {
                        String version = RegistryUtils.getResourceName(versionChild);
                        PolicyDTO policyDTO = getPolicy(policyId, version, registry);
                        if (policyDTO != null) {
                            policies.add(policyDTO);
                        }
                    }
                }
            }
        } catch (RegistryException e) {
            LOG.error("RDM-XACML>> Error while listing xacml policies in registry.", e);
            return new ArrayList<>();
        }
        return policies;
    }

    private static List<String> getPolicyIds(Registry registry) throws RegistryException {

        String[] children = ((Collection) registry.get(PAP_BASE_PATH)).getChildren();
        List<String> policyIds = new ArrayList<>();
        for (String child : children) {
            String policyId = child.substring(child.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1);
            if (!policyIds.contains(policyId)) {
                policyIds.add(policyId);
            }
        }
        return policyIds;
    }

    private List<PolicyStoreDTO> getPDPPoliciesFromRegistry(Registry registry, String tenantDomain)
            throws MigrationClientException {

        List<PolicyStoreDTO> policies = new ArrayList<>();
        try {
            Collection collection = (Collection) registry.get(PDP_BASE_PATH);
            String[] children = collection.getChildren();
            for (String child : children) {
                String publishedPolicyId = child.substring(child.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1);
                String path = PDP_BASE_PATH + publishedPolicyId;
                if (registry.resourceExists(path)) {
                    policies.add(readPolicy(registry.get(path)));
                }
            }
        } catch (RegistryException e) {
            String msg = String.format("Error while checking the existence of xacml policies in tenant %s.",
                    tenantDomain);
            throw new MigrationClientException(msg, e);
        }
        return policies;
    }

    private PolicyDTO getPolicy(String policyId, String version, Registry registry) throws MigrationClientException {

        if (version == null || version.trim().isEmpty()) {
            return null;
        }

        String collection = PAP_VERSION_PATH + policyId + PATH_SEPARATOR;
        String path = collection + version;
        return getPolicyDTO(policyId, path, registry);
    }

    private PolicyDTO getPolicyDTO(String policyId, String path, Registry registry) throws MigrationClientException {

        if (LOG.isDebugEnabled()) {
            LOG.debug(String.format("Retrieving entitlement policy %s", policyId));
        }

        try {
            if (!registry.resourceExists(path)) {
                return null;
            }

            Resource resource = registry.get(path);
            if (resource == null) {
                return null;
            }

            PolicyDTO dto = new PolicyDTO();
            dto.setPolicyId(policyId);
            dto.setPolicy(new String((byte[]) resource.getContent(), StandardCharsets.UTF_8));
            dto.setActive(Boolean.parseBoolean(resource.getProperty(PDPConstants.ACTIVE_POLICY)));
            String policyOrder = resource.getProperty(PDPConstants.POLICY_ORDER);
            if (policyOrder != null) {
                dto.setPolicyOrder(Integer.parseInt(policyOrder));
            } else {
                dto.setPolicyOrder(0);
            }

            String version = resource.getProperty(PDPConstants.POLICY_VERSION);
            if (version != null) {
                dto.setVersion(version);
            }
            String lastModifiedTime = resource.getProperty(PDPConstants.LAST_MODIFIED_TIME);
            if (lastModifiedTime != null) {
                dto.setLastModifiedTime(lastModifiedTime);
            }
            String lastModifiedUser = resource.getProperty(PDPConstants.LAST_MODIFIED_USER);
            if (lastModifiedUser != null) {
                dto.setLastModifiedUser(lastModifiedUser);
            }
            dto.setPolicyType(resource.getProperty(PDPConstants.POLICY_TYPE));

            String policyReferences = resource.getProperty(PDPConstants.POLICY_REFERENCE);
            if (policyReferences != null && !policyReferences.trim().isEmpty()) {
                dto.setPolicyIdReferences(policyReferences.split(PDPConstants.ATTRIBUTE_SEPARATOR));
            }

            String policySetReferences = resource.getProperty(PDPConstants.POLICY_SET_REFERENCE);
            if (policySetReferences != null && !policySetReferences.trim().isEmpty()) {
                dto.setPolicySetIdReferences(policySetReferences.split(PDPConstants.ATTRIBUTE_SEPARATOR));
            }

            dto.setPolicyEditor(resource.getProperty(PDPConstants.POLICY_EDITOR_TYPE));
            String basicPolicyEditorMetaDataAmount =
                    resource.getProperty(PDPConstants.BASIC_POLICY_EDITOR_META_DATA_AMOUNT);
            if (basicPolicyEditorMetaDataAmount != null) {
                int amount = Integer.parseInt(basicPolicyEditorMetaDataAmount);
                String[] basicPolicyEditorMetaData = new String[amount];
                for (int i = 0; i < amount; i++) {
                    basicPolicyEditorMetaData[i] = resource.getProperty(PDPConstants.BASIC_POLICY_EDITOR_META_DATA + i);
                }
                dto.setPolicyEditorData(basicPolicyEditorMetaData);
            }
            PolicyAttributeBuilder policyAttributeBuilder = new PolicyAttributeBuilder();
            dto.setAttributeDTOs(
                    policyAttributeBuilder.getPolicyMetaDataFromRegistryProperties(resource.getProperties()));

            return dto;

        } catch (RegistryException e) {
            throw new MigrationClientException("Error while retrieving entitlement policy PAP policy store", e);
        }

    }

    private PolicyStoreDTO readPolicy(Resource resource) throws MigrationClientException {

        String policy;
        AbstractPolicy absPolicy;
        PolicyStoreDTO dto;

        try {
            if (resource.getContent() == null) {
                throw new MigrationClientException("Error while loading entitlement policy. Policy content is null");
            }
            policy = new String((byte[]) resource.getContent(), StandardCharsets.UTF_8);
            absPolicy = PAPPolicyReader.getInstance(null).getPolicy(policy);
            dto = new PolicyStoreDTO();
            dto.setPolicyId(absPolicy.getId().toASCIIString());
            dto.setPolicy(policy);
            String policyOrder = resource.getProperty(ORDER);
            if (policyOrder != null) {
                dto.setPolicyOrder(Integer.parseInt(policyOrder));
            } else {
                dto.setPolicyOrder(0);
            }
            String policyActive = resource.getProperty(ACTIVE);
            if (policyActive != null) {
                dto.setActive(Boolean.parseBoolean(policyActive));
            }
            PolicyAttributeBuilder policyAttributeBuilder = new PolicyAttributeBuilder();
            dto.setAttributeDTOs(policyAttributeBuilder.
                    getPolicyMetaDataFromRegistryProperties(resource.getProperties()));
            return dto;
        } catch (RegistryException e) {
            throw new MigrationClientException("Error while loading entitlement policy", e);
        }
    }

    private List<PolicyMigrationDTO> combinePAPAndPDPToPolicyDTOs(List<PolicyDTO> xacmlPAPPolicies,
                                                                  List<PolicyStoreDTO> xacmlPDPPolicies) {

        List<PolicyMigrationDTO> xacmlPolicies = new ArrayList<>();
        Set<String> matchedPapKeys = new HashSet<>();

        // Process PDP policies
        for (PolicyStoreDTO pdpPolicy : xacmlPDPPolicies) {
            String pdpKey = pdpPolicy.getPolicyId() + ":" + pdpPolicy.getVersion();
            boolean matched = false;

            for (PolicyDTO papPolicy : xacmlPAPPolicies) {
                String papKey = papPolicy.getPolicyId() + ":" + papPolicy.getVersion();
                if (pdpKey.equals(papKey)) {
                    // Update PAP policy with PDP details
                    papPolicy.setPolicyOrder(pdpPolicy.getPolicyOrder());
                    papPolicy.setActive(pdpPolicy.isActive());
                    papPolicy.setPromote(true);

                    PolicyMigrationDTO policyMigrationDTO = new PolicyMigrationDTO(papPolicy, true);
                    xacmlPolicies.add(policyMigrationDTO);
                    matchedPapKeys.add(papKey);
                    matched = true;
                    break;
                }
            }

            if (!matched) {
                // Add dummy policy if policy exists in PDP only, but not in PAP.
                PolicyDTO newPolicy = new PolicyDTO();
                newPolicy.setPolicyId(pdpPolicy.getPolicyId());
                newPolicy.setPolicy(pdpPolicy.getPolicy());
                newPolicy.setVersion(pdpPolicy.getVersion());
                newPolicy.setActive(pdpPolicy.isActive());
                newPolicy.setPromote(true);
                newPolicy.setPolicyType("Dummy");
                newPolicy.setLastModifiedTime(String.valueOf(new Timestamp(System.currentTimeMillis())));
                newPolicy.setPolicyOrder(pdpPolicy.getPolicyOrder());
                newPolicy.setAttributeDTOs(pdpPolicy.getAttributeDTOs());

                PolicyMigrationDTO policyMigrationDTO = new PolicyMigrationDTO(newPolicy, false);
                xacmlPolicies.add(policyMigrationDTO);
            }
        }

        // Add unpublished PAP policies (not in PDP)
        for (PolicyDTO papPolicy : xacmlPAPPolicies) {
            String papKey = papPolicy.getPolicyId() + ":" + papPolicy.getVersion();
            if (!matchedPapKeys.contains(papKey)) {
                papPolicy.setPromote(false);
                PolicyMigrationDTO policyMigrationDTO = new PolicyMigrationDTO(papPolicy, true);
                xacmlPolicies.add(policyMigrationDTO);
            }
        }
        return xacmlPolicies;
    }

    private Registry getRegistry(int tenantId) throws MigrationClientException {

        try {
            return IdentityTenantUtil.getRegistryService().getGovernanceSystemRegistry(tenantId);
        } catch (RegistryException e) {
            String msg = String.format("Error while getting registry for tenant %d.", tenantId);
            throw new MigrationClientException(msg, e);
        }
    }
}
