/*
 * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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.dynamic.client.web.app.registration.util;

import com.google.gson.stream.JsonReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.core.security.AuthenticatorsConfiguration;
import org.wso2.carbon.dynamic.client.registration.DynamicClientRegistrationException;
import org.wso2.carbon.dynamic.client.registration.OAuthApplicationInfo;
import org.wso2.carbon.dynamic.client.registration.profile.RegistrationProfile;
import org.wso2.carbon.dynamic.client.web.app.registration.dto.DynamicClientRegistrationSettings;
import org.wso2.carbon.dynamic.client.web.app.registration.dto.OAuthAppDetails;
import org.wso2.carbon.dynamic.client.web.app.registration.dto.JaggeryOAuthConfigurationSettings;
import org.wso2.carbon.dynamic.client.web.app.registration.internal.DynamicClientWebAppRegistrationDataHolder;
import org.wso2.carbon.registry.api.RegistryException;
import org.wso2.carbon.registry.api.Resource;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.ConfigurationContextService;
import org.wso2.carbon.utils.NetworkUtils;

import javax.servlet.ServletContext;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.*;
import java.nio.charset.Charset;

/**
 * Holds the utility methods to be used in Dynamic client web app registration component.
 */
public class DynamicClientWebAppRegistrationUtil {

    private final static String OAUTH_PARAM_GRANT_TYPE = "grantType";
    private final static String OAUTH_PARAM_TOKEN_SCOPE = "tokenScope";
    private final static String OAUTH_PARAM_SAAS_APP = "saasApp";
    private final static String OAUTH_PARAM_CALLBACK_URL = "callbackURL";
    private final static String AUDIENCE = "audience";
    private final static String ASSERTION_CONSUMER_URL = "assertionConsumerURL";
    private final static String RECEPIENT_VALIDATION_URL = "recepientValidationURL";
    private static final String JAGGERY_APP_OAUTH_CONFIG_PATH =
            "config" + File.separator + "service-provider.json";

    private static final Log log =
            LogFactory.getLog(DynamicClientWebAppRegistrationUtil.class);
    private static final String CHARSET_UTF_8 = "UTF-8";

    public static Registry getGovernanceRegistry() throws DynamicClientRegistrationException {
        try {
            int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
            return DynamicClientWebAppRegistrationDataHolder.getInstance().getRegistryService().
                    getGovernanceSystemRegistry(tenantId);
        } catch (RegistryException e) {
            throw new DynamicClientRegistrationException(
                    "Error in retrieving governance registry instance: " +
                    e.getMessage(), e);
        }
    }

    public static OAuthAppDetails getOAuthApplicationData(String appName)
            throws DynamicClientRegistrationException {
        Resource resource;
        String resourcePath = DynamicClientWebAppRegistrationConstants.OAUTH_APP_DATA_REGISTRY_PATH + "/" + appName;
        try {
            if (log.isDebugEnabled()) {
                log.debug("Retrieving OAuth application " + appName + " data from Registry");
            }
            resource = DynamicClientWebAppRegistrationUtil.getRegistryResource(resourcePath);
            if (resource != null) {
                JAXBContext context = JAXBContext.newInstance(OAuthAppDetails.class);
                Unmarshaller unmarshaller = context.createUnmarshaller();
                return (OAuthAppDetails) unmarshaller.unmarshal(
                        new StringReader(new String((byte[]) resource.getContent(), Charset.forName(
                                DynamicClientWebAppRegistrationConstants.CharSets.CHARSET_UTF8))));
            }
            return new OAuthAppDetails();
        } catch (JAXBException e) {
            throw new DynamicClientRegistrationException(
                    "Error occurred while parsing the OAuth application data : " + appName, e);
        } catch (RegistryException e) {
            throw new DynamicClientRegistrationException(
                    "Error occurred while retrieving the Registry resource of OAuth application : " + appName, e);
        }
    }

