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.HttpPost
import org.apache.http.client.entity.UrlEncodedFormEntity
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 String accessToken
    private String headerParam
    private String tokenUrlValue

    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']
        String tokenUrl = props['tokenUrl']
        String oAuthusername = props['oAuthusername']
        String oAuthpassword = props['oAuthpassword']
        String scope = (props['scope']!=null && props['scope'].length()>0) ? props['scope'].trim() : props['scope']
        String grantType = (props['grantType']!=null && props['grantType'].length()>0) ? props['grantType'].trim() : props['grantType']
        String clientID = props['clientID']
        String clientSecret = props['clientSecret']
        tokenUrlValue = tokenUrl

        HttpClientBuilder clientBuilder = new HttpClientBuilder()
        clientBuilder.setPassword(password)
        clientBuilder.setUsername(username)
        client = clientBuilder.buildClient()

        if(tokenUrl !=null && tokenUrl.length()>0) {
            clientBuilder.setPreemptiveAuthentication(false)
            HttpPost getTokenMethod = new HttpPost(tokenUrl);
            getTokenMethod.addHeader("Accept", "application/json");
            getTokenMethod.addHeader("Content-Type", "application/x-www-form-urlencoded");

            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
            nameValuePairs.add(new BasicNameValuePair("scope", scope));
            nameValuePairs.add(new BasicNameValuePair("grant_type", grantType));
            nameValuePairs.add(new BasicNameValuePair("client_id", clientID));
            nameValuePairs.add(new BasicNameValuePair("client_secret", clientSecret));
            if(grantType != 'client_credentials') {
                nameValuePairs.add(new BasicNameValuePair("username", oAuthusername));
                nameValuePairs.add(new BasicNameValuePair("password", oAuthpassword));
            }
            
            getTokenMethod.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
            def responseToken = client.execute(getTokenMethod);
            def entity = EntityUtils.toString(responseToken.getEntity());
            def slurper = new JsonSlurper();
            def parsedJson = slurper.parseText(entity)
            accessToken = parsedJson.access_token;
            headerParam = "Bearer "+ accessToken
        } else {
            clientBuilder.setPreemptiveAuthentication(true)
        }
        
        // 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)
        if(tokenUrlValue !=null && tokenUrlValue.length()>0) {
            def headerDataBefore = request.getHeaders("Authorization")
            request.addHeader("Authorization", headerParam )
            def headerDataAfter = request.getHeaders("Authorization")
        }
        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
    }
}
