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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.identity.application.common.model.Scope;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
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.OAuth2ScopeDAO;
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.Application;
import org.wso2.carbon.is.migration.util.Utility;
import org.wso2.carbon.user.api.Tenant;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;


public class OAuth2ScopeMigrationService {

    private static final Logger LOG = LoggerFactory.getLogger(OAuth2ScopeMigrationService.class);
    private static final APIResourceDAO apiResourceDAO = new APIResourceDAO();
    private static final ScopeDAO scopeDAO = new ScopeDAO();
    private static final OAuth2ScopeDAO oAuth2ScopeDAO = new OAuth2ScopeDAO();
    private static final ApplicationDAO applicationDAO = new ApplicationDAO();
    private static final RoleV2DAO roleV2DAO = new RoleV2DAO();
    public static final String DEFAULT_OAUTH_2_RESOURCE = "User-defined-oauth2-resource";

    public void execute() throws MigrationClientException {

        LOG.info("......... Started OAuth2 Scopes Migration .........");
        try {
            // Migrate OAuth2 scopes in super tenant.
            if (apiResourceDAO.getAPIResourceIDByIdentifier(DEFAULT_OAUTH_2_RESOURCE,
                    MultitenantConstants.SUPER_TENANT_ID) == null) {
                // Register the oauth2 scopes under a single API resource.
                migrateOAuth2Scopes(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
                // Authorize the API resource to all the applications.
                authorizeToApplications(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
                // Update roles with permissions.
                updateRoles(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
            }
        } catch (Exception e) {
            LOG.error("Error while migrating OAuth2 scopes, hence rolling back OAuth2 scope migration for tenant: "
                    + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, e);
            deleteAPIResourceWithAuthorizations(MultitenantConstants.SUPER_TENANT_ID);
        }

        // Migrate OAuth2 scopes.
        Set<Tenant> tenants = Utility.getTenants();
        for (Tenant tenant : tenants) {
            try {
                if (apiResourceDAO.getAPIResourceIDByIdentifier(DEFAULT_OAUTH_2_RESOURCE, tenant.getId()) == null) {
                    // Register the oauth2 scopes under a single API resource.
                    migrateOAuth2Scopes(tenant.getDomain());
                    // Authorize the API resource to all the applications.
                    authorizeToApplications(tenant.getDomain());
                    // Update roles with permissions.
                    updateRoles(tenant.getDomain());
                }
            } catch (Exception e) {
                LOG.error("Error while migrating OAuth2 scopes, hence rolling back OAuth2 scope migration for tenant: "
                        + tenant.getDomain(), e);
                deleteAPIResourceWithAuthorizations(tenant.getId());
            }
        }
    }

    /**
     * Migrate OAuth2 scopes.
     *
     * @param tenantDomain Tenant domain.
     * @throws MigrationClientException If an error occurs while migrating OAuth2 scopes.
     */
    private void migrateOAuth2Scopes(String tenantDomain) throws MigrationClientException {

        LOG.info("Started migrating OAuth2 scopes in an API resource for tenant: " + tenantDomain);
        int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
        try {
            // Get OAuth2 scopes in the tenant.
            List<Scope> oauth2Scopes = oAuth2ScopeDAO.getOAuth2Scopes(tenantId);
            if (oauth2Scopes.isEmpty()) {
                LOG.info("No OAuth2 scopes found for tenant: " + tenantDomain + ", hence skipping OAuth2 scope " +
                        "migration for the tenant.");
                return;
            }
            // Create default OAuth2 resource.
            String apiId = UUID.randomUUID().toString();
            apiResourceDAO.createAPIResource(apiId, "User-defined OAuth2 Resource", DEFAULT_OAUTH_2_RESOURCE, tenantId
                    , "This is Default OAuth2 Resource Representation", "BUSINESS", true);
            scopeDAO.insertScopes(apiId, oauth2Scopes, tenantId);
            LOG.info("Completed migrating OAuth2 scopes for tenant: " + tenantDomain);
        } catch (Throwable e) {
            LOG.error("Error while migrating OAuth2 scopes, hence rolling back OAuth2 scope migration for tenant: "
                    + tenantDomain, e);
            apiResourceDAO.deleteAPIResourceByIdentifier(DEFAULT_OAUTH_2_RESOURCE, tenantId);
        }
    }

    private void authorizeToApplications(String tenantDomain) throws MigrationClientException {

        LOG.info("Started authorizing OAuth2 scopes for applications in tenant: " + tenantDomain);
        int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
        try {
            String apiId = apiResourceDAO.getAPIResourceIDByIdentifier(DEFAULT_OAUTH_2_RESOURCE, tenantId);
            if (apiId != null) {
                List<Application> applications = applicationDAO.getApplicationsExceptSystemApps(tenantId);
                for (Application application : applications) {
                    applicationDAO.authorizeAPIToApplication(application.getUUID(), apiId, "RBAC");
                    applicationDAO.authorizeScopesToApplication(application.getUUID(), apiId,
                            apiResourceDAO.getScopeIDsByAPIId(apiId));
                }
            } else {
                LOG.info("OAuth2 API resource not found in tenant: " + tenantDomain);
            }
        } catch (MigrationClientException e) {
            LOG.error("Error while authorizing OAuth2 scopes for applications, hence rolling back OAuth2 scope" +
                    " migration in tenant: " + tenantDomain, e);
            applicationDAO.deleteAuthorizedAPI(apiResourceDAO.getAPIResourceIDByIdentifier(DEFAULT_OAUTH_2_RESOURCE,
                    tenantId));
            applicationDAO.deleteAuthorizedScope(apiResourceDAO.getAPIResourceIDByIdentifier(DEFAULT_OAUTH_2_RESOURCE,
                    tenantId));
        }
    }

    private void deleteAPIResourceWithAuthorizations(int tenantId) {

        try {
            String apiId = apiResourceDAO.getAPIResourceIDByIdentifier(DEFAULT_OAUTH_2_RESOURCE, tenantId);
            if (apiId != null) {
                applicationDAO.deleteAuthorizedScope(apiId);
                applicationDAO.deleteAuthorizedAPI(apiId);
                apiResourceDAO.deleteAPIResourceByIdentifier(DEFAULT_OAUTH_2_RESOURCE, tenantId);
            }
        } catch (MigrationClientException e) {
            LOG.error("Error while deleting API resource: " + DEFAULT_OAUTH_2_RESOURCE + " for tenant: " + tenantId, e);
        }
    }

    private void updateRoles(String tenantDomain) {

        int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
        try {
            // Get V2 roles in the tenant.
            List<String> roles = roleV2DAO.getV2Roles(tenantId);
            // Get scope bindings of the OAuth2 scopes.
            Map<String, List<String>> scopeBindings = oAuth2ScopeDAO.getScopeBindings(tenantId);

            for (String role : roles) {
                // If there's a scope binding for the role, assign the permission to the role.
                if (scopeBindings.containsKey(role)) {
                    String roleUUID = roleV2DAO.getRoleUUIDByName(role, tenantId);
                    roleV2DAO.assignPermissionToRole(roleUUID, scopeBindings.get(role), tenantId);
                }
            }
        } catch (MigrationClientException e) {
            LOG.error("Error while migrating SCIM role v1 to role v2.", e);
        }
    }
}
