package com.urbancode.air.plugin.odm

import java.io.IOException
import java.net.URI
import java.net.URISyntaxException
import java.util.List
import java.util.Properties
import org.apache.http.HttpEntity
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.NameValuePair
import org.apache.http.StatusLine
import org.apache.http.client.ClientProtocolException
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpUriRequest
import org.apache.http.client.utils.URIBuilder
import org.apache.http.message.BasicNameValuePair
import org.apache.http.util.EntityUtils
import com.urbancode.air.AirPluginTool
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import groovy.json.JsonSlurper

public class OdmRestClient {
    
    private final JsonSlurper jsonslurper = new JsonSlurper()

    private HttpClient client
    private String url
    private String apiBasePath

    private String datasource

    private final String API_VERSION = 'v1'
    private final String API_BASE_PATH_DEFAULT = '/decisioncenter-api/' + API_VERSION

    def OdmRestClient(AirPluginTool airTool) throws Exception {
        Properties props = airTool.getStepProperties()

        datasource = props['datasource']

        String password = props['password']
        String username = props['username']

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

        client = clientBuilder.buildClient()

        // Get the base url by stripping the path off of the user-specified url
        def uri = new URI(props['odmUrl'].trim())
        url = uri.getScheme() + '://' + uri.getAuthority()

        // Get the user-specified url path and remove trailing slashes
        def userSpecifiedPath = uri.getPath()
        while (userSpecifiedPath && userSpecifiedPath.endsWith('/')) {
            userSpecifiedPath = userSpecifiedPath.substring(0, userSpecifiedPath.length()-1)
        }

        /* Look for the ODM REST API by trying a list of paths:
         * 1. user-specified url path
         * 2. default path hardcoded in this class
         */
        def pathsToTry = [
            userSpecifiedPath,
            API_BASE_PATH_DEFAULT,
        ].unique() - ''
        pathsToTry = pathsToTry.iterator()

        def apiFound = false

        println 'Looking for ODM REST API'
        while (!apiFound && pathsToTry.hasNext()) {
            apiBasePath = pathsToTry.next()

            print "  ${url}${apiBasePath} ... "

            try {
                HttpResponse response = getAbout()
                StatusLine statusLine = response.getStatusLine()

                println statusLine.getReasonPhrase()

                apiFound = statusLine.getStatusCode() == HttpStatus.SC_OK
            } catch (Exception e) {
                println e.getMessage()
            }
        }

        if (!apiFound) {
            throw new Exception('REST API not found, falling back to Java client')
        }
    }
    
    private HttpResponse doHttpGet(String path, List<NameValuePair> query = null) throws URISyntaxException, ClientProtocolException, IOException {
        URIBuilder uribuilder = new URIBuilder(url + apiBasePath + path)
        if (query) {
            uribuilder.setParameters(query)
        }
        URI uri = uribuilder.build()

        HttpUriRequest request = new HttpGet(uri)

        return client.execute(request)
    }

    private httpResponseToMap(HttpResponse response){
        HttpEntity responseEntity = response.getEntity()
        String json = EntityUtils.toString(responseEntity)

        return jsonslurper.parseText(json)
    }

    private getAbout() {
        String path = '/about'

        doHttpGet(path)
    }

    private getDecisionServices(String q = null) {
        String path = '/decisionservices'

        List<NameValuePair> query = []
        if (q) {
            query.add(new BasicNameValuePair('q', q))
        }

        doHttpGet(path, query)
    }

    private getDeploymentsForDecisionService(String decisionServiceId, String q = null) {
        String path = "/decisionservices/${decisionServiceId}/deployments"

        List<NameValuePair> query = []
        if (q) {
            query.add(new BasicNameValuePair('q', q))
        }

        doHttpGet(path, query)
    }

    private getRuleappArchiveForDeployment(String deploymentId) {
        String path = "/deployments/${deploymentId}/download"

        doHttpGet(path)
    }

    def getDecisionService(String name) {
        def query = name ? "name:$name" : null

        HttpResponse response = getDecisionServices(query)
        StatusLine statusLine = response.getStatusLine()

        if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
            println 'Error getting decision services: ' + statusLine
            System.exit(1)
        }

        def result = httpResponseToMap(response)
        if (!result.size) {
            println "Decision service with name \"${name}\" not found."
            System.exit(1)
        }

        result.elements.first()
    }

    def getDeployment(String decisionServiceId, String name) {
        def query = name ? "name:$name" : null

        HttpResponse response = getDeploymentsForDecisionService(decisionServiceId, query)
        StatusLine statusLine = response.getStatusLine()

        if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
            println 'Error getting deployments: ' + statusLine
            System.exit(1)
        }

        def result = httpResponseToMap(response)
        if (!result.size) {
            println "Deployment with name \"${name}\" not found."
            System.exit(1)
        }

        result.elements.first()
    }

    def downloadRuleappArchive(String deploymentId, String filename) {
        HttpResponse response = getRuleappArchiveForDeployment(deploymentId)
        StatusLine statusLine = response.getStatusLine()

        if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
            println 'Error downloading rulapp archive: ' + statusLine
            System.exit(1)
        }

        HttpEntity entity = response.getEntity()
        if (entity == null) {
            println 'Error downloading ruleapp archive: API response did not contain a file entity.'
            System.exit(1)
        }

        File outputFile = new File(filename)
        try {
            FileOutputStream fos = new FileOutputStream(outputFile)
            entity.writeTo(fos)
        } catch (Exception e) {
            println 'Error writing ruleapp archive to file: ' + e.getMessage()
            System.exit(1)
        }

        outputFile
    }
}
