/*
 * 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.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.database.utils.jdbc.NamedJdbcTemplate;
import org.wso2.carbon.database.utils.jdbc.exceptions.TransactionException;
import org.wso2.carbon.identity.api.resource.mgt.constant.SQLConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.*;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementServiceImpl;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.oauth.IdentityOAuthAdminException;
import org.wso2.carbon.identity.oauth.OAuthUtil;
import org.wso2.carbon.identity.oauth.dto.OAuthConsumerAppDTO;
import org.wso2.carbon.is.migration.internal.ISMigrationServiceDataHolder;
import org.wso2.carbon.is.migration.service.v700.constant.MigratorConstants;
import org.wso2.carbon.is.migration.service.v700.dao.ApplicationDAO;
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.registry.core.exceptions.RegistryException;
import org.wso2.carbon.user.api.Tenant;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.UserRealm;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.AUTHORIZATION_CODE;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN;
import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.APPLICATION;
import static org.wso2.carbon.is.migration.service.v700.constant.MigratorConstants.*;
import static org.wso2.carbon.is.migration.util.TenantPortalMigratorUtil.registerAndRetrieveOAuthApplicationData;
import static org.wso2.carbon.utils.multitenancy.MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;

/**
 * Migrator implementation for creating Console and MyAccount apps in each tenant.
 */
public class CreateSystemAppsInTenantService {

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

    private final ApplicationDAO applicationDAO = new ApplicationDAO();
    private static final String CONSOLE_CALLBACK_URL = "regexp=(<PROTOCOL>://<HOSTNAME>:<PORT>/t/(.*)/console|<PROTOCOL>://<HOSTNAME>:<PORT>/t/(.*)/o/(.*)/console)";
    private static final String MYACCOUNT_CALLBACK_URL = "regexp=(<PROTOCOL>://<HOSTNAME>:<PORT>/t/(.*)/myaccount|<PROTOCOL>://<HOSTNAME>:<PORT>/t/(.*)/o/(.*)/myaccount)";

    private static final String CLONE_SUPER_TENANT_SERVICE_PROVIDER = "INSERT INTO SP_APP " +
            "(UUID, TENANT_ID, APP_NAME, USER_STORE, USERNAME, DESCRIPTION, AUTH_TYPE, IS_LOCAL_CLAIM_DIALECT, " +
            "IS_USE_TENANT_DOMAIN_SUBJECT) SELECT ?, ?, APP_NAME, USER_STORE, ?, DESCRIPTION, AUTH_TYPE, " +
            "IS_LOCAL_CLAIM_DIALECT, IS_USE_TENANT_DOMAIN_SUBJECT FROM SP_APP WHERE APP_NAME = ? AND TENANT_ID = -1234";
    private static final String CLONE_SP_METADATA = "INSERT INTO SP_METADATA (SP_ID, TENANT_ID, NAME, " +
            "DISPLAY_NAME, VALUE) SELECT ?, ?, NAME, DISPLAY_NAME, VALUE FROM SP_METADATA WHERE SP_ID = ?";
    private static final String CLONE_SP_METADATA_H2 = "INSERT INTO SP_METADATA (SP_ID, TENANT_ID, NAME, " +
            "DISPLAY_NAME, `VALUE`) SELECT ?, ?, NAME, DISPLAY_NAME, `VALUE` FROM SP_METADATA WHERE SP_ID = ?";
    private static final String CREATE_OAUTH_CONSUMER_APP = "INSERT INTO IDN_OAUTH_CONSUMER_APPS " +
            "(TENANT_ID, CALLBACK_URL, CONSUMER_KEY, CONSUMER_SECRET, USERNAME, USER_DOMAIN, APP_NAME, OAUTH_VERSION, " +
            "GRANT_TYPES, PKCE_MANDATORY, APP_STATE, USER_ACCESS_TOKEN_EXPIRE_TIME, APP_ACCESS_TOKEN_EXPIRE_TIME, " +
            "REFRESH_TOKEN_EXPIRE_TIME, ID_TOKEN_EXPIRE_TIME) SELECT ?, ?, CONSUMER_KEY, ?, USERNAME, USER_DOMAIN, " +
            "APP_NAME, OAUTH_VERSION, GRANT_TYPES, PKCE_MANDATORY, APP_STATE, USER_ACCESS_TOKEN_EXPIRE_TIME, " +
            "APP_ACCESS_TOKEN_EXPIRE_TIME, REFRESH_TOKEN_EXPIRE_TIME, ID_TOKEN_EXPIRE_TIME " +
            "FROM IDN_OAUTH_CONSUMER_APPS WHERE APP_NAME = ? AND TENANT_ID = -1234";

