package com.dabsquared.gitlabjenkins.trigger.handler.pipeline;


import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineEventObjectAttributes;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PipelineHook;
import com.dabsquared.gitlabjenkins.trigger.exception.NoRevisionToBuildException;
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter;
import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilter;
import com.dabsquared.gitlabjenkins.trigger.handler.AbstractWebHookTriggerHandler;
import com.dabsquared.gitlabjenkins.util.BuildUtil;
import com.dabsquared.gitlabjenkins.util.LoggerUtil;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Run;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.RevisionParameterAction;

import javax.ws.rs.WebApplicationException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.dabsquared.gitlabjenkins.cause.CauseDataBuilder.causeData;
import static com.dabsquared.gitlabjenkins.trigger.handler.builder.generated.BuildStatusUpdateBuilder.buildStatusUpdate;

/**
 * @author Milena Zachow
 */
class PipelineHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler<PipelineHook> implements PipelineHookTriggerHandler {

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

    private final List<String> allowedStates;

    PipelineHookTriggerHandlerImpl(List<String> allowedStates) {
        this.allowedStates = allowedStates;
    }

    @Override
    public void handle(Job<?, ?> job, PipelineHook hook, boolean ciSkip, BranchFilter branchFilter, MergeRequestLabelFilter mergeRequestLabelFilter) {
        PipelineEventObjectAttributes objectAttributes = hook.getObjectAttributes();
        try {
            if (job instanceof AbstractProject<?, ?>) {
                GitLabConnectionProperty property = job.getProperty(GitLabConnectionProperty.class);

                if (property != null && property.getClient() != null) {
                    GitLabClient client = property.getClient();
                    com.dabsquared.gitlabjenkins.gitlab.api.model.Project projectForName = client.getProject(hook.getProject().getPathWithNamespace());
                    hook.setProjectId(projectForName.getId());
                }
            }
        } catch (WebApplicationException e) {
            LOGGER.log(Level.WARNING, "Failed to communicate with gitlab server to determine project id: " + e.getMessage(), e);
        }
        if (allowedStates.contains(objectAttributes.getStatus()) && !isLastAlreadyBuild(job,hook)) {
            if (ciSkip && isCiSkip(hook)) {
                LOGGER.log(Level.INFO, "Skipping due to ci-skip.");
                return;
            }
            //we do not call super here, since we do not want the status to be changed
            //in case of pipeline events that could lead to a deadlock
            String sourceBranch = getSourceBranch(hook);
            String targetBranch = getTargetBranch(hook);
            if (branchFilter.isBranchAllowed(sourceBranch, targetBranch)) {
                LOGGER.log(Level.INFO, "{0} triggered for {1}.", LoggerUtil.toArray(job.getFullName(), getTriggerType()));

                super.scheduleBuild(job, createActions(job, hook));
            } else {
                LOGGER.log(Level.INFO, "branch {0} is not allowed", sourceBranch + " or " + targetBranch);
            }

        }
    }

    @Override
    protected boolean isCiSkip(PipelineHook hook) {
        //we don't get a commit message or suchlike that could contain ci-skip
        return false;
    }

    @Override
    protected String getSourceBranch(PipelineHook hook) {
        return hook.getObjectAttributes().getRef() == null ? null : hook.getObjectAttributes().getRef().replaceFirst("^refs/heads/", "");
    }

    @Override
    protected String getTargetBranch(PipelineHook hook) {
        return hook.getObjectAttributes().getRef() == null ? null : hook.getObjectAttributes().getRef().replaceFirst("^refs/heads/", "");
    }

    @Override
    protected String getTriggerType() {
        return "pipeline event";
    }

