/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * IBM Urbancode Release
 * (c) Copyright IBM Corporation 2016. 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.marathon

import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpDelete
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpPut
import org.apache.http.client.methods.HttpUriRequest
import org.apache.http.entity.StringEntity
import org.apache.http.HttpResponse
import org.apache.http.util.EntityUtils
import java.io.File
import java.util.concurrent.TimeUnit
import groovy.json.JsonSlurper
import com.urbancode.air.XTrustProvider
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder

public class MarathonRestHelper {
    def app
    def baseUrl
    def client
    def disableCerts
    def slurper
    def timeout

    // Scale parameters
    def instances

    public MarathonRestHelper(props) {
        this.app = props['app']
        this.baseUrl = props['baseUrl']
        this.disableCerts = props['disableCerts']
        this.slurper = new JsonSlurper()
        this.instances = props['instances']
        this.timeout = props['timeout']

        if (!baseUrl.substring(baseUrl.length() - 1).equals("/")) {
            baseUrl += "/"
        }

        HttpClientBuilder builder = new HttpClientBuilder()

        if (disableCerts) {
            XTrustProvider.install()
            builder.setTrustAllCerts(true)
        }
        builder.setPreemptiveAuthentication(true)

        this.client = builder.buildClient()

        System.setProperty("https.protocols", "TLSv1")
    }

    private void killAndScale() {
        HttpDelete killRequest = new HttpDelete(baseUrl + "v2/apps/${app}/tasks")
        HttpResponse response = doRequest(killRequest)
        def statusLine = response.getStatusLine()
        def statusCode = statusLine.getStatusCode()
        if (statusCode != 200) {
            println("Task kill failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            throw new RuntimeException(response.entity?.content?.text)
        } else {
            println("Tasks from ${app} successfully killed and scaled.")
        }
    }

    private void launchNewApplication() {
        String jsonString = new File(app).text
        def id = slurper.parseText(jsonString)?.id
        if (!id) {
            throw new RuntimeException("JSON file either does not contain required id field or the id is invalid.")
        }

        HttpPost launchRequest = new HttpPost(baseUrl + "v2/apps")
        HttpResponse response = doRequest(launchRequest, jsonString)

        def responseString = EntityUtils.toString(response.getEntity())

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

        if (statusCode != 201) {
            println("Launch failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            throw new RuntimeException(responseString)
        } else {
            println("New Application ${id} was successfully created.")
        }

        // Now check if app is ready
        def ready = false
        long startTime = System.currentTimeMillis()
        while ((ready == false) && (System.currentTimeMillis()-startTime<(timeout.toInteger())*1000)) {
            HttpGet checkRequest = new HttpGet(baseUrl + "v2/apps/" + id)
            response = doRequest(checkRequest)
            statusLine = response.getStatusLine()
            statusCode = statusLine.getStatusCode()
            if (statusCode == 200) {
                ready = true
            }
            // Status code of 404 means app has not been created yet. Wait until timeout completes to give app time to launch.
            else if (statusCode != 404) {
                throw new RuntimeException("Check failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            }
            TimeUnit.SECONDS.sleep(1);
        }
        if (!ready) {
            if (statusCode == 404) {
                println("Timed out: Application ${id} is not yet created.")
            }
            else {
                println("Timed out: Application ${id} is created but not yet running.")
            }
            System.exit(1)
        }
        else {
            println("Application was successfully started.")
        }

    }

    private boolean checkAppExists() {
        HttpGet getRequest = new HttpGet(baseUrl + "v2/apps/${app}")
        HttpResponse response = doRequest(getRequest)
        def statusLine = response.getStatusLine()
        def statusCode = statusLine.getStatusCode()
        def exists
        if (statusCode == 404) {
            exists = false
        }
        else {
            exists = true
        }
        return  exists
    }

    private void removeApplication() {
        if (!checkAppExists()) {
            println("${app} doesn't exist.")
        }
        else {
            HttpDelete removeRequest = new HttpDelete(baseUrl + "v2/apps/${app}")
            HttpResponse response = doRequest(removeRequest)
            def statusLine = response.getStatusLine()
            def statusCode = statusLine.getStatusCode()

            if (statusCode< 200 || statusCode >= 300) {
                println("Removal of ${app} failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            } else {
                println("${app} removed successfully.")
            }
        }
    }

    private void scaleApplication() {
        if(!checkAppExists()) {
            println("${app} does not exist.")
        }
        else {
            HttpPut scaleRequest = new HttpPut(baseUrl + "v2/apps/" + app)
            HttpResponse response = doRequest(scaleRequest, "{\"instances\": ${instances}}")
            def statusLine = response.getStatusLine()
            def statusCode = statusLine.getStatusCode()

            if (statusCode< 200 || statusCode >= 300) {
                println("Scale failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            } else {
                println("Success, ${app} scaled by ${instances} instance(s).")
            }
        }
    }

    // Execute a general http request
    private HttpResponse doRequest(HttpUriRequest request) {
        return doRequest(request, null)
    }

    // Specify a json string to provide a string entity to the http request
    private HttpResponse doRequest(HttpUriRequest request, String contentString) {
        request.addHeader("Content-Type", "application/json")
        request.addHeader("Accept", "application/json,application/xml")

        if (contentString) {
            StringEntity input

            try {
                input = new StringEntity(contentString)
            }
            catch (UnsupportedEncodingException ex) {
                println("Unsupported characters in http request content: ${contentString}")
                System.exit(1)
            }

            request.setEntity(input)
        }

        HttpResponse response = client.execute(request)

        return response
    }
}