    private static final String SET_UP_SP_INBOUND_OAUTH = "INSERT INTO SP_INBOUND_AUTH " +
            "(APP_ID, TENANT_ID, INBOUND_AUTH_KEY, INBOUND_AUTH_TYPE, INBOUND_CONFIG_TYPE, PROP_NAME, PROP_VALUE) " +
            "SELECT ?, ?, INBOUND_AUTH_KEY, INBOUND_AUTH_TYPE, INBOUND_CONFIG_TYPE, PROP_NAME, PROP_VALUE FROM " +
            "SP_INBOUND_AUTH WHERE APP_ID = ?";

    private static final String ADD_SP_CLAIM_MAPPINGS = "INSERT INTO SP_CLAIM_MAPPING " +
            "(APP_ID, TENANT_ID, IDP_CLAIM, SP_CLAIM, IS_REQUESTED, IS_MANDATORY) SELECT ?, ?, IDP_CLAIM, SP_CLAIM, " +
            "IS_REQUESTED, IS_MANDATORY FROM SP_CLAIM_MAPPING WHERE APP_ID = ?";

    private static final String ADD_SP_OIDC_PROPERTY = "INSERT INTO IDN_OIDC_PROPERTY " +
            "(TENANT_ID, CONSUMER_KEY, PROPERTY_KEY, PROPERTY_VALUE) SELECT ?, ?, PROPERTY_KEY, PROPERTY_VALUE " +
            "FROM IDN_OIDC_PROPERTY WHERE TENANT_ID = -1234 AND CONSUMER_KEY = ?";

    public void dryRun(ReportUtil reportUtil) throws MigrationClientException {

        reportUtil.writeMessage("Checking for custom applications with system applications name which are created in tenants.");
        LOG.info(Constant.MIGRATION_LOG + "Checking for custom applications with system applications name which are created in tenants. ");

        List<String> customConsoleExistTenants = applicationDAO.customSystemAppExistTenants("CONSOLE",
                FrameworkConstants.Application.CONSOLE_APP);
        if (!customConsoleExistTenants.isEmpty()) {
            reportUtil.writeMessage("Tenants with custom Console app: " + StringUtils.join(customConsoleExistTenants, " , "));
            LOG.info(Constant.MIGRATION_LOG + "Tenants with custom Console app: " + StringUtils.join(customConsoleExistTenants, " , "));
        }

        List<String> customMyAccountExistTenants = applicationDAO.customSystemAppExistTenants("MY_ACCOUNT",
                FrameworkConstants.Application.MY_ACCOUNT_APP);
        if (!customMyAccountExistTenants.isEmpty()) {
            reportUtil.writeMessage("Tenants with custom MyAccount app: " + StringUtils.join(customMyAccountExistTenants, " , "));
            LOG.info(Constant.MIGRATION_LOG + "Tenants with custom MyAccount app: " + StringUtils.join(customMyAccountExistTenants, " , "));
        }
    }

