/*
 * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). All Rights Reserved.
 *
 * This software is the property of WSO2 LLC. and its suppliers, if any.
 * Dissemination of any information or reproduction of any material contained
 * herein in any form is strictly forbidden, unless permitted by WSO2 expressly.
 * You may not alter or remove any copyright or other notice from copies of this content.
 */

package org.wso2.carbon.is.migration.service.v700.migrator;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.identity.api.resource.collection.mgt.model.APIResourceCollection;
import org.wso2.carbon.identity.api.resource.collection.mgt.util.APIResourceCollectionMgtConfigBuilder;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.is.migration.internal.ISMigrationServiceDataHolder;
import org.wso2.carbon.is.migration.service.Migrator;
import org.wso2.carbon.is.migration.service.v700.dao.APIResourceDAO;
import org.wso2.carbon.is.migration.service.v700.dao.ApplicationDAO;
import org.wso2.carbon.is.migration.service.v700.dao.RoleV1DAO;
import org.wso2.carbon.is.migration.service.v700.dao.RoleV2DAO;
import org.wso2.carbon.is.migration.service.v700.dao.ScopeDAO;
import org.wso2.carbon.is.migration.service.v700.model.RoleV2;
import org.wso2.carbon.is.migration.service.v700.util.Utils;
import org.wso2.carbon.is.migration.util.Constant;
import org.wso2.carbon.is.migration.util.ReportUtil;
import org.wso2.carbon.is.migration.util.Utility;
import org.wso2.carbon.user.api.Tenant;
import org.wso2.carbon.user.api.UserStoreException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

import static org.wso2.carbon.is.migration.service.v700.constant.MigratorConstants.ADMINISTRATOR;
import static org.wso2.carbon.is.migration.service.v700.constant.MigratorConstants.ADMIN_PERMISSION;
import static org.wso2.carbon.is.migration.service.v700.constant.MigratorConstants.APPLICATION;
import static org.wso2.carbon.is.migration.util.Constant.REPORT_PATH;

public class ConsoleRoleMigrator extends Migrator {

    private static final Logger LOG = LoggerFactory.getLogger(ConsoleRoleMigrator.class);

    private final ApplicationDAO applicationDAO = new ApplicationDAO();
    private final RoleV2DAO roleV2DAO = new RoleV2DAO();
    private final RoleV1DAO roleV1DAO = new RoleV1DAO();
    private final ScopeDAO scopeDAO = new ScopeDAO();


    @Override
    public void dryRun() throws MigrationClientException {

        try {
            LOG.info(Constant.MIGRATION_LOG + "Executing dry run for {}", this.getClass().getName());
            Properties migrationProperties = getMigratorConfig().getParameters();
            String reportPath = (String) migrationProperties.get(REPORT_PATH);
            ReportUtil reportUtil = new ReportUtil(reportPath);
            reportUtil.writeMessage("\n--- Summary of the report ---\n");
            LOG.info(Constant.MIGRATION_LOG + "Started the dry run for {} migration.",
                    this.getClass().getName());

            reportUtil.writeMessage("Checking for a role with name Administrator.");
            LOG.info(Constant.MIGRATION_LOG + "Checking for a role with name Administrator.");


            Set<Tenant> tenants = Utility.getTenants();
            Tenant superTenant = new Tenant();
            superTenant.setId(-1234);
            tenants.add(superTenant);
            boolean conflictingRoleExist = false;
            for (Tenant tenant : tenants) {
                if (roleV1DAO.getRoleIDByName(ADMINISTRATOR, tenant.getId()) != null) {
                    List<String> permissionsList = roleV1DAO.getRolePermissions(ADMINISTRATOR, tenant.getId());
                    if (permissionsList.contains("/permission/admin")) {
                        continue;
                    }
                    conflictingRoleExist = true;
                    reportUtil.writeMessage("A role with name 'Administrator' with less permissions exist in the tenant " + tenant.getId());
                }
            }
            if (conflictingRoleExist) {
                reportUtil.writeMessage("The role with name Administrator will be used as the default admin role in migrated tenants.");
                reportUtil.writeMessage("Take the following actions to resolve the issues");
                reportUtil.writeMessage("The users who were in the Administrator role in the previous version will get full " +
                        "admin access after the migration.");
            }
            reportUtil.commit();
        } catch (IOException e) {
            throw new MigrationClientException("Error while writing the dry run report.", e);
        }
    }

