package de.codecentric.jenkins.dashboard;

import hudson.Extension;
import hudson.model.Item;
import hudson.model.Result;
import hudson.model.TopLevelItem;
import hudson.model.ViewGroup;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Cause;
import hudson.model.Descriptor;
import hudson.model.ModifiableItemGroup;
import hudson.model.ParametersAction;
import hudson.model.StringParameterValue;
import hudson.model.View;
import hudson.model.ViewDescriptor;
import hudson.model.queue.QueueTaskFuture;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import jenkins.model.Jenkins;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.bind.JavaScriptMethod;

import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;

import de.codecentric.jenkins.dashboard.api.environments.ServerEnvironment;
import de.codecentric.jenkins.dashboard.api.repositories.Artifact;
import de.codecentric.jenkins.dashboard.api.repositories.RepositoryInterface;
import de.codecentric.jenkins.dashboard.impl.environments.ec2.EC2Connector;
import de.codecentric.jenkins.dashboard.impl.repositories.RepositoryTypes;
import de.codecentric.jenkins.dashboard.impl.repositories.artifactory.ArtifactoryConnector;
import de.codecentric.jenkins.dashboard.impl.repositories.nexus.NexusConnector;

/**
 * Central class for the dashboard view. When adding a new view to Jenkins page,
 * this DashboardView will appear. Each time this view is loaded, this class
 * will be called.
 * 
 */
public class DashboardView extends View {

    private final static Logger LOGGER = Logger.getLogger(DashboardView.class.getName());

    @Extension
    public static final DashboardViewDescriptor DESCRIPTOR = new DashboardViewDescriptor();

    public static final String PARAM_VERSION = "VERSION";
    public static final String PARAM_ENVIRONMENT = "ENVIRONMENT";
    public static final String PARAM_EC2_REGION = "EC2_REGION";
    public static final String PARAM_AWS_KEY= "AWS_KEY";
    
    private boolean showDeployField;

    private String groupId = "";
    private String artefactId = "";

    private List<Environment> environments;

    public DashboardView(final String name) {
        super(name);
    }

    public DashboardView(final String name, final ViewGroup owner) {
        super(name, owner);
    }

    @DataBoundConstructor
    public DashboardView(final String name, final boolean showDeployField, final String groupId, final String artefactId, final List<Environment> environments) {
        this(name);
        setShowDeployField(showDeployField);
        setGroupId(groupId);
        setArtefactId(artefactId);
        setEnvironments(environments);
    }

    @Override
    public ViewDescriptor getDescriptor() {
        return DESCRIPTOR;
    }

    /**
     * Gets all the items in this collection in a read-only view.
     */
    @Override
    public Collection<TopLevelItem> getItems() {
        return new ArrayList<TopLevelItem>();
    }

    /**
     * Checks if the job is in this collection.
     */
    @Override
    public boolean contains(final TopLevelItem item) {
        return false;
    }

    @JavaScriptMethod
    public String deploy(String version, String environment) {
        LOGGER.info("Deploy version " + version + " to environment " + environment);

        // Get the environment with corresponding build-job
        Environment buildEnvironment = null;
        for (Environment env : environments) {
            if (env.getAwsInstance().equals(environment)) {
                buildEnvironment = env;
                break;
            }
        }

        final AbstractProject buildJob = Jenkins.getInstance().getItemByFullName(buildEnvironment.getBuildJob(), AbstractProject.class);
        LOGGER.info("Executing job: " + buildJob);

        if (buildJob == null) {
            return String.format(Messages.DashboardView_buildJobNotFound(), buildEnvironment.getName());
        }

        if ((!buildJob.isBuildable()) || (!buildJob.isParameterized())) {
            return Messages.DashboardView_deploymentCannotBeExecuted();
        }

        final ParametersAction versionParam = new ParametersAction(new StringParameterValue(PARAM_VERSION, version));
        final ParametersAction environmentParam = new ParametersAction(new StringParameterValue(PARAM_ENVIRONMENT, environment));
        final ParametersAction ec2RegionParam = new ParametersAction(new StringParameterValue(PARAM_EC2_REGION, environment));
        final ParametersAction awsKeyParam = new ParametersAction(new StringParameterValue(PARAM_AWS_KEY, environment));

        List<ParametersAction> actions = Arrays.asList(versionParam, environmentParam, ec2RegionParam, awsKeyParam);
        QueueTaskFuture<AbstractBuild> scheduledBuild = buildJob.scheduleBuild2(2, new Cause.UserIdCause(), actions);

        Result result = Result.FAILURE;
        try {
            AbstractBuild finishedBuild = scheduledBuild.get();
            result = finishedBuild.getResult();
            LOGGER.info("Build finished with result: " + result + " completed in: " + finishedBuild.getDurationString() + ". ");
        } catch (Exception e) {
            LOGGER.severe("Error while waiting for build " + scheduledBuild.toString() + ".");
            LOGGER.severe(e.getMessage());
            LOGGER.severe(ExceptionUtils.getFullStackTrace(e));
            return String.format(Messages.DashboardView_buildJobFailed(), buildJob.getName());
        }
        if (result == Result.SUCCESS) {
            return String.format(Messages.DashboardView_buildJobScheduledSuccessfully(), buildJob.getName());
        }
        return String.format(Messages.DashboardView_buildJobSchedulingFailed(), buildJob.getName());
    }

