/**
* Licensed Materials - Property of IBM* and/or HCL**
* IBM UrbanCode Deploy
* (c) Copyright IBM Corporation 2011, 2017. All Rights Reserved.
* (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*
* * Trademark of International Business Machines
* ** Trademark of HCL Technologies Limited
*/

package com.ibm.rational.air.plugin.worklight;

import com.urbancode.ud.client.UDRestClient;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.content.StringBody
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.entity.StringEntity;
import org.codehaus.jettison.json.JSONObject;

/**
* A utility class for helping to run the Worklight REST commands.
*/
public class WLRestClient extends UDRestClient {

    public WLRestClient(URI url, String clientUser, String clientPassword) {
        super (url, clientUser, clientPassword);
    }

    public String getServerVersion() {
        String result = getServerVersion8X();
        if (!result) {
            result = getServerVersion6X();
        }
        return result;
    }

    // Attempt to connect to the Worklight 8.0 REST API
    private String getServerVersion8X() {
        HttpGet getRequest = new HttpGet(this.url.toString() + "/management-apis/2.0/config");
        String result;
        HttpResponse response;
        try {
            response = this.invokeMethod(getRequest);
            JSONObject props = new JSONObject(getBody(response));
            result = props.getString("productVersion");
        } catch (IOException e) {
            println "[MobileFirst v8] Unable to connect to the server using REST to obtain the version " +
                "because of: " + e.getMessage();
            if (e.getMessage().contains("Unauthorized")) {
                println "[Possible Solution] Specify IBM MobileFirst username and password properties to authorize."
            }
        }
        return result;
    }

    /**
    * Connects to the supplied Worklight Server and obtains the version.
    * Returns the Worklight Server version from the REST call.
    */
    public String getServerVersion6X() {

        // When a user and password are provided, we attempt to authenticate to
        // the Worklight Console for Worklight 6.0 and 6.1 for the REST request.
        if(this.clientUser != null && this.clientPassword != null) {
            if(!authenticate()) {
                println "[MobileFirst v6] Failed to authenticate with the Worklight Server using REST.";
                return null;
            }
        }

        HttpGet getRequest = new HttpGet(this.url.toString() + "/console/api/ui/serverVersion");
        String result;
        HttpResponse response;
        try {
            response = this.invokeMethod(getRequest);
            JSONObject props = new JSONObject(getBody(response));
            result = props.getString("serverVersion");
        } catch (IOException e) {
            // Ignore when the system can be running on a WL 6.2 or newer server.
            // Possibly the server could be unreachable.
            println "[MobileFirst v6] Unable to connect to the server using REST to obtain the version " +
                "because of: " + e.getMessage();
        }
        return result;
    }

    /**
    * Authenticates with a secure Worklight console.
    */
    private boolean authenticate() {
        // A GET request needs to be sent first to initiate the login request.
        HttpGet getRequest = new HttpGet(this.url.toString() + "/console");
        try {
            this.invokeMethod(getRequest);
        } catch (IOException e) {
            println "[MobileFirst v6] An error occurred while trying to initiate authentication " +
                "with the Worklight Server using REST: " + e.getMessage();
        }

        // Send a POST request for the authentication. Send the user credentials in
        // the body for security.
        HttpPost postRequest = new HttpPost(this.url.toString() + "/j_security_check");
        postRequest.setEntity(new StringEntity("j_username=" + this.clientUser + "&j_password=" +
            this.clientPassword, ContentType.APPLICATION_FORM_URLENCODED));
        String result;
        HttpResponse response;
        try {
            response = this.invokeRedirectMethod(postRequest);
        } catch (IOException e) {
            // Ignore when the system can be running on a WL 6.2 or newer server.
            // Possibly the server could be unreachable.
            println "[MobileFirst v6] Unable to connect to the server using REST to authenticate " +
                "because of: " + e.getMessage();
        }
        // Make sure we got redirected to the logged in session.
        return (response?.getStatusLine()?.getStatusCode() == 302);
    }

