package slacknotifications.teamcity.payload.content;

import jetbrains.buildServer.BuildProblemData;
import jetbrains.buildServer.serverSide.*;
import jetbrains.buildServer.tests.TestInfo;
import jetbrains.buildServer.users.SUser;
import jetbrains.buildServer.vcs.SVcsModification;
import slacknotifications.teamcity.BuildStateEnum;
import slacknotifications.teamcity.Loggers;
import slacknotifications.teamcity.SlackNotificator;
import slacknotifications.teamcity.TeamCityIdResolver;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

public class SlackNotificationPayloadContent {
    String buildStatus;
    String buildResult;
    String buildResultPrevious;
    String buildResultDelta;
    String notifyType;
    String buildFullName;
    String buildName;
    String buildId;
    String buildTypeId;
    String buildInternalTypeId;
    String buildExternalTypeId;
    String buildStatusUrl;
    String buildStatusHtml;
    String rootUrl;
    String projectName;
    String projectId;
    String projectInternalId;
    String projectExternalId;
    String buildNumber;
    String agentName;
    String agentOs;
    String agentHostname;
    String triggeredBy;
    String comment;
    String message;
    String text;
    String branchName;
    String branchDisplayName;
    String buildStateDescription;
    String progressSummary;
    List<Commit> commits;
    private boolean isFirstFailedBuild;

    Boolean branchIsDefault;

    Branch branch;

/*
    public final static String BUILD_STATUS_FAILURE   = "failure";
    public final static String BUILD_STATUS_SUCCESS   = "success";
    public final static String BUILD_STATUS_RUNNING   = "running";
    public final static String BUILD_STATUS_NO_CHANGE = "unchanged";
    public final static String BUILD_STATUS_FIXED     = "fixed";
    public final static String BUILD_STATUS_BROKEN    = "broken";
    public final static String BUILD_STATUS_UNKNOWN   = "unknown";*/

    public final static String BUILD_STATUS_FAILURE   = "Failed";
    public final static String BUILD_STATUS_SUCCESS   = "Succeeded";
    public final static String BUILD_STATUS_RUNNING   = "Running";
    public final static String BUILD_STATUS_NO_CHANGE = "Unchanged";
    public final static String BUILD_STATUS_FIXED     = "Fixed";
    public final static String BUILD_STATUS_BROKEN    = "Broken";
    public final static String BUILD_STATUS_UNKNOWN   = "Unknown";
    private String buildLink;
    private String color;
    private long elapsedTime;
    private boolean isComplete;
    private ArrayList<String> failedBuildMessages = new ArrayList<String>();
    private ArrayList<String> failedTestNames = new ArrayList<String>();

    public SlackNotificationPayloadContent(){

        }

    /**
		 * Constructor: Only called by RepsonsibilityChanged.
		 * @param server
		 * @param buildType
		 * @param buildState
		 */
		public SlackNotificationPayloadContent(SBuildServer server, SBuildType buildType, BuildStateEnum buildState) {
			populateCommonContent(server, buildType, buildState);
		}

		/**
		 * Constructor: Called by everything except RepsonsibilityChanged.
		 * @param server
		 * @param sRunningBuild
		 * @param previousBuild
		 * @param buildState
		 */
		public SlackNotificationPayloadContent(SBuildServer server, SRunningBuild sRunningBuild, SFinishedBuild previousBuild,
                                               BuildStateEnum buildState) {

            this.commits = new ArrayList<Commit>();
            populateCommonContent(server, sRunningBuild, previousBuild, buildState);
    		populateMessageAndText(sRunningBuild, buildState);
            populateCommits(sRunningBuild);
    		populateArtifacts(sRunningBuild);
            populateResults(sRunningBuild);
		}

    private void populateResults(SRunningBuild sRunningBuild) {
        List<BuildProblemData> failureReasons = sRunningBuild.getFailureReasons();
        if(failureReasons == null){
            return;
        }
        HashSet<String> failureTestNames = new HashSet<String>();
        HashSet<String> failureMessages = new HashSet<String>();
        for(BuildProblemData reason : failureReasons){
            if(reason.getType() == BuildProblemData.TC_FAILED_TESTS_TYPE){
                List<TestInfo> failedTestMessages = sRunningBuild.getTestMessages(0, 2000);
                if(!failedTestMessages.isEmpty()) {
                    for (TestInfo failedTest : failedTestMessages) {
                        failureTestNames.add(failedTest.getName());
                    }
                }
                else {
                    failureMessages.add(reason.getDescription());
                }
            }
            else {
                failureMessages.add(reason.getDescription());
            }
        }
        failedBuildMessages = new ArrayList<String>(failureMessages);
        failedTestNames = new ArrayList<String>(failureTestNames);
    }

