/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2013. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
package com.urbancode.air.plugin.servicenow

import com.urbancode.air.AirPluginTool
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.httpcomponentsutil.PreemptiveAuthHttpClient

import groovyx.net.http.*

import org.apache.http.client.methods.HttpEntityEnclosingRequestBase
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPut
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpPatch
import org.apache.http.client.methods.HttpDelete
import org.apache.http.entity.StringEntity
import org.apache.http.Header
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.util.EntityUtils

import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import groovy.util.slurpersupport.GPathResult

public class HelperRestClientJsonV1 {

    def apTool
    def auth
    String changeRequestId
    DefaultHttpClient client
    HttpClientBuilder clientBuilder
    def password
    def props = []
    def proxyHost
    def proxyPass
    def proxyPort
    def proxyUser
    def serverUrl
    def username

    public HelperRestClientJsonV1(def apToolIn) {
        apTool = apToolIn
        props = apTool.getStepProperties()

        changeRequestId = props['changeRequestId']
        password = props['password']
        proxyHost = props['proxyHost']
        serverUrl = props['serverUrl']
        username = props['username']

        println "Using serverUrl ${serverUrl}"
        println "Using username ${username}"

        while (serverUrl.endsWith('/')) {
            serverUrl = serverUrl.substring(0, serverUrl.length() - 1);
        }

        if (!serverUrl.toLowerCase().startsWith("http", 0)) {
            serverUrl = "https://" + serverUrl
        }

        clientBuilder = new HttpClientBuilder()
        clientBuilder.setUsername(username)
        clientBuilder.setPassword(password)
        clientBuilder.setPreemptiveAuthentication(true)

        if (proxyHost != null && !proxyHost.isEmpty()) {
            proxyPass = props['proxyPass']
            proxyPort = props['proxyPort']
            proxyUser = props['proxyUser']

            println "Using Proxy Host ${proxyHost}"
            println "Using Proxy Port ${proxyPort}"
            println "Using Proxy User ${proxyUser}"

            clientBuilder.setProxyHost(proxyHost)
            clientBuilder.setProxyPassword(proxyPass)
            clientBuilder.setProxyPort(Integer.valueOf(proxyPort))
            clientBuilder.setProxyUsername(proxyUser)
        }

        client = clientBuilder.buildClient()
    }

    // Issues a GET API V1 call with an encoded query (number=) and returns approval value
    def String getApproval() {
        def parsedJson = getRecords('change_request?sysparm_query=number=' + changeRequestId)
        if (parsedJson.result.size() == 0) {
            println "No Change Request found with ID ${changeRequestId}. Exiting failure."
            System.exit(1)
        }
        return parsedJson.result[0].approval
    }

    // Issues a GET API V1 call with an encoded query (number=) and returns 'state' value
    def Integer getStatus() {
        def parsedJson = getRecords('change_request?sysparm_query=number=' + changeRequestId)
        if (parsedJson.result.size() == 0) {
            println "No Change Request found with ID ${changeRequestId}. Exiting failure."
            System.exit(1)
        }
        return Integer.valueOf(parsedJson.result[0].state)
    }

    // Issues a GET API V1 call with an encoded query (number=) and returns 'sys_id' value
    def String getChangeRequestSysId() {
        def parsedJson = getRecords('change_request?sysparm_query=number=' + changeRequestId)
        if (parsedJson.result.size() == 0) {
            println "No Change Request found with ID ${changeRequestId}. Exiting failure."
            System.exit(1)
        }
        return parsedJson.result[0].sys_id
    }

    //// Issues a GET API V1 call with an encoded query (number=) and returns JSON body
    def getTaskList(String changeRequestSysId) {
        def parsedJson = getRecords('change_task?sysparm_query=change_request=' + changeRequestSysId)
        return parsedJson
    }

    // Issues a PATCH API V1 call to edit the state value of a change task(identified by sys_id)
    def setTaskStatus(String taskId, String status) {
        HttpPatch patch = new HttpPatch(serverUrl + '/api/now/v1/table/change_task/' + taskId)
        patch.addHeader("Accept", "application/json")
        patch.addHeader("Content-Type", "application/json")
        String updateInfo = "{'state':'" + status + "'}"
        StringEntity postEntity = new StringEntity(updateInfo)
        patch.setEntity(postEntity)
        println "Setting task ${taskId} with status ${status}"
        println updateInfo

        def resp = client.execute(patch)
        def statusCode = resp.getStatusLine().getStatusCode()
        if (statusCode < 200 || statusCode >= 300) {
            println "Request failed with status ${statusCode}. Exiting Failure."
            System.exit(1)
        }

        def newStatus = getTaskStatus(taskId)
        if (newStatus != Integer.valueOf(status)) {
            println "Status was not updated. Exiting Failure"
            System.exit(1)
        }
    }

    // Issues a GET API V1 call to a Change Task determined by encoded query (sys_id=)
    // and returns its State value
    def Integer getTaskStatus(String taskId) {
        def parsedJson = getRecords('change_task?sysparm_query=sys_id=' + taskId)

        if (parsedJson.result.size() == 0) {
            println "No Change Request found with ID ${changeRequestId}. Exiting failure."
            System.exit(1)
        }

        return Integer.valueOf(parsedJson.result[0].state)
    }