    public static boolean putOAuthApplicationData(OAuthAppDetails oAuthAppDetails) throws
                                                                                   DynamicClientRegistrationException {
        boolean status;
        try {
            if (log.isDebugEnabled()) {
                log.debug("Persisting OAuth application data in Registry");
            }
            StringWriter writer = new StringWriter();
            JAXBContext context = JAXBContext.newInstance(OAuthAppDetails.class);
            Marshaller marshaller = context.createMarshaller();
            marshaller.marshal(oAuthAppDetails, writer);

            Resource resource = DynamicClientWebAppRegistrationUtil.getGovernanceRegistry().newResource();
            resource.setContent(writer.toString());
            resource.setMediaType(DynamicClientWebAppRegistrationConstants.ContentTypes.CONTENT_TYPE_XML);
            String resourcePath = DynamicClientWebAppRegistrationConstants.OAUTH_APP_DATA_REGISTRY_PATH + "/" +
                    oAuthAppDetails.getClientName();
            status = DynamicClientWebAppRegistrationUtil.putRegistryResource(resourcePath, resource);
        } catch (RegistryException e) {
            throw new DynamicClientRegistrationException(
                    "Error occurred while persisting OAuth application data : " +
                    oAuthAppDetails.getClientName(), e);
        } catch (JAXBException e) {
            throw new DynamicClientRegistrationException(
                    "Error occurred while parsing the OAuth application data : " +
                    oAuthAppDetails.getWebAppName(), e);
        }
        return status;
    }

    public static boolean putRegistryResource(String path, Resource resource) throws DynamicClientRegistrationException {
        try {
            Registry governanceRegistry = DynamicClientWebAppRegistrationUtil.getGovernanceRegistry();
            governanceRegistry.beginTransaction();
            governanceRegistry.put(path, resource);
            governanceRegistry.commitTransaction();
            return true;
        } catch (RegistryException e) {
            throw new DynamicClientRegistrationException("Error occurred while persisting registry resource : " +
                                                         e.getMessage(), e);
        }
    }

    public static Resource getRegistryResource(String path) throws DynamicClientRegistrationException {
        try {
            Registry governanceRegistry = DynamicClientWebAppRegistrationUtil.getGovernanceRegistry();
            if (governanceRegistry.resourceExists(path)) {
                return governanceRegistry.get(path);
            }
            return null;
        } catch (RegistryException e) {
            throw new DynamicClientRegistrationException("Error in retrieving registry resource : " +
                                                         e.getMessage(), e);
        }
    }

    public static String getUserName() {
        String username = "";
        RealmService realmService = DynamicClientWebAppRegistrationDataHolder.getInstance().getRealmService();
        if (realmService != null) {
            username = realmService.getBootstrapRealmConfiguration().getAdminUserName();
        }
        return username;
    }

    public static RegistrationProfile constructRegistrationProfile(ServletContext servletContext, String webAppName) {
        RegistrationProfile registrationProfile;
        registrationProfile = new RegistrationProfile();
        registrationProfile.setGrantType(servletContext.getInitParameter(
                DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_GRANT_TYPE));
        registrationProfile.setTokenScope(servletContext.getInitParameter(
                DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_TOKEN_SCOPE));
        registrationProfile.setOwner(DynamicClientWebAppRegistrationUtil.getUserName());
        String callbackURL = servletContext.getInitParameter(
                DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_CALLBACK_URL);
        if ((callbackURL != null) && !callbackURL.isEmpty()) {
            registrationProfile.setCallbackUrl(callbackURL);
        } else {
            registrationProfile.setCallbackUrl(DynamicClientWebAppRegistrationUtil.getCallbackUrl(webAppName));
        }
        registrationProfile.setClientName(webAppName);
        registrationProfile.setSaasApp(Boolean.parseBoolean(servletContext.getInitParameter(
                DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_SAAS_APP)));
        return registrationProfile;
    }

    public static RegistrationProfile constructRegistrationProfile(JaggeryOAuthConfigurationSettings
                                                                 jaggeryOAuthConfigurationSettings, String webAppName) {
        RegistrationProfile registrationProfile = new RegistrationProfile();
        if (jaggeryOAuthConfigurationSettings != null) {
            registrationProfile.setGrantType(jaggeryOAuthConfigurationSettings.getGrantType());
            registrationProfile.setTokenScope(jaggeryOAuthConfigurationSettings.getTokenScope());
            registrationProfile.setClientName(webAppName);
            registrationProfile.setSaasApp(jaggeryOAuthConfigurationSettings.isSaasApp());
            registrationProfile.setOwner(DynamicClientWebAppRegistrationUtil.getUserName());
            registrationProfile.setAudience(jaggeryOAuthConfigurationSettings.getAudience());
            registrationProfile.setAssertionConsumerURL(jaggeryOAuthConfigurationSettings.getAssertionConsumerURL());
            registrationProfile.setRecepientValidationURL(jaggeryOAuthConfigurationSettings.getRecepientValidationURL());
            if (jaggeryOAuthConfigurationSettings.getCallbackURL() != null) {
                registrationProfile.setCallbackUrl(jaggeryOAuthConfigurationSettings.getCallbackURL());
            } else {
                registrationProfile.setCallbackUrl(DynamicClientWebAppRegistrationUtil.getCallbackUrl(webAppName));
            }
        } else {
            log.warn("Please configure OAuth settings properly for jaggery app : " + webAppName);
        }
        return registrationProfile;
    }