    private void populateCommits(SRunningBuild sRunningBuild) {
        List<SVcsModification> changes = sRunningBuild.getContainingChanges();
        if(changes == null){
            return;
        }

        for(SVcsModification change : changes){
			Collection<SUser> committers = change.getCommitters();
			String slackUserName = null;
			if(committers != null && !committers.isEmpty()){
				SUser committer = committers.iterator().next();
				slackUserName = committer.getPropertyValue(SlackNotificator.USERNAME_KEY);
				Loggers.ACTIVITIES.debug("Resolved committer " + change.getUserName() + " to Slack User " + slackUserName);
			}
			commits.add(new Commit(change.getVersion(), change.getDescription(), change.getUserName(), slackUserName));
        }
    }

    private void populateArtifacts(SRunningBuild runningBuild) {
			//ArtifactsInfo artInfo = new ArtifactsInfo(runningBuild);
			//artInfo.
			
		}

		/**
		 * Used by RepsonsiblityChanged.
		 * Therefore, does not have access to a specific build instance.
		 * @param server
		 * @param buildType
		 * @param state
		 */
		private void populateCommonContent(SBuildServer server, SBuildType buildType, BuildStateEnum state) {
			setBuildFullName(buildType.getFullName());
			setBuildName(buildType.getName());
			setBuildTypeId(TeamCityIdResolver.getBuildTypeId(buildType));
			setBuildStatusUrl(server.getRootUrl() + "/viewLog.html?buildTypeId=" + buildType.getBuildTypeId() + "&buildId=lastFinished");

		}
		
		private void populateMessageAndText(SRunningBuild sRunningBuild,
				BuildStateEnum state) {
			// Message is a long form message, for on webpages or in email.

			// Text is designed to be shorter, for use in Text messages and the like.    		
    		setText(getBuildDescriptionWithLinkSyntax()
                    + " has " + state.getDescriptionSuffix() + ". Status: " + this.buildResult);


		}

    /**
     * Used by everything except ResponsibilityChanged. Is passed a valid build instance.
     *
     * @param server
     * @param sRunningBuild
     * @param previousBuild
     * @param buildState
     */
    private void populateCommonContent(SBuildServer server, SRunningBuild sRunningBuild, SFinishedBuild previousBuild,
                                       BuildStateEnum buildState) {
        setBuildResult(sRunningBuild, previousBuild, buildState);
        setBuildFullName(sRunningBuild.getBuildType().getFullName());
        setBuildName(sRunningBuild.getBuildType().getName());
        setBuildId(Long.toString(sRunningBuild.getBuildId()));
        setBuildStatus(sRunningBuild.getStatusDescriptor().getText());
        setBuildTypeId(TeamCityIdResolver.getBuildTypeId(sRunningBuild.getBuildType()));
        setAgentName(sRunningBuild.getAgentName());
        setElapsedTime(sRunningBuild.getElapsedTime());
        Loggers.SERVER.info("SlackNotificationPayloadContent :: " + sRunningBuild.toString());
        Loggers.SERVER.info("SlackNotificationPayloadContent :: " + sRunningBuild.getStatusDescriptor().getText());

        try {
            if (sRunningBuild.getBranch() != null) {
                setBranch(sRunningBuild.getBranch());
                setBranchDisplayName(getBranch().getDisplayName());
                setBranchIsDefault(getBranch().isDefaultBranch());
            } else {
                Loggers.SERVER.debug("SlackNotificationPayloadContent :: Branch is null. Either feature branch support is not configured or Teamcity does not support feature branches on this VCS");
            }

        } catch (NoSuchMethodError e) {
            Loggers.SERVER.debug("SlackNotificationPayloadContent :: Could not get Branch Info by calling sRunningBuild.getBranch(). Probably an old version of TeamCity");
        }
        setBuildStatusUrl(server.getRootUrl() + "/viewLog.html?buildTypeId=" + getBuildTypeId() + "&buildId=" + getBuildId());
        String branchSuffix = (getBranchIsDefault() != null && getBranchIsDefault()) || getBranchDisplayName() == null ? "" : (" [" + getBranchDisplayName() + "]");
        setBuildDescriptionWithLinkSyntax(String.format("<" + getBuildStatusUrl() + "|" + getBuildResult() + " - " + sRunningBuild.getBuildType().getFullName() + " #" + sRunningBuild.getBuildNumber() + branchSuffix + ">"));
    }
		
		

		private Branch getBranch() {
			return this.branch;
		}
		
		public void setBranch(Branch branch) {
			this.branch = branch;
		}

		public String getBranchDisplayName() {
			return this.branchDisplayName;
		}
		
		public void setBranchDisplayName(String displayName) {
			this.branchDisplayName = displayName;
		}

		public Boolean getBranchIsDefault() {
			return branchIsDefault;
		}