    // Sets the status of a Change Request
    def setStatus(String status) {
        HttpEntityEnclosingRequestBase method = null
        def sysId = getChangeRequestSysId()

        method = new HttpPut(serverUrl + '/api/now/v1/table/change_request/' + sysId)
        method.addHeader("Accept", "application/json")
        method.addHeader("Content-Type", "application/json")

        String updateInfo = "{'state':'" + status + "'}"
        StringEntity postEntity = new StringEntity(updateInfo)
        method.setEntity(postEntity)

        def resp = client.execute(method)

        def statusCode = resp.getStatusLine().getStatusCode()
        if (statusCode < 200 || statusCode >= 300) {
            println "Request failed with status ${statusCode}. Exiting Failure."
            System.exit(1)
        }

        def newStatus = getStatus()
        if (newStatus != Integer.valueOf(status)) {
            println "Status was not updated. Exiting Failure"
            println EntityUtils.toString(resp.getEntity())
            System.exit(1)
        }
    }

    // Insert a table row macthing TableName using the POST API V1 with a JSON
    // Reqeust Body
    def insertRow() {
        def tableName = props['table']
        def unparsedFields = props['fields']

        def fields = [:]

        HttpPost post = new HttpPost(serverUrl + "/api/now/v1/table/" + tableName)
        post.addHeader("Accept", "application/json")
        post.addHeader("Content-Type", "application/json")

        def nameValuePairs = unparsedFields.split("\n")
        nameValuePairs.each { nameValue ->
            try {
                def delim = nameValue.indexOf(":")
                def name = nameValue.substring(0, delim)
                def value = nameValue.substring(delim+1)
                fields[name] = value
            } catch (IndexOutOfBoundsException e) {
                throw new IllegalArgumentException("The syntax of your input field was incorrect for field $nameValue")
            }
        }
        def jsonMap = new JsonBuilder(fields)
        StringEntity postEntity = new StringEntity(jsonMap.toString())
        post.setEntity(postEntity)

        def resp = client.execute(post)

        def statusCode = resp.getStatusLine().getStatusCode()
        if (statusCode != 201) {
            println "Request failed with status ${statusCode}. Exiting Failure."
            System.exit(1)
        }
    }

    // Deletes a table row macthing TableName and sys_id
    def deleteRow() {
        def tableName = props['table']
        def id = props['id']

        println "Deleting row from table ${tableName}"
        println "Deleting row with Id ${id}"

        deleteRecord(tableName + "/" + id)
    }

    // Deletes all Table rows macthing a TableName and an encoded query
    def deleteMultipleRows() {
        def condition = props['condition']
        def tableName = props['table']


        while (condition.startsWith('=')) {
            condition = condition.substring(1, condition.length())
        }

        println "Deleting row from table ${tableName}"
        println "Deleting rows with condition ${condition}"

        def queryJson = getRecords(tableName + '?sysparm_query=' + condition)
        def querySize = queryJson.result.size()

        for (int i = 0; i < querySize; i++) {
            deleteRecord(tableName + "/" + queryJson.result.sys_id[i])
            println "Deleted " + tableName + " record with sys_id=" + queryJson.result.sys_id[i]
        }
    }

//////////////////////////////// HELPER METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    // Excutes ServiceNows V1 REST API DELETE call. input form: {TableName}/{sys_id}
    def deleteRecord(String tableAndSys_id) {
        HttpDelete delete = new HttpDelete(serverUrl + "/api/now/v1/table/" + tableAndSys_id)
        delete.addHeader("Accept", "application/json")
        delete.addHeader("Content-Type", "application/json")

        def resp = client.execute(delete)
        def statusCode = resp.getStatusLine().getStatusCode()
        if (statusCode != 204) {
            println "Request failed with status ${statusCode}. Exiting Failure."
            System.exit(1)
        }
    }

    // Accepts any string compatible with ServiceNow's API GET Retreive Record appended to serverUrl + '/api/now/v1/table/'
    def getRecords(String query) {
        def parsedJson

        def encodedQuery = encodeQuery(query)

        HttpGet get = new HttpGet(serverUrl + '/api/now/v1/table/' + encodedQuery)
        get.addHeader("Accept", "application/json")
        def resp = client.execute(get)

        def statusCode = resp.getStatusLine().getStatusCode()
        if (statusCode < 200 || statusCode >= 300) {
            println "Request failed with status ${statusCode}. Exiting Failure."
            System.exit(1)
        }
        def entity = EntityUtils.toString(resp.getEntity())

        def slurper = new JsonSlurper()
        try {
            parsedJson = slurper.parseText(entity)
        }
        catch (groovy.json.JsonException e) {
            println ""
            println "Failed to parse response body. Printing useful debugging information and exiting."
            println ""
            println "Response status code: ${statusCode}."
            println ""
            println "Header:"
            Header[] headers = resp.getAllHeaders()
            for (Header header : headers) {
                System.out.println(header.getName() + ":"+ header.getValue())
                }
            println ""
            println "Body:"
            println entity
            println ""
            println "Stacktrace:"
            e.printStackTrace()
            System.exit(1)
        }
        return parsedJson
    }

    // Custom URL econder for Service Now API
    def encodeQuery(String query) {
        query = query.replaceAll("\\<", "%3C")
        query = query.replaceAll("\\>", "%3E")
        query = query.replaceAll("\\{", "%7B")
        query = query.replaceAll("}", "%7D")
        query = query.replaceAll("\\^","%5E")
        query = query.replaceAll("\\ ","%20")
        return query
    }
}