    /**
    * Copied from UDRestClient, but updated to allow the redirect request
    * used by the Worklight Authentication.
    */
    private HttpResponse invokeRedirectMethod(HttpRequestBase request)
    throws IOException, ClientProtocolException {
        HttpResponse response = client.execute(request);
        int status = response.getStatusLine().getStatusCode();
        // Allow the HTTP 302 redirect status.
        if (status > 299 && status != 302) {
            throw new IOException(String.format("%d %s\n%s",
                    response.getStatusLine().getStatusCode(),
                    response.getStatusLine().getReasonPhrase(),
                    getBody(response)));
        }
        return response;
    }

    /**
     * General POST helper method
     * param path URL to use with the POST call
     * param file File to attach to the POST call
     */
    private def post(String path, String file) {
        HttpPost postRequest = new HttpPost(this.url.toString() + path);
        postRequest.setHeader("Accept", "application/json")
        if (file) {
            if (file.endsWith(".json")) {
                postRequest = attachJson(postRequest, file)
            } else if (file.endsWith(".xml")) {
                postRequest = attachXml(postRequest, file)
            } else {
                postRequest = attachBinary(postRequest, file)
            }
        }
        HttpResponse response;
        try {
            response = this.invokeMethod(postRequest);
            println "Status Line: ${response?.getStatusLine()}"
        } catch (IOException e) {
            println "[MobileFirst v8] Unable to complete the POST request because of: " + e.getMessage();
        }

        return response?.getStatusLine()?.getStatusCode()?:-1
    }

    /**
     * General PUT helper method
     * param path URL to use with the PUT call
     * param file File to attach to the PUT call
     */
    private def put(String path, String file) {
        HttpPut putRequest = new HttpPut(this.url.toString() + path);
        putRequest.setHeader("Accept", "application/json")

        // Might need to consume application/json, application/xml, text/xml
        if (file) {
            if (file.endsWith(".json")) {
                putRequest = attachJson(putRequest, file)
            } else if (file.endsWith(".xml")) {
                putRequest = attachXml(putRequest, file)
            } else {
                putRequest = attachBinary(putRequest, file)
            }
        }
        HttpResponse response;
        try {
            response = this.invokeMethod(putRequest);
            println "Status Line: ${response?.getStatusLine()}"
        } catch (IOException e) {
            println "[MobileFirst v8] Unable to complete the POST request because of: " + e.getMessage();
        }

        return response?.getStatusLine()?.getStatusCode()?:-1
    }

    /**
     * General DELETE helper method
     * param path URL to use with the DELETE call
     */
    private def delete(String path) {
        HttpDelete deleteRequest = new HttpDelete(this.url.toString() + path);
        deleteRequest.setHeader("Accept", "application/json")

        HttpResponse response;
        try {
            response = this.invokeMethod(deleteRequest);
            println "Status Line: ${response?.getStatusLine()}"
        } catch (IOException e) {
            println "[MobileFirst v8] Unable to complete the POST request because of: " + e.getMessage();
        }

        return response?.getStatusLine()?.getStatusCode()?:-1
    }

    /**
     * param request The request to attach the json to
     * param file The file containing the json
     * return Request entity with json contents attached and header set
     */
    private def attachJson(def request, String file) {
        request.setHeader("Content-Type", "application/json")
        File f = new File(file)
        request.setEntity(new StringEntity(f.text))
        return request
    }

    /**
     * param request Attaches the xml to this request
     * param file The file containing the xml
     * return Request entity with xml contents attached and header set
     */
    private def attachXml(def request, String file) {
        request.setHeader("Content-Type", "application/xml")
        File f = new File(file)
        request.setEntity(new StringEntity(f.text))
        return request
    }

    /**
     * param request Attaches the binary text to this request
     * param file The file containing the binary text
     * return Request entity with binary contents attached and header set
     */
    private def attachBinary(def request, String file) {
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        File f = new File(file)
        builder.addBinaryBody("body",
            new FileInputStream(f),
            ContentType.APPLICATION_OCTET_STREAM,
            f.getName());
        request.setEntity(builder.build());
        return request
    }
}