    public void execute() throws MigrationClientException {

        List<Tenant> tenantList;
        try {
            Tenant[] tenants = ISMigrationServiceDataHolder.getRealmService().getTenantManager().getAllTenants();
            tenantList = Arrays.stream(tenants).collect(Collectors.toList());
        } catch (UserStoreException e) {
            throw new MigrationClientException("Error while retrieving tenants.", e);
        }

        // Read oAuth Config Elements.
        Utils.parseDefaultValidityPeriods();

        int consoleAppID = applicationDAO.getSPIdFromAppName(SUPER_TENANT_ID, FrameworkConstants.Application.CONSOLE_APP);
        int myAccountAppID = applicationDAO.getSPIdFromAppName(SUPER_TENANT_ID, FrameworkConstants.Application.MY_ACCOUNT_APP);

        if (consoleAppID == -1) {
            createConsoleApplication();
            consoleAppID = applicationDAO.getSPIdFromAppName(SUPER_TENANT_ID, FrameworkConstants.Application.CONSOLE_APP);
        }

        if (myAccountAppID == -1) {
            createMyAccountApplication();
            myAccountAppID = applicationDAO.getSPIdFromAppName(SUPER_TENANT_ID, FrameworkConstants.Application.MY_ACCOUNT_APP);
        }

        // Enable organization_switch grant for the console app.
        String allowedGrants = applicationDAO.getAllowedGrantTypesOfApp(SUPER_TENANT_ID, FrameworkConstants.Application.CONSOLE_APP);
        if (!allowedGrants.contains("organization_switch")) {
            allowedGrants += " organization_switch";
            applicationDAO.updateAllowedGrantTypesOfApp(SUPER_TENANT_ID, FrameworkConstants.Application.CONSOLE_APP, allowedGrants);
        }

        // Set application role audience for the console app.
        if (applicationDAO.getApplicationProperty(consoleAppID, AUDIENCE_FOR_ASSOCIATED_ROLES) == null) {
            applicationDAO.insertApplicationProperty(consoleAppID, SUPER_TENANT_ID, AUDIENCE_FOR_ASSOCIATED_ROLES, APPLICATION, null);
        }

        // Set share with all organizations property as true.
        if (applicationDAO.getApplicationProperty(consoleAppID, SHARE_WITH_ALL_CHILDREN) == null) {
            applicationDAO.insertApplicationProperty(consoleAppID, SUPER_TENANT_ID, SHARE_WITH_ALL_CHILDREN, "true", null);
        }
        // Set share with all organizations property as true.
        if (applicationDAO.getApplicationProperty(myAccountAppID, SHARE_WITH_ALL_CHILDREN) == null) {
            applicationDAO.insertApplicationProperty(myAccountAppID, SUPER_TENANT_ID, SHARE_WITH_ALL_CHILDREN, "true", null);
        }

        if (tenantList.isEmpty()) {
            return;
        }
        for (Tenant tenant : tenantList) {
            String appName = FrameworkConstants.Application.CONSOLE_APP;
            if (!applicationDAO.isApplicationExist(appName, tenant.getId())) {
                createSystemAppClonedFromSuperTenant(tenant.getId(), appName, "CONSOLE", CONSOLE_CALLBACK_URL);
            }

            appName = FrameworkConstants.Application.MY_ACCOUNT_APP;
            if (!applicationDAO.isApplicationExist(appName, tenant.getId())) {
                createSystemAppClonedFromSuperTenant(tenant.getId(), appName, "MY_ACCOUNT", MYACCOUNT_CALLBACK_URL);
            }
        }
    }

