/*
 * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed 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.identity.api.server.application.management.v1.core.functions.application.inbound.oauth2;

import org.apache.commons.lang.StringUtils;
import org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementServiceHolder;
import org.wso2.carbon.identity.api.server.application.management.v1.OpenIDConnectConfiguration;
import org.wso2.carbon.identity.api.server.application.management.v1.core.functions.application.inbound.InboundFunctions;
import org.wso2.carbon.identity.api.server.common.error.APIError;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.StandardInboundProtocols;
import org.wso2.carbon.identity.application.common.model.InboundAuthenticationRequestConfig;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.oauth.IdentityOAuthAdminException;
import org.wso2.carbon.identity.oauth.IdentityOAuthClientException;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.dto.OAuthConsumerAppDTO;

import java.util.UUID;

import static org.wso2.carbon.identity.api.server.application.management.v1.core.functions.Utils.buildBadRequestError;
import static org.wso2.carbon.identity.api.server.application.management.v1.core.functions.Utils.buildServerError;

/**
 * Helper functions for OAuth inbound management.
 */
public class OAuthInboundFunctions {

    private OAuthInboundFunctions() {

    }

    public static InboundAuthenticationRequestConfig putOAuthInbound(ServiceProvider application,
                                                                     OpenIDConnectConfiguration oidcConfigModel) {

        // First we identify whether this is a insert or update.
        try {
            String currentClientId = InboundFunctions.getInboundAuthKey(application, StandardInboundProtocols.OAUTH2);
            if (currentClientId != null) {
                // This is an update.
                OAuthConsumerAppDTO oauthApp = ApplicationManagementServiceHolder.getInstance().getOAuthAdminService
                        ().getOAuthApplicationData(currentClientId);

                if (!StringUtils.equals(oauthApp.getOauthConsumerKey(), oidcConfigModel.getClientId())) {
                    throw buildBadRequestError("Invalid ClientID provided for update.");
                }

                if (!StringUtils.equals(oauthApp.getOauthConsumerSecret(), oidcConfigModel.getClientSecret())) {
                    throw buildBadRequestError("Invalid ClientSecret provided for update.");
                }

                OAuthConsumerAppDTO appToUpdate = new ApiModelToOAuthConsumerApp().apply(application
                        .getApplicationName(), oidcConfigModel);
                ApplicationManagementServiceHolder.getInstance().getOAuthAdminService().updateConsumerApplication
                        (appToUpdate);

                String updatedClientId = appToUpdate.getOauthConsumerKey();
                return createInboundAuthRequestConfig(updatedClientId);
            } else {
                return createOAuthInbound(application.getApplicationName(), oidcConfigModel);
            }

        } catch (IdentityOAuthAdminException e) {
            throw handleOAuthException(e);
        }
    }

    private static APIError handleOAuthException(IdentityOAuthAdminException e) {

        String message = "Error while Creating/Updating OAuth2/OpenIDConnect configuration. " + e.getMessage();
        if (e instanceof IdentityOAuthClientException) {
            return buildBadRequestError(message);
        }
        return buildServerError(message, e);
    }

    /**
     * @deprecated
     *
     * This method will be removed in the upcoming major release.
     * Because, service provider name should be passed to create oauth app name.
     * Use {@link #createOAuthInbound(String, OpenIDConnectConfiguration)} instead.
     *
     */
    @Deprecated
    public static InboundAuthenticationRequestConfig createOAuthInbound(OpenIDConnectConfiguration oidcModel) {

        return createOAuthInbound(UUID.randomUUID().toString(), oidcModel);
    }

    public static InboundAuthenticationRequestConfig createOAuthInbound(String appName, OpenIDConnectConfiguration
                                                                        oidcModel) {

        // Build a consumer apps object.
        OAuthConsumerAppDTO consumerApp = new ApiModelToOAuthConsumerApp().apply(appName, oidcModel);
        try {
            OAuthConsumerAppDTO createdOAuthApp = ApplicationManagementServiceHolder.getInstance()
                    .getOAuthAdminService()
                    .registerAndRetrieveOAuthApplicationData(consumerApp);

            return createInboundAuthRequestConfig(createdOAuthApp.getOauthConsumerKey());
        } catch (IdentityOAuthAdminException e) {
            throw handleOAuthException(e);
        }
    }

    private static InboundAuthenticationRequestConfig createInboundAuthRequestConfig(String clientId) {

        InboundAuthenticationRequestConfig oidcInbound = new InboundAuthenticationRequestConfig();
        oidcInbound.setInboundAuthType(StandardInboundProtocols.OAUTH2);
        oidcInbound.setInboundAuthKey(clientId);
        return oidcInbound;
    }

    public static OpenIDConnectConfiguration getOAuthConfiguration(InboundAuthenticationRequestConfig inboundAuth) {

        String clientId = inboundAuth.getInboundAuthKey();
        try {
            OAuthConsumerAppDTO oauthApp =
                    ApplicationManagementServiceHolder.getInstance().getOAuthAdminService().getOAuthApplicationData
                            (clientId);
            return new OAuthConsumerAppToApiModel().apply(oauthApp);

        } catch (IdentityOAuthAdminException e) {
            throw buildServerError("Error while retrieving oauth application for clientId: " + clientId, e);
        }
    }

    public static void deleteOAuthInbound(InboundAuthenticationRequestConfig inbound) {

        try {
            String consumerKey = inbound.getInboundAuthKey();
            ApplicationManagementServiceHolder.getInstance().getOAuthAdminService().removeOAuthApplicationData
                    (consumerKey);
        } catch (IdentityOAuthAdminException e) {
            throw buildServerError("Error while trying to rollback OAuth2/OpenIDConnect " +
                    "configuration." + e.getMessage(), e);
        }
    }

    public static OpenIDConnectConfiguration regenerateClientSecret(String clientId) {

        try {
            OAuthConsumerAppDTO oAuthConsumerAppDTO = ApplicationManagementServiceHolder.getInstance()
                    .getOAuthAdminService().updateAndRetrieveOauthSecretKey(clientId);
            return new OAuthConsumerAppToApiModel().apply(oAuthConsumerAppDTO);
        } catch (IdentityOAuthAdminException e) {
            throw buildServerError("Error while regenerating client secret of oauth application.", e);
        }
    }

    public static void revokeOAuthClient(String clientId) {

        try {
            ApplicationManagementServiceHolder.getInstance().getOAuthAdminService()
                    .updateConsumerAppState(clientId, OAuthConstants.OauthAppStates.APP_STATE_REVOKED);
        } catch (IdentityOAuthAdminException e) {
            throw buildServerError("Error while revoking oauth application.", e);
        }
    }
}