    public static boolean validateRegistrationProfile(RegistrationProfile registrationProfile) {
        boolean status = true;
        //todo fix this
        if (registrationProfile.getGrantType() == null) {
            status = false;
            log.warn("Required parameter 'grantType' is missing for initiating Dynamic-Client " +
                     "registration for webapp : " + registrationProfile.getClientName());
        }
        if (registrationProfile.getTokenScope() == null) {
            status = false;
            log.warn("Required parameter 'tokenScope' is missing for initiating Dynamic-Client " +
                     "registration for webapp : " + registrationProfile.getClientName());
        }
        return status;
    }

    public static JaggeryOAuthConfigurationSettings getJaggeryAppOAuthSettings(ServletContext servletContext) {
        JaggeryOAuthConfigurationSettings jaggeryOAuthConfigurationSettings = new JaggeryOAuthConfigurationSettings();
        try {
            InputStream inputStream = servletContext.getResourceAsStream(JAGGERY_APP_OAUTH_CONFIG_PATH);
            if (inputStream != null) {
                JsonReader reader = new JsonReader(new InputStreamReader(inputStream, CHARSET_UTF_8));
                reader.beginObject();
                while (reader.hasNext()) {
                    String key = reader.nextName();
                    switch (key) {
                        case DynamicClientWebAppRegistrationConstants.DYNAMIC_CLIENT_REQUIRED_FLAG:
                            jaggeryOAuthConfigurationSettings.setRequireDynamicClientRegistration(reader.nextBoolean());
                            break;
                        case DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_GRANT_TYPE:
                            jaggeryOAuthConfigurationSettings.setGrantType(reader.nextString());
                            break;
                        case DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_TOKEN_SCOPE:
                            jaggeryOAuthConfigurationSettings.setTokenScope(reader.nextString());
                            break;
                        case DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_SAAS_APP:
                            jaggeryOAuthConfigurationSettings.setSaasApp(reader.nextBoolean());
                            break;
                        case DynamicClientWebAppRegistrationUtil.OAUTH_PARAM_CALLBACK_URL:
                            jaggeryOAuthConfigurationSettings.setCallbackURL(reader.nextString());
                            break;
                        case DynamicClientWebAppRegistrationUtil.AUDIENCE:
                            jaggeryOAuthConfigurationSettings.setAudience(reader.nextString());
                            break;
                        case DynamicClientWebAppRegistrationUtil.ASSERTION_CONSUMER_URL:
                            jaggeryOAuthConfigurationSettings.setAssertionConsumerURL(reader.nextString());
                            break;
                        case DynamicClientWebAppRegistrationUtil.RECEPIENT_VALIDATION_URL:
                            jaggeryOAuthConfigurationSettings.setRecepientValidationURL(reader.nextString());
                            break;
                    }
                }
                return jaggeryOAuthConfigurationSettings;
            }
        } catch (UnsupportedEncodingException e) {
            log.error("Error occurred while initializing OAuth settings for the Jaggery app.", e);
        } catch (IOException e) {
            log.error("Error occurred while initializing OAuth settings for the Jaggery app.", e);
        }
        return jaggeryOAuthConfigurationSettings;
    }

    public static String getServerBaseUrl() {
        // Hostname
        String hostName = "localhost";
        try {
            hostName = NetworkUtils.getMgtHostName();
        } catch (Exception ignored) {
        }
        // HTTPS port
        String mgtConsoleTransport = CarbonUtils.getManagementTransport();
        ConfigurationContextService configContextService =
                DynamicClientWebAppRegistrationDataHolder.getInstance().getConfigurationContextService();
        int port = CarbonUtils.getTransportPort(configContextService, mgtConsoleTransport);
        int httpsProxyPort =
                CarbonUtils.getTransportProxyPort(configContextService.getServerConfigContext(),
                                                  mgtConsoleTransport);
        if (httpsProxyPort > 0) {
            port = httpsProxyPort;
        }
        return "https://" + hostName + ":" + port;
    }