    private void createSystemAppClonedFromSuperTenant(int tenantId, String appName, String clientID, String callbackURL)
            throws MigrationClientException {

        String tenantAdminUser;
        try {
            tenantAdminUser = ISMigrationServiceDataHolder.getRealmService().getTenantUserRealm(tenantId)
                    .getRealmConfiguration().getAdminUserName();
        } catch (UserStoreException e) {
            throw new MigrationClientException("Error while retrieving the tenant admin username.", e);
        }

        NamedJdbcTemplate namedJdbcTemplate = getNewTemplateForIdentityDB();
        try {
            namedJdbcTemplate.withTransaction(template -> {
                template.executeInsert(CREATE_OAUTH_CONSUMER_APP,
                        preparedStatement -> {
                            preparedStatement.setInt(1, tenantId);
                            preparedStatement.setString(2, callbackURL);
                            preparedStatement.setString(3, RandomStringUtils.randomAlphanumeric(28));
                            preparedStatement.setString(4, appName);
                        }, null, false);
                createServiceProvider(tenantId, appName, tenantAdminUser);
                int tenantAppID = applicationDAO.getSPIdFromAppName(tenantId, appName);
                int superTenantAppID = applicationDAO.getSPIdFromAppName(-1234, appName);
                createServiceProviderMetaData(tenantAppID, tenantId, superTenantAppID);
                setupServiceProviderInboundAuthConfigs(tenantAppID, tenantId, superTenantAppID);
                addSPClaimMappings(tenantAppID, tenantId, superTenantAppID);
                return null;
            });
        } catch (TransactionException e) {
            throw new MigrationClientException("Error while creating OAuth app for the application.", e);
        }
        addSpOIDCProperties(tenantId, clientID);
    }

    private void createServiceProvider(int tenantId, String appName, String appOwner) throws MigrationClientException {

        try (Connection connection = IdentityDatabaseUtil.getDBConnection(true)) {
            try (PreparedStatement preparedStatement = connection.prepareStatement(CLONE_SUPER_TENANT_SERVICE_PROVIDER)) {
                preparedStatement.setString(1, UUID.randomUUID().toString());
                preparedStatement.setInt(2, tenantId);
                preparedStatement.setString(3, appOwner);
                preparedStatement.setString(4, appName);
                preparedStatement.execute();
            }
        } catch (SQLException e) {
            throw new MigrationClientException("Error while retrieving OAuth2 scopes.", e);
        }
    }

