/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Build
 * (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.automation

import groovy.io.FileType;
import java.io.BufferedWriter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.entity.FileEntity
import org.apache.http.impl.client.DefaultHttpClient


import com.urbancode.air.*
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO


public class UploadReport {
    def reportXml
    def reportName
    CommandHelper ch

    final def out = System.out
    final def workDir = new File('.').canonicalFile
    final def apTool

    //----------------------------------------------------------------
    public UploadReport() {
        ch = new CommandHelper(workDir);
    }

    //----------------------------------------------------------------
    public void parseAndUploadResults() {

        def reportXml = produceXml(parseXml());

        if (reportXml) {
            uploadResult(reportXml.toString(), reportName, false)
        }
        else {
            println 'No report was able to be generated'
        }
    }

    //----------------------------------------------------------------
    public def produceXml(List<Map<String, String>> findings) {
        //return the xml to be uploaded
        final def buildLifeId = System.getenv()['BUILD_LIFE_ID']
        def xml = new groovy.xml.StreamingMarkupBuilder().bind {
            mkp.xmlDeclaration()

            analytics (
                name:reportName, buildLifeId:buildLifeId, type:'AppScanSource') {

                findings.each { findingMap ->
                    finding() {
                        id(findingMap['findingId'])
                        file(findingMap['findingFile'])
                        line(findingMap['findingLine'])
                        name(findingMap['findingName'])
                        severity(findingMap['findingSeverity'])
                        description(findingMap['findingDesc'])
                        status(findingMap['findingStatus'])
                    }
                }
            }
        }
        return xml;
    }

    //----------------------------------------------------------------
    public List<Map<String, String>> parseXml() {
        //return a list of findings maps
        File xmlFile = new File(reportXml);
        List<Map<String,String>>  result = new ArrayList<Map<String,String>>();

        def xmlSlurpy = new XmlSlurper().parse(new FileInputStream(xmlFile));

        //add findings to the list
        //@error_status=1 means an error

        System.out.println("Parsing results Xml file : " + xmlFile.getAbsolutePath())
        xmlSlurpy.Assessment[0].Assessment.each { assessment ->
            System.out.println("found assessment!");

            assessment.AsmntFile.each { asmntFile ->
                //get the fileId and name
                String fileId = asmntFile.@file_id.text()
                String fileName =
                        xmlSlurpy.FilePool[0].File.find { it.@id.text().equals(fileId) }.@value.text()

                //get the error_status and make sure its not 1
                if (asmntFile.AssessmentStats[0].@error_status.text().equals("1")) {
                    System.out.println("Found record with error for file : " + fileName);
                    //add an error entry
                    Map<String, String> tempMap = new HashMap<String, String>();

                    tempMap.put('findingId', fileId)
                    tempMap.put('findingFile', fileName)
                    tempMap.put('findingName', "Error Scanning")
                    tempMap.put('findingSeverity', "Error")
                    result.add(tempMap)
                }
                else {

                    //get the filename
                    System.out.println("Found record for file : " + fileName);

                    //iterate through all the findings for the file and add the finding to the result list
                    asmntFile.Finding.each { finding ->

                        //get the actual FindingData entry to get interesting things like severity
                        String findingId = finding.@data_id.text();
                        def finding_data =
                                xmlSlurpy.FindingDataPool[0].FindingData.find { it.@id.text().equals(findingId) }

                        //get the finding name from the type
                        String findingTypeId = finding_data.@vtype.text()
                        def findingType =
                                xmlSlurpy.StringPool[0].String.find { it.@id.text().equals(findingTypeId) }.@value.text()

                        //determine severity level
                        String sev = finding_data.@sev.text();
                        if (sev.equals("0")) {
                            sev = "High"
                        }
                        else if (sev.equals("1")) {
                            sev = "Med"
                        }
                        else if (sev.equals("2")) {
                            sev = "Low"
                        }
                        else if (sev.equals("3")) {
                            sev = "Info"
                        }

                        Map<String, String> tempMap = new HashMap<String, String>();

                        tempMap.put('findingId', findingId)
                        tempMap.put('findingFile', fileName)
                        tempMap.put('findingName', findingType)
                        tempMap.put('findingSeverity', sev)
                        //tempMap.put('findingLine', finding.line)
                        //tempMap.put('findingDesc', finding.name)
                        //tempMap.put('findingStatus', finding.status)

                        //add to list
                        result.add(tempMap);
                    }
                }
            }
        }
        return result;
    }

    public void uploadResult(def reportFileContent, String reportName, boolean includeDescription) {
        def buildLifeId = System.getenv("BUILD_LIFE_ID")

            println reportFileContent

            String baseUrl = System.getenv("WEB_URL")
            baseUrl += baseUrl.endsWith("/") ? "" : "/"
            def encodedReportName = URLEncoder.encode(reportName)
            String url = baseUrl + "rest/buildlife/${buildLifeId}/sourceanalytics?reportName=${encodedReportName}"
            sendPostRequest(url, reportFileContent)
    }

    //------------------------------------------------------------------------------
    // Construct XML message and upload
    //------------------------------------------------------------------------------
    private void sendPostRequest(String url, String xmlFile) {
        println "Sending request to $url"
        HttpClientBuilder clientBuilder = new HttpClientBuilder();
        clientBuilder.setTrustAllCerts(true);
        DefaultHttpClient client = clientBuilder.buildClient();

        HttpPost postMethod = new HttpPost(url);
        def authToken = System.getenv("AUTH_TOKEN")
        if (authToken) {
            postMethod.setHeader("Authorization-Token", authToken)
            postMethod.setHeader("Content-Type", "application/xml")
        }

        postMethod.setEntity(new StringEntity(xmlFile));

        def httpResponse = client.execute(postMethod)
        def responseCode = httpResponse.getStatusLine().getStatusCode()
        if (isGoodResponseCode(responseCode)) {
            IO.copy(postMethod.getEntity().getContent(), System.out)
        }
        else {
            IO.copy(postMethod.getEntity().getContent(), System.err)
            throw new RuntimeException("Failed to upload RationalAppScanSource report. StatusCode: ${responseCode}")
        }
    }

    private boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }

}