/*
 * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
 *
 * Copyright (c) 2014, Gluu
 */

package org.gluu.oxauth.client;

import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang.StringUtils;
import org.gluu.oxauth.model.common.AuthorizationMethod;
import org.gluu.oxauth.model.crypto.OxAuthCryptoProvider;
import org.gluu.oxauth.model.jwe.Jwe;
import org.gluu.oxauth.model.jwt.Jwt;
import org.gluu.oxauth.model.userinfo.UserInfoErrorResponseType;
import org.gluu.oxauth.model.util.JwtUtil;
import org.gluu.oxauth.model.util.Util;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Encapsulates functionality to make user info request calls to an authorization server via REST Services.
 *
 * @author Javier Rojas Blum
 * @version December 26, 2016
 */
public class UserInfoClient extends BaseClient<UserInfoRequest, UserInfoResponse> {

    private String sharedKey;
    private PrivateKey privateKey;
    private String jwksUri;

    /**
     * Constructs an User Info client by providing a REST url where the service is located.
     *
     * @param url The REST Service location.
     */
    public UserInfoClient(String url) {
        super(url);
    }

    @Override
    public String getHttpMethod() {
        if (request.getAuthorizationMethod() == null
                || request.getAuthorizationMethod() == AuthorizationMethod.AUTHORIZATION_REQUEST_HEADER_FIELD
                || request.getAuthorizationMethod() == AuthorizationMethod.URL_QUERY_PARAMETER) {
            return HttpMethod.GET;
        } else /*if (request.getAuthorizationMethod() == AuthorizationMethod.FORM_ENCODED_BODY_PARAMETER)*/ {
            return HttpMethod.POST;
        }
    }

    /**
     * Executes the call to the REST Service and processes the response.
     *
     * @param accessToken The access token obtained from the oxAuth authorization request.
     * @return The service response.
     */
    public UserInfoResponse execUserInfo(String accessToken) {
        setRequest(new UserInfoRequest(accessToken));

        return exec();
    }

    /**
     * Executes the call to the REST Service and processes the response.
     *
     * @return The service response.
     */
    public UserInfoResponse exec() {
        // Prepare request parameters
        initClientRequest();

        Builder clientRequest = null;
        if (getRequest().getAuthorizationMethod() == null
                || getRequest().getAuthorizationMethod() == AuthorizationMethod.AUTHORIZATION_REQUEST_HEADER_FIELD) {
            if (StringUtils.isNotBlank(getRequest().getAccessToken())) {
            	clientRequest = webTarget.request();
                clientRequest.header("Authorization", "Bearer " + getRequest().getAccessToken());
            }
        } else if (getRequest().getAuthorizationMethod() == AuthorizationMethod.FORM_ENCODED_BODY_PARAMETER) {
            if (StringUtils.isNotBlank(getRequest().getAccessToken())) {
                requestForm.param("access_token", getRequest().getAccessToken());
            }
        } else if (getRequest().getAuthorizationMethod() == AuthorizationMethod.URL_QUERY_PARAMETER) {
            if (StringUtils.isNotBlank(getRequest().getAccessToken())) {
            	addReqParam("access_token", getRequest().getAccessToken().toString());
            }
        }

        if (clientRequest == null) {
        	clientRequest = webTarget.request();
        }

        clientRequest.header("Content-Type", MediaType.APPLICATION_FORM_URLENCODED);
//      clientRequest.setHttpMethod(getHttpMethod());

        // Call REST Service and handle response
        try {
            if (getRequest().getAuthorizationMethod() == null
                    || getRequest().getAuthorizationMethod() == AuthorizationMethod.AUTHORIZATION_REQUEST_HEADER_FIELD
                    || getRequest().getAuthorizationMethod() == AuthorizationMethod.URL_QUERY_PARAMETER) {
                clientResponse = clientRequest.buildGet().invoke();
            } else if (getRequest().getAuthorizationMethod() == AuthorizationMethod.FORM_ENCODED_BODY_PARAMETER) {
                clientResponse = clientRequest.buildPost(Entity.form(requestForm)).invoke();
            }

            int status = clientResponse.getStatus();

            setResponse(new UserInfoResponse(status));

            String entity = clientResponse.readEntity(String.class);
            getResponse().setEntity(entity);
            getResponse().setHeaders(clientResponse.getMetadata());
            if (StringUtils.isNotBlank(entity)) {
                List<Object> contentType = clientResponse.getHeaders().get("Content-Type");
                if (contentType != null && contentType.contains("application/jwt")) {
                    String[] jwtParts = entity.split("\\.");
                    if (jwtParts.length == 5) {
                        byte[] sharedSymmetricKey = sharedKey != null ? sharedKey.getBytes(Util.UTF8_STRING_ENCODING) : null;
                        Jwe jwe = Jwe.parse(entity, privateKey, sharedSymmetricKey);
                        getResponse().setClaims(jwe.getClaims().toMap());
                    } else {
                        Jwt jwt = Jwt.parse(entity);

                        OxAuthCryptoProvider cryptoProvider = new OxAuthCryptoProvider();
                        boolean signatureVerified = cryptoProvider.verifySignature(
                                jwt.getSigningInput(),
                                jwt.getEncodedSignature(),
                                jwt.getHeader().getKeyId(),
                                JwtUtil.getJSONWebKeys(jwksUri),
                                sharedKey,
                                jwt.getHeader().getSignatureAlgorithm());

                        if (signatureVerified) {
                            getResponse().setClaims(jwt.getClaims().toMap());
                        }
                    }
                } else {
                    try {
                        JSONObject jsonObj = new JSONObject(entity);

                        if (jsonObj.has("error")) {
                            getResponse().setErrorType(UserInfoErrorResponseType.fromString(jsonObj.getString("error")));
                            jsonObj.remove("error");
                        }
                        if (jsonObj.has("error_description")) {
                            getResponse().setErrorDescription(jsonObj.getString("error_description"));
                            jsonObj.remove("error_description");
                        }
                        if (jsonObj.has("error_uri")) {
                            getResponse().setErrorUri(jsonObj.getString("error_uri"));
                            jsonObj.remove("error_uri");
                        }

                        for (Iterator<String> iterator = jsonObj.keys(); iterator.hasNext(); ) {
                            String key = iterator.next();
                            List<String> values = new ArrayList<String>();

                            JSONArray jsonArray = jsonObj.optJSONArray(key);
                            if (jsonArray != null) {
                                for (int i = 0; i < jsonArray.length(); i++) {
                                    String value = jsonArray.optString(i);
                                    if (value != null) {
                                        values.add(value);
                                    }
                                }
                            } else {
                                String value = jsonObj.optString(key);
                                if (value != null) {
                                    values.add(value);
                                }
                            }

                            getResponse().getClaims().put(key, values);
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeConnection();
        }

        return getResponse();
    }

    public void setSharedKey(String sharedKey) {
        this.sharedKey = sharedKey;
    }

    public void setPrivateKey(PrivateKey privateKey) {
        this.privateKey = privateKey;
    }

    public String getJwksUri() {
        return jwksUri;
    }

    public void setJwksUri(String jwksUri) {
        this.jwksUri = jwksUri;
    }
}