    /**
     * Handles the configuration submission. Load view-specific properties here.
     */
    @Override
    protected synchronized void submit(final StaplerRequest req) throws IOException, ServletException, Descriptor.FormException {
        LOGGER.info("DashboardView submit configuration");
        req.bindJSON(this, req.getSubmittedForm()); // Mapping the JSON directly should work
    }

    /**
     * Creates a new {@link hudson.model.Item} in this collection.
     * 
     * This method should call
     * {@link ModifiableItemGroup#doCreateItem(org.kohsuke.stapler.StaplerRequest, org.kohsuke.stapler.StaplerResponse)}
     * and then add the newly created item to this view.
     * 
     * @return null if it fails
     */
    @Override
    public Item doCreateItem(final StaplerRequest req, final StaplerResponse rsp) throws IOException, ServletException {
        return Jenkins.getInstance().doCreateItem(req, rsp);
    }

    public String getDisplayDeployField() {
        return showDeployField ? "" : "display:none;";
    }

    public boolean getShowDeployField() {
        return showDeployField;
    }

    public void setShowDeployField(boolean showDeployField) {
        this.showDeployField = showDeployField;
    }

    public String getArtefactId() {
        return artefactId;
    }

    public void setArtefactId(final String artefactId) {
        this.artefactId = artefactId;
    }

    public String getGroupId() {
        return groupId;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public List<Artifact> getArtifacts() {
        LOGGER.info("Getting artifacts for " + DESCRIPTOR.getRepositoryType());

        // User needs to configure an artifact repository on the global config
        // page
        if (DESCRIPTOR.getRepositoryType() == null) {
            return new ArrayList<Artifact>();
        }

        RepositoryInterface repository;
        try {
            repository = createRepository();
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return new ArrayList<Artifact>();
        }

        return repository.getArtefactList(groupId, artefactId);
    }

    private RepositoryInterface createRepository() throws URISyntaxException {
        URI repositoryURI = new URI(DESCRIPTOR.getRepositoryRestUri());
        RepositoryInterface repository;

        if (DESCRIPTOR.getRepositoryType().equalsIgnoreCase(RepositoryTypes.ARTIFACTORY.getid())) {
            repository = new ArtifactoryConnector(DESCRIPTOR.getUsername(), DESCRIPTOR.getPassword(), repositoryURI);
        } else {
            repository = new NexusConnector(DESCRIPTOR.getUsername(), DESCRIPTOR.getPassword(), repositoryURI);
        }
        return repository;
    }

    public List<ServerEnvironment> getMatchingEC2Environments() {
        final List<ServerEnvironment> list = new ArrayList<ServerEnvironment>();
        for (Environment env : environments) {
            final EC2Connector envConn = EC2Connector.getEC2Connector(env.getCredentials());
            if (envConn == null || !envConn.areAwsCredentialsValid()) {
                LOGGER.info("Invalid credentials in environment '" + env.getName() + "'");
                continue;
            }
            List<ServerEnvironment> foundEnvironments = envConn.getEnvironmentsByTag(Region.getRegion(Regions.fromName(env.getRegion())), env.getAwsInstance());
            updateEnvironmentsWithUrlPrePostFix(foundEnvironments, env);
            list.addAll(foundEnvironments);
        }
        return list;
    }

    private void updateEnvironmentsWithUrlPrePostFix(List<ServerEnvironment> foundEnvironments, Environment environment) {
        for (ServerEnvironment serverEnvironment : foundEnvironments) {
            serverEnvironment.setUrlPrefix(environment.getUrlPrefix());
            serverEnvironment.setUrlPostfix(environment.getUrlPostfix());
        }
    }

    public List<Environment> getEnvironments() {
        return Collections.unmodifiableList(environments);
    }

    public void setEnvironments(final List<Environment> environmentsList) {
        this.environments = environmentsList == null ? new ArrayList<Environment>() : new ArrayList<Environment>(environmentsList);
    }

}