import com.offbytwo.jenkins.JenkinsServer;
import com.offbytwo.jenkins.client.JenkinsHttpClient;
import com.offbytwo.jenkins.model.Build;
import com.offbytwo.jenkins.model.BuildResult;
import com.offbytwo.jenkins.model.BuildWithDetails;
import com.offbytwo.jenkins.model.Job;
import com.offbytwo.jenkins.model.JobWithDetails;
import org.apache.http.client.HttpResponseException;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 Copyright 2016 Alianza Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 */

public class JenkinsHelper {
    JSONObject config;
    JenkinsHttpClient client;
    JenkinsServer jenkins;
    boolean crumbsFlag = false;

    public JenkinsHelper(String configFile) throws JSONException, URISyntaxException, IOException {
        // parse the config file
        config = ConfigParser.parseConfigFile(configFile);

        // crumbs flag will not work without "Prevent Cross Site Request Forgery Exploits" enabled in jenkins
        if (config.has("crumbsFlag"))
            crumbsFlag = config.getBoolean("crumbsFlag");
        URI jenkinsURI = new URI(config.getString("jenkinsURI"));
        String username = config.getString("username");
        String token = config.getString("token");

        // setup our client
        client = new JenkinsHttpClient(jenkinsURI, username, token);
        jenkins = new JenkinsServer(client);
    }

    public void createJob(String test, JSONObject repository) throws Exception {
        String jobName = ConfigParser.parseConfigString(repository.getString("jobName"), test);

        String jobXML = generateJobXML(test, repository);
        Map<String, Job> jobs = jenkins.getJobs();
        if (jobs.containsKey(jobName.toLowerCase())) {
            jenkins.updateJob(jobName.toLowerCase(), jobXML, crumbsFlag);
            System.out.println("Updating [" + test + "]");
        }
        else {
            jenkins.createJob(jobName, jobXML, crumbsFlag);
            System.out.println("Creating [" + test + "]");
        }
        if (repository.has("addToView")) {
            String addToView = repository.getString("addToView");
            addJobToView(jobName, addToView);
        }
    }

    public void runJobIfFailing(String test, JSONObject repository) throws Exception {
        String jobName = ConfigParser.parseConfigString(repository.getString("jobName"), test);

        JobWithDetails job = jenkins.getJob(jobName);
        if (job == null) {
            System.out.println("Job not yet created, skipping: [" + jobName + "]");
            return;
        }
        Build lastBuild = job.getLastBuild();
        if (lastBuild == null) {
            runJob(test, repository);
            return;
        }
        BuildResult lastResult = lastBuild.details().getResult();
        if (lastResult != null && lastResult.equals(BuildResult.FAILURE)) {
            runJob(test, repository);
        }
    }

    public void runJob(String test, JSONObject repository) throws Exception {
        String jobName = ConfigParser.parseConfigString(repository.getString("jobName"), test);
        Map<String, String> params = new HashMap<>();
        if (repository.has("buildParams")) {
            JSONObject buildParams = repository.getJSONObject("buildParams");
            Iterator keys = buildParams.keys();
            while (keys.hasNext()) {
                String key = (String) keys.next();
                params.put(key, buildParams.getString(key));
            }
            System.out.println("Running job with parameters [" + jobName + "]");
            runJob(test, repository, params);
        }
        else {
            System.out.println("Running job without parameters [" + jobName + "]");
            try {
                jenkins.getJob(jobName).build(crumbsFlag);
            } catch (HttpResponseException e) {
                System.out.println("Running job [" + jobName + "] without parameters failed, were there supposed to be parameters?");
                e.printStackTrace();
            }
        }
    }

    public void runJob(String test, JSONObject repository, Map<String, String> params) throws Exception {
        String jobName = ConfigParser.parseConfigString(repository.getString("jobName"), test);

        try {
            jenkins.getJob(jobName).build(params, crumbsFlag);
        } catch (HttpResponseException e) {
            System.out.println("Running job [" + jobName + "] with parameters failed, params sent:");
            System.out.println(params);
            e.printStackTrace();
        }
    }

