package com.cloudbees.jenkins;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.ListBoxModel;
import jenkins.tasks.SimpleBuildStep;
import org.jenkinsci.plugins.github.common.ExpandableMessage;
import org.jenkinsci.plugins.github.extension.status.StatusErrorHandler;
import org.jenkinsci.plugins.github.status.GitHubCommitStatusSetter;
import org.jenkinsci.plugins.github.status.err.ChangingBuildStatusErrorHandler;
import org.jenkinsci.plugins.github.status.err.ShallowAnyErrorHandler;
import org.jenkinsci.plugins.github.status.sources.AnyDefinedRepositorySource;
import org.jenkinsci.plugins.github.status.sources.BuildDataRevisionShaSource;
import org.jenkinsci.plugins.github.status.sources.ConditionalStatusResultSource;
import org.jenkinsci.plugins.github.status.sources.DefaultCommitContextSource;
import org.jenkinsci.plugins.github.status.sources.DefaultStatusResultSource;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.github.GHCommitState;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collections;

import static com.cloudbees.jenkins.Messages.GitHubCommitNotifier_DisplayName;
import static hudson.model.Result.FAILURE;
import static hudson.model.Result.SUCCESS;
import static hudson.model.Result.UNSTABLE;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.jenkinsci.plugins.github.status.sources.misc.AnyBuildResult.onAnyResult;
import static org.jenkinsci.plugins.github.status.sources.misc.BetterThanOrEqualBuildResult.betterThanOrEqualTo;

/**
 * Create commit status notifications on the commits based on the outcome of the build.
 *
 * @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
 */
public class GitHubCommitNotifier extends Notifier implements SimpleBuildStep {
    private static final ExpandableMessage DEFAULT_MESSAGE = new ExpandableMessage("");

    private ExpandableMessage statusMessage = DEFAULT_MESSAGE;

    private final String resultOnFailure;
    private static final Result[] SUPPORTED_RESULTS = {FAILURE, UNSTABLE, SUCCESS};

    private static final Logger LOGGER = LoggerFactory.getLogger(GitHubCommitNotifier.class);

    @Restricted(NoExternalUse.class)
    public GitHubCommitNotifier() {
        this(getDefaultResultOnFailure().toString());
    }

    /**
     * @since 1.10
     */
    @DataBoundConstructor
    public GitHubCommitNotifier(String resultOnFailure) {
        this.resultOnFailure = resultOnFailure;
    }

    /**
     * @since 1.14.1
     */
    public ExpandableMessage getStatusMessage() {
        return statusMessage;
    }

    /**
     * @since 1.14.1
     */
    @DataBoundSetter
    public void setStatusMessage(ExpandableMessage statusMessage) {
        this.statusMessage = statusMessage;
    }

    /**
     * @since 1.10
     */
    @NonNull
    public String getResultOnFailure() {
        return resultOnFailure != null ? resultOnFailure : getDefaultResultOnFailure().toString();
    }

    @NonNull
    public static Result getDefaultResultOnFailure() {
        return FAILURE;
    }

    @NonNull
    /*package*/ Result getEffectiveResultOnFailure() {
        return Result.fromString(trimToEmpty(resultOnFailure));
    }

    @Override
    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }

    @Override
    public void perform(@NonNull Run<?, ?> build,
                        @NonNull FilePath ws,
                        @NonNull Launcher launcher,
                        @NonNull TaskListener listener) throws InterruptedException, IOException {

        GitHubCommitStatusSetter setter = new GitHubCommitStatusSetter();
        setter.setReposSource(new AnyDefinedRepositorySource());
        setter.setCommitShaSource(new BuildDataRevisionShaSource());
        setter.setContextSource(new DefaultCommitContextSource());


        String content = (statusMessage != null ? statusMessage : DEFAULT_MESSAGE).getContent();

        if (isNotBlank(content)) {
            setter.setStatusResultSource(new ConditionalStatusResultSource(
                    asList(
                            betterThanOrEqualTo(SUCCESS, GHCommitState.SUCCESS, content),
                            betterThanOrEqualTo(UNSTABLE, GHCommitState.FAILURE, content),
                            betterThanOrEqualTo(FAILURE, GHCommitState.ERROR, content),
                            onAnyResult(GHCommitState.PENDING, content)
                    )));
        } else {
            setter.setStatusResultSource(new DefaultStatusResultSource());
        }

        if (getEffectiveResultOnFailure().equals(SUCCESS)) {
            setter.setErrorHandlers(Collections.<StatusErrorHandler>singletonList(new ShallowAnyErrorHandler()));
        } else if (resultOnFailure == null) {
            setter.setErrorHandlers(null);
        } else {
            setter.setErrorHandlers(Collections.<StatusErrorHandler>singletonList(
                    new ChangingBuildStatusErrorHandler(getEffectiveResultOnFailure().toString())));
        }

        setter.perform(build, ws, launcher, listener);
    }

    @Extension
    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            return true;
        }

        @Override
        public String getDisplayName() {
            return GitHubCommitNotifier_DisplayName();
        }

        public ListBoxModel doFillResultOnFailureItems() {
            ListBoxModel items = new ListBoxModel();
            for (Result result : SUPPORTED_RESULTS) {
                items.add(result.toString());
            }
            return items;
        }
    }
}