		public Boolean isMergeBranch() { return this.branchName != null && this.branchName.endsWith("/merge");}

		public void setBranchIsDefault(boolean branchIsDefault) {
			this.branchIsDefault = branchIsDefault;
		}

		/**
		 * Determines a useful build result. The one from TeamCity can't be trusted because it
		 * is not set until all the Notifiers have run, of which we are one. 
		 * @param sRunningBuild
		 * @param previousBuild
		 * @param buildState
		 */
		private void setBuildResult(SRunningBuild sRunningBuild,
				SFinishedBuild previousBuild, BuildStateEnum buildState) {


			if (previousBuild != null){
				if (previousBuild.isFinished()){ 
					if (previousBuild.getStatusDescriptor().isSuccessful()){
						this.buildResultPrevious = BUILD_STATUS_SUCCESS;
					} else {
						this.buildResultPrevious = BUILD_STATUS_FAILURE;
					}
				} else {
					this.buildResultPrevious = BUILD_STATUS_RUNNING;
				}
			} else {
				this.buildResultPrevious = BUILD_STATUS_UNKNOWN;
			}

            isComplete = buildState == BuildStateEnum.BUILD_FINISHED;

			if (buildState == BuildStateEnum.BEFORE_BUILD_FINISHED || buildState == BuildStateEnum.BUILD_FINISHED){ 
				if (sRunningBuild.getStatusDescriptor().isSuccessful()){
					this.buildResult = BUILD_STATUS_SUCCESS;
                    this.color = "good";
					if (this.buildResultPrevious.equals(this.buildResult)){
						this.buildResultDelta = BUILD_STATUS_NO_CHANGE;
					} else {
						this.buildResultDelta = BUILD_STATUS_FIXED;
					}
				} else {
					this.buildResult = BUILD_STATUS_FAILURE;
                    this.color = "danger";
					if (this.buildResultPrevious.equals(this.buildResult)){
						this.buildResultDelta = BUILD_STATUS_NO_CHANGE;
					} else {
						this.buildResultDelta = BUILD_STATUS_BROKEN;
                        this.setFirstFailedBuild(true);
					}
				}
			} else {
				this.buildResult = BUILD_STATUS_RUNNING;
				this.buildResultDelta = BUILD_STATUS_UNKNOWN;
			}
			
		}

		// Getters and setters
		

		public String getBuildResult() {
			return buildResult;
		}

		public void setBuildResult(String buildResult) {
			this.buildResult = buildResult;
		}

    public String getBuildStatus() {
        return buildStatus;
    }

    public void setBuildStatus(String buildStatus) {
        this.buildStatus = buildStatus;
    }

		public String getBuildFullName() {
			return buildFullName;
		}

		public void setBuildFullName(String buildFullName) {
			this.buildFullName = buildFullName;
		}

		public String getBuildName() {
			return buildName;
		}

		public void setBuildName(String buildName) {
			this.buildName = buildName;
		}

		public String getBuildId() {
			return buildId;
		}

		public void setBuildId(String buildId) {
			this.buildId = buildId;
		}

		public String getBuildTypeId() {
			return buildTypeId;
		}

		public void setBuildTypeId(String buildTypeId) {
			this.buildTypeId = buildTypeId;
		}






		public String getAgentName() {
			return agentName;
		}

		public void setAgentName(String agentName) {
			this.agentName = agentName;
		}


		public String getBuildStatusUrl() {
			return buildStatusUrl;
		}

		public void setBuildStatusUrl(String buildStatusUrl) {
			this.buildStatusUrl = buildStatusUrl;
		}


		public String getText() {
			return text;
		}


		public void setText(String text) {
			this.text = text;
		}


    public void setBuildDescriptionWithLinkSyntax(String buildLink) {
        this.buildLink = buildLink;
    }

    public String getBuildDescriptionWithLinkSyntax() {
        return buildLink;
    }


    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }


    public void setElapsedTime(long elapsedTime) {
        this.elapsedTime = elapsedTime;
    }

    public long getElapsedTime() {
        return elapsedTime;
    }

    public List<Commit> getCommits() {
        return commits;
    }

    public void setCommits(List<Commit> commits) {
        this.commits = commits;
    }

    public boolean getIsComplete() {
        return isComplete;
    }

    public void setIsComplete(boolean isComplete) {
        this.isComplete = isComplete;
    }

    public boolean getIsFirstFailedBuild() {
        return isFirstFailedBuild;
    }

    public void setFirstFailedBuild(boolean isFirstFailedBuild) {
        this.isFirstFailedBuild = isFirstFailedBuild;
    }

    public ArrayList<String> getFailedBuildMessages() {
        return failedBuildMessages;
    }

    public ArrayList<String> getFailedTestNames() {
        return failedTestNames;
    }
}