/*
 * 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.device.mgt.ios.util;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService;
import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey;
import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException;
import org.wso2.carbon.apimgt.impl.APIManagerConfiguration;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.ios.api.utils.beans.DeviceIdentifierBean;
import org.wso2.carbon.device.mgt.ios.api.utils.beans.LoginBean;
import org.wso2.carbon.device.mgt.ios.beans.APIRegistrationProfile;
import org.wso2.carbon.device.mgt.ios.core.exception.IOSEnrollmentException;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class OAuthUtils {

    private static final Log log = LogFactory.getLog(OAuthUtils.class);
    private final static String GRANT_TYPE = "grant_type";
    private final static String GRANT_TYPE_PASSWORD = "password";
    private final static String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
    private final static String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
    private final static String AUTHORIZATION_CODE = "code";
    private final static String AUTHORIZATION_MODE = "Basic ";
    private final static String AUTHORIZATION_HEADER = "Authorization";
    private final static String CONTENT_TYPE_HEADER = "Content-Type";
    private final static String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded";
    private final static String REDIRECT_URL = "redirect_uri";
    private static final String USERNAME_LABEL = "username";
    private static final String PASSWORD_LABEL = "password";
    private final static String SCOPE_LABEL = "scope";
    private static final String COLON = ":";
    private static final String SCOPE = "perm:ios:enroll perm:ios:view-device perm:ios:applications perm:ios:enterprise-wipe";
    private static final String REFRESH_TOKEN = "refresh_token";
    private static final String ACCESS_TOKEN = "access_token";
    private static final String[] SUBSCRIBED_API = { "ios" };
    private static final String APPLICATION_NAME = "devicetype_ios";
    private static final String DEFAULT_TOKEN_TYPE = "PRODUCTION";
    public static final String DEFAULT_VALIDITY_PERIOD = "3600";

    public static ApiApplicationKey getCredentials(APIRegistrationProfile registrationProfile, String tenantDomain)
            throws APIManagerException, UserStoreException {
        ApiApplicationKey info;
        if (tenantDomain == null || tenantDomain.isEmpty()) {
            tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
        }
        try {
            PrivilegedCarbonContext.startTenantFlow();
            PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
            PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(
                    PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration()
                            .getAdminUserName());
            PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
            APIManagementProviderService apiManagementProviderService = (APIManagementProviderService) ctx
                    .getOSGiService(APIManagementProviderService.class, null);
            info = apiManagementProviderService.
                    generateAndRetrieveApplicationKeys(registrationProfile.getApplicationName(),
                            registrationProfile.getTags(), DEFAULT_TOKEN_TYPE, registrationProfile.getApplicationName(),
                            registrationProfile.isAllowedToAllDomains(), DEFAULT_VALIDITY_PERIOD);
        } finally {
            PrivilegedCarbonContext.endTenantFlow();
        }

        return info;
    }

    public static DeviceIdentifierBean getOAuthToken(LoginBean loginBean)
            throws APIManagerException, UserStoreException {

        APIRegistrationProfile registrationProfile = new APIRegistrationProfile();
        registrationProfile.setAllowedToAllDomains(false);
        registrationProfile.setTags(SUBSCRIBED_API);
        registrationProfile.setMappingAnExistingOAuthApp(false);

        String username;
        if (loginBean.getTenantDomain() != null && !loginBean.getTenantDomain().isEmpty()) {
            username = loginBean.getUsername() + "@" + loginBean.getTenantDomain();
        } else {
            username = loginBean.getUsername();
        }

        registrationProfile.setApplicationName(APPLICATION_NAME);
        ApiApplicationKey info = getCredentials(registrationProfile, loginBean.getTenantDomain());

        Map<String, String> request_params = new HashMap<String, String>();
        request_params.put(GRANT_TYPE, GRANT_TYPE_PASSWORD);
        if (loginBean.getTenantDomain() != null && !loginBean.getTenantDomain().isEmpty()) {
            request_params.put(USERNAME_LABEL, loginBean.getUsername() + "@" + loginBean.getTenantDomain());
        } else {
            request_params.put(USERNAME_LABEL, loginBean.getUsername());
        }
        request_params.put(PASSWORD_LABEL, loginBean.getPassword());
        request_params.put(SCOPE_LABEL, SCOPE);

        byte[] credentials = Base64.encodeBase64((info.getConsumerKey() + COLON +
                info.getConsumerSecret()).getBytes());
        String encodedCredentials = new String(credentials);

        Map<String, String> headers = new HashMap<String, String>();
        String authorizationString = AUTHORIZATION_MODE + encodedCredentials;
        headers.put(AUTHORIZATION_HEADER, authorizationString);
        headers.put(CONTENT_TYPE_HEADER, DEFAULT_CONTENT_TYPE);
        String payload = buildPayload(request_params);
        Map<String, String> responseParams = null;

        org.wso2.carbon.apimgt.impl.APIManagerConfigurationService configurationService = (org.wso2.carbon.apimgt.impl.APIManagerConfigurationService) PrivilegedCarbonContext
                .getThreadLocalCarbonContext()
                .getOSGiService(org.wso2.carbon.apimgt.impl.APIManagerConfigurationService.class, null);
        APIManagerConfiguration configuration = configurationService.getAPIManagerConfiguration();
        String keyValidatorServerURL = configuration.getProperty("APIKeyValidator.ServerURL").get(0);
        String keyManagerURL;
        if (keyValidatorServerURL.contains("//services/")) {
            keyManagerURL = configuration.getProperty("APIKeyValidator.ServerURL").get(0).replaceAll("//services/", "");
        } else {
            keyManagerURL = configuration.getProperty("APIKeyValidator.ServerURL").get(0).replaceAll("/services/", "");
        }
        String tokenEndPoint =
                keyManagerURL + configuration.getProperty("OAuthConfigurations.TokenEndPointName").get(0);
        HttpClient client = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(tokenEndPoint);
        httpPost = (HttpPost) buildHeaders(httpPost, headers);
        byte[] postData = payload.getBytes();

        JsonParser parser;
        DeviceIdentifierBean deviceIdentifierBean = null;
        try {
            httpPost.setEntity(new ByteArrayEntity(postData));
            HttpResponse response = client.execute(httpPost);
            String status = String.valueOf(response.getStatusLine().getStatusCode());

            String result = getResponseBody(response);
            parser = new JsonParser();
            JsonObject tokenData = (JsonObject) parser.parse(result);
            deviceIdentifierBean = new DeviceIdentifierBean();
            deviceIdentifierBean.setRefreshToken(tokenData.get(REFRESH_TOKEN).getAsString());
            deviceIdentifierBean.setAccessToken(tokenData.get(ACCESS_TOKEN).getAsString());
            deviceIdentifierBean.setClientCredentials(encodedCredentials);
        } catch (ClientProtocolException e) {
            log.error("Error occurred while sending 'Post' request due to an invalid client protocol being used", e);
        } catch (IOException e) {
            log.error("Error occurred while sending 'Post' request due to failure of server connection", e);
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while processing the token rsponse", e);
        }
        return deviceIdentifierBean;
    }

    public static HttpRequestBase buildHeaders(HttpRequestBase httpRequestBase, Map<String, String> headers) {
        for (Map.Entry<String, String> header : headers.entrySet()) {
            httpRequestBase.setHeader(header.getKey(), header.getValue());
        }
        return httpRequestBase;
    }

    public static String buildPayload(Map<String, String> params) {
        StringBuilder bodyBuilder = new StringBuilder();
        Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> param = iterator.next();
            bodyBuilder.append(param.getKey()).append('=').append(param.getValue());
            if (iterator.hasNext()) {
                bodyBuilder.append('&');
            }
        }

        return bodyBuilder.toString();
    }

    public static String getResponseBody(HttpResponse response) throws IOSEnrollmentException {

        String responseBody = null;
        HttpEntity entity = null;

        entity = response.getEntity();
        try {
            responseBody = getResponseBodyContent(entity);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return responseBody;
    }

    public static String getResponseBodyContent(final HttpEntity entity) throws IOException {

        InputStream instream = entity.getContent();

        if (entity.getContentLength() > Integer.MAX_VALUE) {
            String errorMsg = "HTTP entity too large to be buffered in memory.";
            throw new IllegalArgumentException(errorMsg);
        }

        String charset = getContentCharSet(entity);

        if (charset == null) {
            charset = HTTP.DEFAULT_CONTENT_CHARSET;
        }

        Reader reader = new InputStreamReader(instream, charset);
        StringBuilder buffer = new StringBuilder();

        try {
            char[] bufferSize = new char[1024];
            int length;

            while ((length = reader.read(bufferSize)) != -1) {
                buffer.append(bufferSize, 0, length);
            }
        } finally {
            reader.close();
        }
        return buffer.toString();
    }

    public static String getContentCharSet(final HttpEntity entity) {

        String charSet = null;

        if (entity.getContentType() != null) {
            HeaderElement values[] = entity.getContentType().getElements();

            if (values.length > 0) {
                NameValuePair param = values[0].getParameterByName("charset");

                if (param != null) {
                    charSet = param.getValue();
                }
            }
        }
        return charSet;
    }

}