    public static String getCallbackUrl(String context) {
        return getServerBaseUrl() + "/" + context;
    }

    public static void addClientCredentialsToWebContext(OAuthAppDetails oAuthAppDetails,
                                                        ServletContext servletContext) {
        if (oAuthAppDetails != null) {
            //Check for client credentials
            if ((oAuthAppDetails.getClientKey() != null && !oAuthAppDetails.getClientKey().isEmpty()) &&
                (oAuthAppDetails.getClientSecret() != null && !oAuthAppDetails.getClientSecret().isEmpty())) {
                servletContext.setAttribute(DynamicClientWebAppRegistrationConstants.OAUTH_CLIENT_KEY,
                                            oAuthAppDetails.getClientKey());
                servletContext.setAttribute(DynamicClientWebAppRegistrationConstants.OAUTH_CLIENT_SECRET,
                                            oAuthAppDetails.getClientSecret());
            } else {
                log.warn("Client credentials not found for web app : " + oAuthAppDetails.getWebAppName());
            }
        }
    }

    public static String replaceInvalidChars(String username) {
        return username.replaceAll("@","_AT_");
    }

    private static DynamicClientRegistrationSettings getDynamicClientRegistrationSettings()
            throws IllegalArgumentException {
        AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance();
        AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration.
                                                   getAuthenticatorConfig(DynamicClientWebAppRegistrationConstants.
                                                                            ConfigurationProperties.AUTHENTICATOR_NAME);
        DynamicClientRegistrationSettings dynamicClientRegistrationSettings = new DynamicClientRegistrationSettings();
        if (authenticatorConfig != null && authenticatorConfig.getParameters() != null) {
            dynamicClientRegistrationSettings.setIsRemote(Boolean.parseBoolean(
                    authenticatorConfig.getParameters().get(
                            DynamicClientWebAppRegistrationConstants.ConfigurationProperties.
                                                                    AUTHENTICATOR_CONFIG_IS_REMOTE)));
            dynamicClientRegistrationSettings.setHost(authenticatorConfig.getParameters().
                    get(DynamicClientWebAppRegistrationConstants.ConfigurationProperties.AUTHENTICATOR_CONFIG_HOST_URL));

        }else{
            throw new IllegalArgumentException("Configuration parameters need to be defined in Authenticators.xml.");
        }
        return dynamicClientRegistrationSettings;
    }

    //This method will choose the best DynamicClientRegistrationService based on server configurations and
    //registers OAuth client.
    public static OAuthApplicationInfo registerOAuthApplication(RegistrationProfile registrationProfile)
            throws DynamicClientRegistrationException {
        DynamicClientRegistrationSettings dynamicClientRegistrationSettings = getDynamicClientRegistrationSettings();
        if (dynamicClientRegistrationSettings.isRemote()) {
            return RemoteDCRClient.createOAuthApplication(registrationProfile,
                                                          getHostName(dynamicClientRegistrationSettings.getHost()));
        } else {
            return DynamicClientWebAppRegistrationDataHolder.getInstance().
                    getDynamicClientRegistrationService().registerOAuthApplication(registrationProfile);
        }
    }

    //This method will choose the best DynamicClientRegistrationService based on server configurations and
    //unregisters OAuth client.
    public static boolean unregisterOAuthApplication(String owner, String clientName, String clientId)
            throws DynamicClientRegistrationException {
        DynamicClientRegistrationSettings dynamicClientRegistrationSettings = getDynamicClientRegistrationSettings();
        if (dynamicClientRegistrationSettings.isRemote()) {
            return RemoteDCRClient.deleteOAuthApplication(owner, clientName, clientId,
                                                          getHostName(dynamicClientRegistrationSettings.getHost()));
        } else {
            return DynamicClientWebAppRegistrationDataHolder.getInstance().
                    getDynamicClientRegistrationService().unregisterOAuthApplication(owner, clientName, clientId);
        }
    }

    private static String getHostName(String host) {
        if (host != null && !host.isEmpty()) {
            if (host.contains("https://")) {
                return host.replace("https://","");
            }
        } else {
            throw new IllegalArgumentException("Remote Host parameter must defined in Authenticators.xml.");
        }
        return null;
    }

}