    @Override
    public void migrate() throws MigrationClientException {

        Set<Tenant> tenants = Utility.getTenants();
        Tenant superTenant = new Tenant();
        superTenant.setId(-1234);
        tenants.add(superTenant);
        Map<String, String> scopeBindingMap = Utils.loadOauthScopeBinding();
        if (scopeBindingMap == null) {
            throw new MigrationClientException("Error while loading scope binding file.");
        }

        for (Tenant tenant : tenants) {
            String appUUID = applicationDAO.getAppUUIDFromAppName(FrameworkConstants.Application.CONSOLE_APP,
                    tenant.getId());
            List<String> existingSystemAPIAccessRoleList = roleV2DAO.getSystemAPIAccessibleRoles(tenant.getId());
            List<String> systemAPIAccessRoleList = new ArrayList<>();
            existingSystemAPIAccessRoleList.forEach(role -> {
                try {
                    if (roleV1DAO.getRolePermissions(role, tenant.getId()).stream()
                            .anyMatch(ADMIN_PERMISSION::equals)) {
                        systemAPIAccessRoleList.add(role);
                    }
                } catch (MigrationClientException e) {
                    LOG.error("Error while retrieving permissions of role: " + role, e);
                }
            });
            boolean administratorRoleExist = existingSystemAPIAccessRoleList.contains(ADMINISTRATOR);
            String adminRole;
            try {
                adminRole = ISMigrationServiceDataHolder.getRealmService().getTenantUserRealm(tenant.getId())
                        .getRealmConfiguration().getAdminRoleName().replace("Internal/", "");
            } catch (UserStoreException e) {
                throw new MigrationClientException("Error while retrieving default admin role of super tenant.", e);
            }
            if (!systemAPIAccessRoleList.contains(ADMINISTRATOR)) {
                systemAPIAccessRoleList.add(ADMINISTRATOR);
            }
            List<String> existingConsoleAppRoles = roleV2DAO.getApplicationRolesOfApp(tenant.getId(), appUUID)
                    .stream().map(RoleV2::getRoleName).collect(Collectors.toList());
            systemAPIAccessRoleList.removeAll(existingConsoleAppRoles);
            for (String role: systemAPIAccessRoleList) {
                roleV2DAO.createV2Role(role, APPLICATION, tenant.getId(), appUUID);
                String roleUUID = roleV2DAO.getGetRoleUUID(tenant.getId(), role, appUUID, APPLICATION);

                int roleAudienceRefID = roleV2DAO.getRoleAudienceByAudienceID(appUUID);
                roleV2DAO.insertSCIM2GroupId(tenant.getId(), role, roleUUID, roleAudienceRefID);

                applicationDAO.assignRoleToApplication(appUUID, roleUUID);
                int newRoleID = roleV2DAO.getRoleID(tenant.getId(), role, appUUID, APPLICATION);
                if (ADMINISTRATOR.equals(role) && !administratorRoleExist) {
                    roleV2DAO.migrateUserOfRoleToNewRole(newRoleID, tenant.getId(), adminRole);
                    if (roleV2DAO.isGroupExist(adminRole, tenant.getId())) {
                        roleV2DAO.assignRoleToGroupOfPrimaryGroup(adminRole, newRoleID, tenant.getId());
                    }
                } else {
                    roleV2DAO.migrateUserOfRoleToNewRole(newRoleID, tenant.getId(), role);
                }

                if (role.startsWith("system_primary_")) {
                    String groupName = role.split("system_primary_")[1];
                    if (!roleV2DAO.isGroupToRoleMapped(groupName, newRoleID, tenant.getId())) {
                        roleV2DAO.assignRoleToGroupOfPrimaryGroup(groupName, newRoleID, tenant.getId());
                    }
                }
            }

            // Assign scopes for console application related roles.
            String consoleAppUUID = applicationDAO.getAppUUIDFromAppName(FrameworkConstants.Application.CONSOLE_APP, tenant.getId());
            List<RoleV2> consoleAppRoles = roleV2DAO.getApplicationRolesOfApp(tenant.getId(), consoleAppUUID);
            for (RoleV2 roleV2 : consoleAppRoles) {
                // The scopes for the Administrator role is provided at runtime.
                if (ADMINISTRATOR.equals(roleV2.getRoleName())) {
                    continue;
                }
                if (roleV2.getRoleName().equals(adminRole)) {
                    roleV2DAO.assignInternalSystemScopesToRole(roleV2.getRoleUUID(), scopeDAO.getAllSystemScopes());
                    continue;
                }
                List<String> permissionsList = roleV1DAO.getRolePermissions(roleV2.getRoleName(), tenant.getId());
                if (permissionsList.size() == 0) {
                    continue;
                }
                List<String> scopesList = new ArrayList<>();
                for (Map.Entry<String, String> entry : scopeBindingMap.entrySet()) {
                    for (String permissionString : permissionsList) {
                        if (StringUtils.containsIgnoreCase(entry.getKey(), permissionString)) {
                            if (!scopesList.contains(entry.getValue())) {
                                scopesList.add(entry.getValue());
                            }
                        }
                    }
                }
                if (scopesList.isEmpty()) {
                    continue;
                }

                Map<String, APIResourceCollection> apiResourceCollectionMap = APIResourceCollectionMgtConfigBuilder.getInstance().getApiResourceCollectionMgtConfigurations();
                for (Map.Entry<String, APIResourceCollection> entry : apiResourceCollectionMap.entrySet()) {
                    String consoleGroupedScope = entry.getValue().getReadScopes().get(0);
                    List<String> readOnlyScopes = entry.getValue().getReadScopes().stream().skip(1)
                            .collect(Collectors.toList());
                    if (new HashSet<>(scopesList).containsAll(readOnlyScopes)) {
                        scopesList.add(consoleGroupedScope);
                    }
                }
                List<String> authorizedScopes = roleV2DAO.getAssignedScopesForRole(roleV2.getRoleUUID());
                List<String> scopesRequiredToRegister = scopesList.stream()
                        .filter(scope -> !authorizedScopes.contains(scope)).collect(Collectors.toList());
                if (scopesRequiredToRegister.isEmpty()) {
                    continue;
                }
                roleV2DAO.assignInternalSystemScopesToRole(roleV2.getRoleUUID(), scopesRequiredToRegister);
            }
        }
    }
}