    private void createServiceProviderMetaData(int spID, int tenantId, int superSPID) throws MigrationClientException {

        try (Connection connection = IdentityDatabaseUtil.getDBConnection(true)) {
            String databaseName = connection.getMetaData().getDatabaseProductName();
            String sql =  databaseName.contains(SQLConstants.H2) ? CLONE_SP_METADATA_H2 : CLONE_SP_METADATA;
            try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
                preparedStatement.setInt(1, spID);
                preparedStatement.setInt(2, tenantId);
                preparedStatement.setInt(3, superSPID);
                preparedStatement.execute();
            }
        } catch (SQLException e) {
            throw new MigrationClientException("Error while creating service provider metadata clone from super " +
                    "tenant app.", e);
        }
    }

    private void setupServiceProviderInboundAuthConfigs(int spID, int tenantId, int superSPID)
            throws MigrationClientException {

        try (Connection connection = IdentityDatabaseUtil.getDBConnection(true)) {
            try (PreparedStatement preparedStatement = connection.prepareStatement(SET_UP_SP_INBOUND_OAUTH)) {
                preparedStatement.setInt(1, spID);
                preparedStatement.setInt(2, tenantId);
                preparedStatement.setInt(3, superSPID);
                preparedStatement.execute();
            }
        } catch (SQLException e) {
            throw new MigrationClientException("Error while setting the service provider inbound auth configs.", e);
        }
    }

    private void addSPClaimMappings(int spID, int tenantId, int superSPID) throws MigrationClientException {

        try (Connection connection = IdentityDatabaseUtil.getDBConnection(true)) {
            try (PreparedStatement preparedStatement = connection.prepareStatement(ADD_SP_CLAIM_MAPPINGS)) {
                preparedStatement.setInt(1, spID);
                preparedStatement.setInt(2, tenantId);
                preparedStatement.setInt(3, superSPID);
                preparedStatement.execute();
            }
        } catch (SQLException e) {
            throw new MigrationClientException("Error while setting the service provider claim mappings.", e);
        }
    }

    private void addSpOIDCProperties(int tenantID, String clientID) throws MigrationClientException {

        try (Connection connection = IdentityDatabaseUtil.getDBConnection(true)) {
            try (PreparedStatement preparedStatement = connection.prepareStatement(ADD_SP_OIDC_PROPERTY)) {
                preparedStatement.setInt(1, tenantID);
                preparedStatement.setString(2, clientID);
                preparedStatement.setString(3, clientID);
                preparedStatement.execute();
            }
        } catch (SQLException e) {
            throw new MigrationClientException("Error while setting the service provider OIDC properties.", e);
        }
    }

    private NamedJdbcTemplate getNewTemplateForIdentityDB() {

        return new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
    }

    private void createConsoleApplication() throws MigrationClientException {

        ApplicationManagementService applicationMgtService = ApplicationManagementServiceImpl.getInstance();

        String adminUsername = null;
        try {
            UserRealm userRealm = ISMigrationServiceDataHolder.getRegistryService().getUserRealm(SUPER_TENANT_ID);
            adminUsername = userRealm.getRealmConfiguration().getAdminUserName();
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new MigrationClientException("Error while retrieving user realm configuration.", e);
        } catch (RegistryException e) {
            throw new MigrationClientException("Error while retrieving user realm.", e);
        }

        initiatePortal(SUPER_TENANT_DOMAIN, SUPER_TENANT_ID, applicationMgtService, adminUsername,
                MigratorConstants.AppPortal.CONSOLE);
    }

    private void createMyAccountApplication() throws MigrationClientException {

        ApplicationManagementService applicationMgtService = ApplicationManagementServiceImpl.getInstance();

        String adminUsername = null;
        try {
            UserRealm userRealm = ISMigrationServiceDataHolder.getRegistryService().getUserRealm(SUPER_TENANT_ID);
            adminUsername = userRealm.getRealmConfiguration().getAdminUserName();
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new MigrationClientException("Error while retrieving user realm configuration.", e);
        } catch (RegistryException e) {
            throw new MigrationClientException("Error while retrieving user realm.", e);
        }

        initiatePortal(SUPER_TENANT_DOMAIN, SUPER_TENANT_ID, applicationMgtService, adminUsername,
                MigratorConstants.AppPortal.MY_ACCOUNT);
    }

    public void initiatePortal(String tenantDomain, int tenantId, ApplicationManagementService applicationMgtService,
                                String adminUsername, AppPortal appPortal) throws MigrationClientException {

        String consumerSecret = null;
        try {
            consumerSecret = OAuthUtil.getRandomNumber();
        } catch (IdentityOAuthAdminException e) {
            LOG.debug("Error occurred while getting a random number.");
            throw new RuntimeException(e);
        }
        List<String> grantTypes = Arrays.asList(AUTHORIZATION_CODE, REFRESH_TOKEN, GRANT_TYPE_ACCOUNT_SWITCH);
        if (CONSOLE_APP.equals(appPortal.getName())) {
            grantTypes = Arrays.asList(AUTHORIZATION_CODE, REFRESH_TOKEN, GRANT_TYPE_ACCOUNT_SWITCH,
                    GRANT_TYPE_ORGANIZATION_SWITCH);
        }
        List<String> allowedGrantTypes = Utils.getAllowedGrantTypes();
        grantTypes = grantTypes.stream().filter(allowedGrantTypes::contains).collect(Collectors.toList());
        String consumerKey = appPortal.getConsumerKey();
        if (!SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
            consumerKey = consumerKey + "_" + tenantDomain;
        }
        try {
            createOAuth2Application(appPortal.getName(), appPortal.getPath(), consumerKey, consumerSecret,
                    adminUsername, tenantId, tenantDomain, MigratorConstants.COOKIE_BASED_TOKEN_BINDER, grantTypes);
        } catch (IdentityOAuthAdminException e) {
            LOG.debug(e.getMessage());
        }
        createApplication(appPortal.getName(), adminUsername, appPortal.getDescription(), consumerKey, tenantDomain,
                appPortal.getPath(), applicationMgtService);
    }

    public void createOAuth2Application(String applicationName, String portalPath, String consumerKey,
                                        String consumerSecret, String appOwner, int tenantId, String tenantDomain, String bindingType,
                                        List<String> grantTypes)
            throws IdentityOAuthAdminException {

        OAuthConsumerAppDTO oAuthConsumerAppDTO = new OAuthConsumerAppDTO();
        oAuthConsumerAppDTO.setApplicationName(applicationName);
        oAuthConsumerAppDTO.setOAuthVersion(VERSION_2);
        oAuthConsumerAppDTO.setOauthConsumerKey(consumerKey);
        oAuthConsumerAppDTO.setOauthConsumerSecret(consumerSecret);
        String callbackUrl = IdentityUtil.getServerURL(portalPath, true, true);
        // Portal initialize only for super tenant.
        if (!SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
            return;
        }
        callbackUrl = "regexp=(" + callbackUrl
                + "|" + callbackUrl.replace(portalPath, "/t/carbon.super" + portalPath)
                + "|" + callbackUrl.replace(portalPath, "/t/carbon.super/o/(.*)" + portalPath)
                + ")";
        oAuthConsumerAppDTO.setCallbackUrl(callbackUrl);
        oAuthConsumerAppDTO.setBypassClientCredentials(true);
        if (grantTypes != null && !grantTypes.isEmpty()) {
            oAuthConsumerAppDTO.setGrantTypes(String.join(" ", grantTypes));
        }
        oAuthConsumerAppDTO.setPkceMandatory(true);
        oAuthConsumerAppDTO.setTokenBindingType(bindingType);
        oAuthConsumerAppDTO.setTokenBindingValidationEnabled(true);
        oAuthConsumerAppDTO.setTokenRevocationWithIDPSessionTerminationEnabled(true);

        // Apply this change for other oauth app if needed.
        if (consumerKey.equals(AppPortal.CONSOLE.getConsumerKey())
                || consumerKey.equals(AppPortal.MY_ACCOUNT.getConsumerKey())) {

            oAuthConsumerAppDTO.setIdTokenExpiryTime(Utils.getOpenIDConnectIDTokenValidityPeriodInSeconds());
            oAuthConsumerAppDTO.setUserAccessTokenExpiryTime(Utils.getUserAccessTokenValidityPeriodInSeconds());
            oAuthConsumerAppDTO.setRefreshTokenExpiryTime(Utils.getRefreshTokenValidityPeriodInSeconds());
            oAuthConsumerAppDTO.setApplicationAccessTokenExpiryTime(
                    Utils.getApplicationAccessTokenValidityPeriodInSeconds());
        }

        try {
            PrivilegedCarbonContext.startTenantFlow();
            PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
            privilegedCarbonContext.setTenantId(tenantId);
            privilegedCarbonContext.setTenantDomain(tenantDomain);
            privilegedCarbonContext.setUsername(appOwner);
            registerAndRetrieveOAuthApplicationData(oAuthConsumerAppDTO);
        } finally {
            PrivilegedCarbonContext.endTenantFlow();
        }
    }

    public void createApplication(String appName, String appOwner, String appDescription, String consumerKey,
                                  String tenantDomain, String portalPath,
                                  ApplicationManagementService applicationMgtService) {

        ServiceProvider serviceProvider = new ServiceProvider();
        serviceProvider.setApplicationName(appName);
        serviceProvider.setDescription(appDescription);

        serviceProvider.setManagementApp(true);
        serviceProvider.setSaasApp(true);
        if (StringUtils.isNotEmpty(portalPath)) {
            serviceProvider.setAccessUrl(IdentityUtil.getServerURL(portalPath, true, true));
        }

        // Set role audience as 'application'
        AssociatedRolesConfig associatedRolesConfig = new AssociatedRolesConfig();
        associatedRolesConfig.setAllowedAudience(APPLICATION);
        serviceProvider.setAssociatedRolesConfig(associatedRolesConfig);

        updateInboundConfiguration(consumerKey, serviceProvider);

        updateLocalAndOutboundConfiguration(serviceProvider);

        updateClaimConfigs(serviceProvider);

        try {
            applicationMgtService.createApplication(serviceProvider, tenantDomain, appOwner);
        } catch (IdentityApplicationManagementException e) {
            LOG.debug("Application already created.");
        }
    }

    private static void updateClaimConfigs(ServiceProvider serviceProvider) {

        ClaimConfig claimConfig = new ClaimConfig();
        claimConfig.setClaimMappings(getRequestedClaimMappings());
        claimConfig.setLocalClaimDialect(true);
        serviceProvider.setClaimConfig(claimConfig);
    }

    private static void updateLocalAndOutboundConfiguration(ServiceProvider serviceProvider) {

        LocalAndOutboundAuthenticationConfig localAndOutboundAuthenticationConfig
                = new LocalAndOutboundAuthenticationConfig();
        localAndOutboundAuthenticationConfig.setUseUserstoreDomainInLocalSubjectIdentifier(true);
        localAndOutboundAuthenticationConfig.setUseTenantDomainInLocalSubjectIdentifier(true);
        localAndOutboundAuthenticationConfig.setSkipConsent(true);
        localAndOutboundAuthenticationConfig.setSkipLogoutConsent(true);
        serviceProvider.setLocalAndOutBoundAuthenticationConfig(localAndOutboundAuthenticationConfig);
    }

    private static void updateInboundConfiguration(String consumerKey, ServiceProvider serviceProvider) {

        InboundAuthenticationRequestConfig inboundAuthenticationRequestConfig
                = new InboundAuthenticationRequestConfig();
        inboundAuthenticationRequestConfig.setInboundAuthKey(consumerKey);
        inboundAuthenticationRequestConfig.setInboundAuthType(INBOUND_AUTH2_TYPE);
        inboundAuthenticationRequestConfig.setInboundConfigType(INBOUND_CONFIG_TYPE);
        List<InboundAuthenticationRequestConfig> inboundAuthenticationRequestConfigs = Arrays
                .asList(inboundAuthenticationRequestConfig);
        InboundAuthenticationConfig inboundAuthenticationConfig = new InboundAuthenticationConfig();
        inboundAuthenticationConfig.setInboundAuthenticationRequestConfigs(
                inboundAuthenticationRequestConfigs.toArray(new InboundAuthenticationRequestConfig[0]));
        serviceProvider.setInboundAuthenticationConfig(inboundAuthenticationConfig);
    }

    private static ClaimMapping[] getRequestedClaimMappings() {

        Claim emailClaim = new Claim();
        emailClaim.setClaimUri(EMAIL_CLAIM_URI);
        ClaimMapping emailClaimMapping = new ClaimMapping();
        emailClaimMapping.setRequested(true);
        emailClaimMapping.setLocalClaim(emailClaim);
        emailClaimMapping.setRemoteClaim(emailClaim);

        Claim displayNameClaim = new Claim();
        displayNameClaim.setClaimUri(DISPLAY_NAME_CLAIM_URI);
        ClaimMapping displayNameClaimMapping = new ClaimMapping();
        displayNameClaimMapping.setRequested(true);
        displayNameClaimMapping.setLocalClaim(displayNameClaim);
        displayNameClaimMapping.setRemoteClaim(displayNameClaim);

        Claim usernameClaim = new Claim();
        usernameClaim.setClaimUri(USERNAME_CLAIM_URI);
        ClaimMapping usernameClaimMapping = new ClaimMapping();
        usernameClaimMapping.setRequested(true);
        usernameClaimMapping.setLocalClaim(usernameClaim);
        usernameClaimMapping.setRemoteClaim(usernameClaim);

        Claim profileUrlClaim = new Claim();
        profileUrlClaim.setClaimUri(PROFILE_CLAIM_URI);
        ClaimMapping profileUrlClaimMapping = new ClaimMapping();
        profileUrlClaimMapping.setRequested(true);
        profileUrlClaimMapping.setLocalClaim(profileUrlClaim);
        profileUrlClaimMapping.setRemoteClaim(profileUrlClaim);

        return new ClaimMapping[] { emailClaimMapping, displayNameClaimMapping, usernameClaimMapping,
                profileUrlClaimMapping };
    }
}