    @Override
    protected CauseData retrieveCauseData(PipelineHook hook) {
        return causeData()
                .withActionType(CauseData.ActionType.PIPELINE)
                .withSourceProjectId(hook.getProject().getId())
                .withBranch(getTargetBranch(hook)==null?"":getTargetBranch(hook))
                .withSourceBranch(getTargetBranch(hook)==null?"":getTargetBranch(hook))
                .withUserName(hook.getUser()==null||hook.getUser().getName()==null?"":hook.getUser().getName())
                .withSourceRepoName(hook.getRepository()==null||hook.getRepository().getName()==null?"":hook.getRepository().getName())
                .withSourceNamespace(hook.getProject()==null||hook.getProject().getNamespace()==null?"":hook.getProject().getNamespace())
                .withSourceRepoSshUrl(hook.getRepository()==null||hook.getRepository().getGitSshUrl()==null?"":hook.getRepository().getGitSshUrl())
                .withSourceRepoHttpUrl(hook.getRepository()==null||hook.getRepository()==null?"":hook.getRepository().getGitHttpUrl())
                .withMergeRequestTitle("")
                .withTargetProjectId(hook.getProject().getId())
                .withTargetBranch(getTargetBranch(hook)==null?"":getTargetBranch(hook))
                .withTargetRepoName("")
                .withTargetNamespace("")
                .withTargetRepoSshUrl("")
                .withTargetRepoHttpUrl("")
                .withLastCommit(hook.getObjectAttributes().getSha())
                .withTriggeredByUser(hook.getUser()==null||hook.getUser().getName()==null?"":hook.getUser().getName())
                .withRef(hook.getObjectAttributes().getRef()==null?"":hook.getObjectAttributes().getRef())
                .withSha(hook.getObjectAttributes().getSha()==null?"":hook.getObjectAttributes().getSha())
                .withBeforeSha(hook.getObjectAttributes().getBeforeSha()==null?"":hook.getObjectAttributes().getBeforeSha())
                .withStatus(hook.getObjectAttributes().getStatus()==null?"":hook.getObjectAttributes().getStatus().toString())
                .withStages(hook.getObjectAttributes().getStages()==null?"":hook.getObjectAttributes().getStages().toString())
                .withCreatedAt(hook.getObjectAttributes().getCreatedAt()==null?"":hook.getObjectAttributes().getCreatedAt().toString())
                .withFinishedAt(hook.getObjectAttributes().getFinishedAt()==null?"":hook.getObjectAttributes().getFinishedAt().toString())
                .withBuildDuration(String.valueOf(hook.getObjectAttributes().getDuration()))
                .build();
    }

    @Override
    protected RevisionParameterAction createRevisionParameter(PipelineHook hook, GitSCM gitSCM) throws NoRevisionToBuildException {
        return new RevisionParameterAction(retrieveRevisionToBuild(hook), retrieveUrIish(hook));
    }

    @Override
    protected BuildStatusUpdate retrieveBuildStatusUpdate(PipelineHook hook) {
        return buildStatusUpdate()
            .withProjectId(hook.getProject().getId())
            .withSha(hook.getObjectAttributes().getSha())
            .withRef(hook.getObjectAttributes().getRef())
            .build();
    }

    private String retrieveRevisionToBuild(PipelineHook hook) throws NoRevisionToBuildException {
        if (hook.getObjectAttributes() != null
                && hook.getObjectAttributes().getSha() != null) {
            return hook.getObjectAttributes().getSha();
        } else {
            throw new NoRevisionToBuildException();
        }
    }

    private boolean isLastAlreadyBuild(Job<?, ?> project, PipelineHook hook) {
        PipelineEventObjectAttributes objectAttributes = hook.getObjectAttributes();
        if (objectAttributes != null && objectAttributes.getSha() != null) {
            Run<?, ?> lastBuild = BuildUtil.getBuildBySHA1IncludingMergeBuilds(project, objectAttributes.getSha());
            if (lastBuild != null) {
                LOGGER.log(Level.INFO, "Last commit has already been built in build #" + lastBuild.getNumber());
                return true;
            }
        }
        return false;
    }
}