/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2017. 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.ubuild.plugin.dra

import com.urbancode.commons.httpcomponentsutil.CloseableHttpClientBuilder
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.entity.mime.HttpMultipartMode
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.entity.mime.content.FileBody
import org.apache.http.entity.StringEntity
import org.apache.http.HttpEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.util.EntityUtils

/**
 * Deployment Risk Analysis REST API: http://dlms.ng.bluemix.net/docs/#!/v2/post_build
 */

/**
 * Helper class for the Bluemix Deployment Risk Analysis Plugin.
 * Basic REST calls that assist in publishing test and build results.
 */
public class DRAHelper {

    private final static String CONTENT_TYPE_JSON = "application/json"
    private final static String CONTENT_TYPE_XML = "application/xml"

    private String serverUrl
    private String token
    private String orgId
    private String toolchainId
    private String buildArtifact
    private String buildId

    private CloseableHttpClient client

    /**
     * Basic contructor initialize all required parameters.
     * @param serverUrl The root URL to the Bluemix server. Default: https://dlms.ng.bluemix.net
     * @param token Bearer token to authenticate with Bluemix
     * @param orgId Bluemix organiziation id where the toolchain is located
     * @param toolchainId Bluemix toolchain ID that defines the project/application
     * @param buildArtifact The Application/Artifact's name in Bluemix
     * @param buildId This particular artifact's ID for publishing
     */
    public DRAHelper(String serverUrl, String token, String orgId, String toolchainId,
            String buildArtifact, String buildId) {

        // Add `bearer` back to token if removed by user
        if (!token.startsWith("bearer ")) {
            token = "bearer " + token
        }

        this.serverUrl    = cleanURL(serverUrl)?:"https://dlms.ng.bluemix.net" // Public Bluemix endpoint
        this.token        = token
        this.orgId        = orgId
        this.toolchainId  = toolchainId
        this.buildArtifact = buildArtifact
        this.buildId      = buildId?:System.getenv()["AH_BUILD_LIFE_ID"]
        this.client       = createClient()

        println "Server: ${this.serverUrl}"
        println "Organiziation ID: ${this.orgId}"
        println "Toolchain ID: ${this.toolchainId}"
        println "Artifact Name: ${this.buildArtifact}"
        println "Build ID: ${this.buildId}"
    }

    /**
     * Returns the CloseableHttpClient object that will connect and authenticate with Bluemix
     *
     * @return         Object contains the authentication information to connect with Bluemix
     * @see            CloseableHttpClient
     */
    private CloseableHttpClient createClient() {
        CloseableHttpClientBuilder clientBuilder = new CloseableHttpClientBuilder()
        //clientBuilder.setPreemptiveAuthentication(true)

        CloseableHttpClient client = clientBuilder.buildClient()
        return client
    }

    /**
     * If not null, it will close the object's CloseableHttpClient
     *
     * @return         Closes client that is no longer needed
     * @see            CloseableHttpClient
     */
    public void closeClient() {
        if (this.client) {
            this.client.close()
        }
    }

     /**
      * Calls a REST POST request and returns the CloseableHttpResponse
      *
      * @param uuUrl     The complete URL address of the REST POST call.
      * @param entity    The data structure that contains the information for the post request.
      * @return          The CloseableHttpResponse for the POST REST Call.
      */
      public CloseableHttpResponse doHttpPost(String url, def entity){

          // Create HttpPost Request and append entity
          // Publish Test -> MultipartEntityBuilder Form
          // Pubhlish Build -> JSON Content
          HttpPost postRequest = new HttpPost(url)
          postRequest.setHeader("Authorization", this.token)
          postRequest.setHeader("Accept", CONTENT_TYPE_JSON)
          if (entity instanceof StringEntity) {
              postRequest.setHeader("Content-Type", CONTENT_TYPE_JSON)
          }
          postRequest.setEntity(entity)

          // Execute the REST Call
          CloseableHttpResponse response = client.execute(postRequest)
          return response
      }