    private String generateJobXML(String test, JSONObject repository) throws Exception {
        String templateJob = repository.getString("templateJob").toLowerCase();

        if(! jenkins.getJobs().containsKey(templateJob)) {
            throw new Exception("Template job not found in jenkins jobs, is the name right?");
        }

        String jobXML = client.get("/job/" + templateJob.replace(" ", "%20") + "/config.xml");
        SAXReader reader = new SAXReader();
        Document doc = reader.read(new StringReader(jobXML));
        Element root = doc.getRootElement();

        if(repository.has("mvnOptions") || repository.has("mvnCommand")) {
            root = insertMvnGoals(root, repository, test);
        }
        else if(repository.has("executeShells") || repository.has("executeShell")) {
            root = insertShellCommand(root, repository, test);
        }
        else {
            System.out.println("WARNING - Did not provide an executeShell or mvnOptions/mvnCommand in repo: " + repository.getString("repository"));
        }

        if (repository.has("description")) {
            root = setDescription(root, repository.getString("description"));
        }
        else {
            root = setDescription(root, "Automatically generated test job");
        }
        root = enableJob(root);

        return doc.asXML();
    }

    private Element insertMvnGoals(Element root, JSONObject repository, String test) throws JSONException {
        String mvnOptions = ConfigParser.parseConfigString(repository.getString("mvnOptions"), test);
        String mvnCommand = ConfigParser.parseConfigString(repository.getString("mvnCommand"), test);

        root.element("goals").setText(mvnOptions + " " + mvnCommand);
        return root;
    }

    private Element setDescription(Element root, String description) {
        root.element("description").setText(description);
        return root;
    }

    private Element insertShellCommand(Element root, JSONObject repository, String test) throws Exception {

        // if we have multiple executeShells specified, make sure they're put in, in order
        if (repository.has("executeShells")) {
            JSONArray shells = repository.getJSONArray("executeShells");
            List list = root.element("builders").elements("hudson.tasks.Shell");

            // check how many execute shell's exist in the template job
            int count = 0;
            for(Iterator iter = list.iterator(); iter.hasNext();) {
                Element el = (Element) iter.next();
                if (el.element("command") != null) {
                    count++;
                }
            }

            // if the number of execute shell's in template job differ from the number in our config
            if (count != shells.length()) {
                throw new Exception("The number of ExecuteShells is different than the number in the template job");
            }

            // insert the commands into the XML
            for(int i = 0; i < shells.length(); i++) {
                ((Element) root.element("builders")
                    .elements("hudson.tasks.Shell")
                    .get(i)
                ).element("command").setText(ConfigParser.parseConfigString(shells.getString(i), test));
            }
        }
        else {
            root.element("builders")
                .element("hudson.tasks.Shell")
                .element("command")
                .setText(ConfigParser.parseConfigString(repository.getString("executeShell"), test));
        }

        return root;
    }

    private Element enableJob(Element root) {
        root.element("disabled").setText("false");
        return root;
    }

    //TODO: make addJobToView use dom4j instead of this nasty regex text parsing
    private void addJobToView(String jobName, String viewName) throws IOException {
        String viewXML = "";
        try {
            viewXML = client.get("/view/" + viewName.replace(" ", "%20") + "/config.xml");
        } catch (IOException e) {
            System.out.println("Failed to get view, please check spelling (case-sensitive) and try again");
            e.printStackTrace();
        }
        if (viewXML.isEmpty())
            return;

        ArrayList<String> views = new ArrayList<>();
        String regex = "<string>([a-zA-Z0-9\\-_\\. ]+)</string>";
        Pattern p = Pattern.compile(regex);
        Matcher matcher = p.matcher(viewXML);
        while(matcher.find()) {
            views.add(matcher.group(1));
        }
        // if our job is already in view, don't do anything else
        for(String view : views) {
            if (view.equals(jobName)) {
                return;
            }
        }
        views.add(jobName);
        Collections.sort(views, new Comparator<String>() {
            @Override
            public int compare(String s, String t1) {
                return s.toLowerCase().compareTo(t1.toLowerCase());
            }
        });
        viewXML = viewXML.replaceAll("\\s+?" +regex + "\\+?", "");
        String v = "";
        for(String view : views) {
            v += "<string>" + view + "</string>";
        }
        viewXML = viewXML.replace("</jobNames>", v + "</jobNames>");
        client.post_xml("/view/" + viewName.replace(" ", "%20") + "/config.xml", viewXML, crumbsFlag);
    }
}
