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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.organization.management.service.model.Organization;
import org.wso2.carbon.is.migration.service.v700.dao.ApplicationDAO;
import org.wso2.carbon.is.migration.service.v700.dao.OrganizationDAO;
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.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.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.wso2.carbon.is.migration.service.v700.constant.MigratorConstants.ORGANIZATION;
import static org.wso2.carbon.is.migration.service.v700.util.Utils.getUMDataSource;

/**
 * Migrator implementation for migrating SCIM role v1 to role v2.
 */
public class RoleV2MigrationService {

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

    private static final RoleV2DAO roleV2DAO = new RoleV2DAO();
    private static final RoleV1DAO roleV1DAO = new RoleV1DAO();
    private static final OrganizationDAO organizationDAO = new OrganizationDAO();
    private static final ApplicationDAO applicationDAO = new ApplicationDAO();

    private static final String AUDIENCE_FOR_ASSOCIATED_ROLES = "allowedAudienceForAssociatedRoles";

    public void execute() throws MigrationClientException {

        LOG.info("Started migrating SCIM role v1 to role v2.");
        // Add hybrid role audience reference to the database per each organization.
        addAudienceRef();

        try (Connection connection = getUMDataSource().getConnection()) {
            connection.setAutoCommit(false);
            // Migrate super tenant roles to V2.
            migrateRoleV2(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, connection);

            Set<Tenant> tenants = Utility.getTenants();
            for (Tenant tenant : tenants) {
                migrateRoleV2(tenant.getDomain(), connection);
            }
            connection.commit();
        } catch (SQLException e) {
            throw new MigrationClientException("Error while retrieving connection.", e);
        }
    }

    /**
     * Add hybrid role audience reference to the database per each organization.
     *
     * @throws MigrationClientException if error occurred while adding hybrid role audience reference.
     */
    public void addAudienceRef() throws MigrationClientException {

        try {
            Connection connection = getUMDataSource().getConnection();
            Set<Organization> organizations = organizationDAO.getOrganizations(connection);
            for (Organization organization : organizations) {
                // Check whether hybrid role audience reference is already added.
                if (!roleV2DAO.isRoleAudienceExist(ORGANIZATION, organization.getId())) {
                    roleV2DAO.createRoleAudience(ORGANIZATION, organization.getId());
                }
            }
        } catch (MigrationClientException e) {
            throw new MigrationClientException("Error while migrating SCIM role v1 to role v2.", e);
        } catch (SQLException e) {
            throw new MigrationClientException("Error while retrieving connection.", e);
        }
    }

    public void migrateRoleV2(String tenantDomain, Connection connection) throws MigrationClientException {

        try {
            int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
            String organizationID = organizationDAO.getOrganizationIdByName(connection, tenantDomain);
            List<String> roles = roleV1DAO.getV1Roles(connection, tenantId);
            for (String role : roles) {
                int roleAudRefId = roleV2DAO.getRoleAudienceByAudienceID(organizationID);
                String uuid = roleV2DAO.getSCIM2GroupId(role, tenantId, roleAudRefId);
                if (uuid == null) {
                    // If SCIM2 group id is not found, generate a new one and populate SCIM2 group id.
                    uuid = UUID.randomUUID().toString();
                    roleV2DAO.insertSCIM2GroupId(tenantId, role, uuid, roleAudRefId);
                }
                roleV2DAO.updateHybridRole(connection, roleAudRefId, uuid, role, tenantId);
                assignRoleToApplications(uuid, tenantDomain);
            }
        } catch (MigrationClientException e) {
            throw new MigrationClientException("Error while migrating SCIM role v1 to role v2.", e);
        }
    }

    private void assignRoleToApplications(String roleId, String tenantDomain) throws MigrationClientException {

        int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
        int spId = -1;
        try {
            List<Application> applications = applicationDAO.getApplicationsExceptSystemApps(tenantId);
            applications = applications.stream()
                    .filter(application -> !"client_credentials".equals(application.getAllowedGrantTypes()))
                    .collect(Collectors.toList());
            for (Application application : applications) {
                spId = application.getAppID();
                // Update application role audience to organization.
                if (applicationDAO.getApplicationProperty(spId, AUDIENCE_FOR_ASSOCIATED_ROLES) == null) {
                    applicationDAO.insertApplicationProperty(spId, tenantId, AUDIENCE_FOR_ASSOCIATED_ROLES, ORGANIZATION, null);
                }
            }
        } catch (MigrationClientException e) {
            LOG.error("Error while assigning role to application", e);
            if (spId != -1) {
                applicationDAO.deleteApplicationAudience(spId);
                applicationDAO.deleteRoleFromApplication(roleId);
            }
        }
    }
}