      /**
       * Publish Build result - http://dlms.ng.bluemix.net/docs/#!/v2/post_build
       *
       * @param buildStatus      `pass` or `fail` status of the given build
       * @param repositoryUrl    Repository URL of the built code
       * @param branch           Repository branch of the code
       * @param commitId         Commit ID of the code set
       * @return                 Success or failure of the posted build
       */
      public Boolean publishBuild(String buildStatus, String repositoryUrl, String branch, String commitId){

          // Publish Build REST API Endpoint
          final String API_PART = "/v2/organizations/ORG_NAME/toolchainids/TOOLCHAIN_ID/buildartifacts/BUILD_ARTIFACT/builds"

          // Build the complete Publish Build URL
          String fullUrl = this.serverUrl + API_PART
          fullUrl = fullUrl.replace("ORG_NAME", this.orgId)
          fullUrl = fullUrl.replace("TOOLCHAIN_ID", this.toolchainId)
          fullUrl = fullUrl.replace("BUILD_ARTIFACT", this.buildArtifact)
          println "REST Endpoint: " + fullUrl

          // Build the full JSON payload
          def repo = [:]
          repo << ["repository_url": repositoryUrl]
          repo << ["branch": branch]
          repo << ["commit_id": commitId]

          def json = [:]
          json << ["build_id": this.buildId]
          json << ["job_url": getUBuildJobURL()]
          json << ["status": buildStatus]
          json << ["timestamp": new Date().toString()]
          json << ["repository": repo]

          JsonBuilder builder = new JsonBuilder(json)
          StringEntity entity = new StringEntity(builder.toString(), "UTF-8")
          println "JSON Payload: " + builder.toString()

          // Send Post
          CloseableHttpResponse response = doHttpPost(fullUrl, entity)
          try {

              def statusLine = response.getStatusLine()
              def statusCode = statusLine.getStatusCode()

              println ""
              // Confirm the REST call was a success, fail otherwise
              if (statusCode == 200) {
                println "Publish Build action was successful - Message: ${response.entity?.content?.getText("UTF-8")}"
                return true
              }
              else if (statusCode == 400) {
                println "Publish Build Failure: Schema (input) validation failed."
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
              else if (statusCode == 403) {
                println "Publish Build Failure: Invalid Authorization token."
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
              else if (statusCode == 500) {
                println "Publish Build Failure: Error saving build record. Try again."
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
              else {
                println "Publish Build Failure: Unexpected Status Code"
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
          }
          finally {
              response.close()
          }
      }



      /**
       * Publish Test result - http://dlms.ng.bluemix.net/docs/#!/v2/results_multipart
       *
       * @param file            The test file to upload
       * @param environment     The environment where the tests were executed
       * @param lifecycleStage  The type of tests being uploaded
       */
      public Boolean publishTest(File file, String environment, String lifecycleStage){

          // Publish Test REST API Endpoint
          final String API_PART = "/v2/organizations/ORG_NAME/toolchainids/TOOLCHAIN_ID/buildartifacts/BUILD_ARTIFACT/builds/BUILD_ID/results_multipart"

          // Build the complete Publish Test URL
          String fullUrl = this.serverUrl + API_PART
          fullUrl = fullUrl.replace("ORG_NAME", this.orgId)
          fullUrl = fullUrl.replace("TOOLCHAIN_ID", this.toolchainId)
          fullUrl = fullUrl.replace("BUILD_ARTIFACT", this.buildArtifact)
          fullUrl = fullUrl.replace("BUILD_ID", this.buildId)

          // Build the full MultipartEntity payload
          MultipartEntityBuilder builder = MultipartEntityBuilder.create()

          builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
          builder.addPart("contents", new FileBody(file))

          if (file.getName().endsWith("xml")) {
              builder.addTextBody("contents_type", CONTENT_TYPE_XML)
          } else if (file.getName().endsWith("json")) {
              builder.addTextBody("contents_type", CONTENT_TYPE_JSON)
          } else {
              throw new RuntimeException("[Error] Ensure your test file has a .json or .xml extension.")
          }

          builder.addTextBody("test_artifact", file.getName())

          builder.addTextBody("url", getUBuildJobURL())

          if (environment) {
              builder.addTextBody("environment_name", environment)
          }

          if (lifecycleStage) {
              builder.addTextBody("lifecycle_stage", lifecycleStage)
          }

          builder.addTextBody("timestamp", new Date().toString())

          HttpEntity entity = builder.build()

          // Sent Post
          CloseableHttpResponse response = doHttpPost(fullUrl, entity)
          try {

              def statusLine = response.getStatusLine()
              def statusCode = statusLine.getStatusCode()

              // Confirm the REST call was a success, fail otherwise
              if (statusCode == 200) {
                println "Publish Test `${file.getName()}` action was successful - Message: ${response.entity?.content?.getText("UTF-8")}"
                return true
              }
              else if (statusCode == 400) {
                println "Publish Test `${file.getName()}` Failure: Input is invalid"
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
              else if (statusCode == 403) {
                println "Publish Test `${file.getName()}` Failure: Invalid Authorization token."
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
              else {
                println "Publish Test `${file.getName()}` Failure: Unexpected Status Code"
                println "Error - StatusCode: ${statusCode} - Message: ${response.entity?.content?.getText("UTF-8")}"
                return false
              }
          }
          finally {
              response.close()
          }
      }

      /**
       * Confirm that the URL begins with either http or https. If it does not, then https:// will
       * be appended to the front of the string.
       *
       * @param url The string representation of a URL
       * @return    String that beings with http, otherwise https:// will be appended to the front
       *        and trailing forward slashes will be removed.
       */
      public String cleanURL(String url) {
          if (url && !url.startsWith("http")) {
              url = "https://" + url
          }

          if (url.endsWith("/")) {
              url = url.substring(0, url.length()-1)
          }

          return url
      }

      /**
       * Constructs the AnthillPro Job URL based on given environment variables
       *
       * @return Full URL of the AnthillPro job that created this Post request
       */
      private String getUBuildJobURL() {
          final String BUILD_LIFE_PATH = "/tasks/project/BuildLifeTasks/viewBuildLife?buildLifeId="

          def env = System.getenv()
          final String WEB_URL = env["WEB_URL"]
          final String BUILD_LIFE_ID = env["BUILD_LIFE_ID"]

          return cleanURL(WEB_URL) + BUILD_LIFE_PATH + BUILD_LIFE_ID
      }
}
