package slacknotifications.teamcity.settings;

import com.intellij.openapi.util.text.StringUtil;
import jetbrains.buildServer.serverSide.SBuildType;
import org.jdom.DataConversionException;
import org.jdom.Element;
import slacknotifications.teamcity.BuildState;
import slacknotifications.teamcity.BuildStateEnum;
import slacknotifications.teamcity.TeamCityIdResolver;
import slacknotifications.teamcity.settings.converter.SlackNotificationBuildStateConverter;

import java.util.*;

import static slacknotifications.teamcity.BuildStateEnum.*;


public class SlackNotificationConfig {
	
	private static final String ENABLED = "enabled";
	private static final String TOKEN = "token";
	private static final String CHANNEL = "channel";
	private static final String TEAM_NAME = "teamName";
	private static final String CHANNEL_ENABLED_MESSAGE = "mention-channel-enabled";
	private static final String SLACK_USER_ENABLED_MESSAGE = "mention-slack-user-enabled";
	private static final String STATES = "states";
	private static final String BUILD_TYPES = "build-types";
	private static final String ENABLED_FOR_ALL = "enabled-for-all";
	private static final String ENABLED_FOR_SUBPROJECTS = "enabled-for-subprojects";
	private static final String BOT_NAME = "botName";
	private static final String CUSTOM_TEMPLATES = "custom-templates";
	private static final String CONTENT = "content";
	private static final String ICON_URL = "iconUrl";
	private static final String SHOW_BUILD_AGENT = "showBuildAgent";
	private static final String SHOW_BUILD_STATUS = "showBuildStatus";
	private static final String SHOW_TITLE = "showTitle";
	private static final String SHOW_COMMITS = "showCommits";
	private static final String SHOW_COMMITTERS = "showCommitters";
	private static final String MAX_COMMITS_TO_DISPLAY = "maxCommitsToDisplay";
	private static final String SHOW_FAILURE_REASON = "showFailureReason";
	
	
	private Boolean enabled = true;
	private String uniqueKey = "";
	private String apiToken;
	private String channel;
    private String teamName;
	private BuildState states = new BuildState();
	private SortedMap<String, CustomMessageTemplate> templates; 
	private Boolean allBuildTypesEnabled = true;
	private Boolean subProjectsEnabled = true;
	private Set<String> enabledBuildTypesSet = new HashSet<String>();
    private boolean mentionChannelEnabled;
	private boolean mentionSlackUserEnabled;
    private boolean customContent;
    private SlackNotificationContentConfig content;

    @SuppressWarnings("unchecked")
	public SlackNotificationConfig(Element e) {
		this.content = new SlackNotificationContentConfig();
		int Min = 1000000, Max = 1000000000;
		Integer Rand = Min + (int)(Math.random() * ((Max - Min) + 1));
		this.uniqueKey = Rand.toString();
		this.templates = new TreeMap<String,CustomMessageTemplate>();
		
		if (e.getAttribute(TOKEN) != null){
			this.setToken(e.getAttributeValue(TOKEN));
		}

		if (e.getAttribute(CHANNEL) != null){
			this.setChannel(e.getAttributeValue(CHANNEL));
		}

        if (e.getAttribute(TEAM_NAME) != null){
            this.setTeamName(e.getAttributeValue(TEAM_NAME));
        }
		
		if (e.getAttribute(ENABLED) != null){
			this.setEnabled(Boolean.parseBoolean(e.getAttributeValue(ENABLED)));
		}

		if (e.getAttribute("statemask") != null){
			this.setBuildStates(SlackNotificationBuildStateConverter.convert(Integer.parseInt(e.getAttributeValue("statemask"))));
		}

		if (e.getAttribute("key") != null){
			this.setUniqueKey(e.getAttributeValue("key"));
		}

        if (e.getAttribute(CHANNEL_ENABLED_MESSAGE) != null){
            this.setMentionChannelEnabled(Boolean.parseBoolean(e.getAttributeValue("mention-channel-enabled")));
        }

		if (e.getAttribute(SLACK_USER_ENABLED_MESSAGE) != null){
			this.setMentionSlackUserEnabled(Boolean.parseBoolean(e.getAttributeValue("mention-slack-user-enabled")));
		}
		
		if(e.getChild(STATES) != null){
			Element eStates = e.getChild(STATES);
			List<Element> statesList = eStates.getChildren("state");
			if (!statesList.isEmpty()){
				for(Element eState : statesList)
				{
					try {
						states.setEnabled(BuildStateEnum.findBuildState(eState.getAttributeValue("type")), 
										  eState.getAttribute(ENABLED).getBooleanValue());
					} catch (DataConversionException e1) {e1.printStackTrace();}
				}
			}
		}
		
		if(e.getChild(BUILD_TYPES) != null){
			Element eTypes = e.getChild(BUILD_TYPES);
			if (eTypes.getAttribute(ENABLED_FOR_ALL) != null){
				try {
					this.enableForAllBuildsInProject(eTypes.getAttribute(ENABLED_FOR_ALL).getBooleanValue());
				} catch (DataConversionException e1) {e1.printStackTrace();}
			}
			if (eTypes.getAttribute(ENABLED_FOR_SUBPROJECTS) != null){
				try {
					this.enableForSubProjects(eTypes.getAttribute(ENABLED_FOR_SUBPROJECTS).getBooleanValue());
				} catch (DataConversionException e1) {e1.printStackTrace();}
			}
			if (!isEnabledForAllBuildsInProject()){
				List<Element> typesList = eTypes.getChildren("build-type");
				if (!typesList.isEmpty()){
					for(Element eType : typesList)
					{
						if (eType.getAttributeValue("id")!= null){
							enabledBuildTypesSet.add(eType.getAttributeValue("id"));
						}
					}
				}
			}
		}
		

		
		if(e.getChild(CUSTOM_TEMPLATES) != null){
			Element eParams = e.getChild(CUSTOM_TEMPLATES);
			List<Element> templateList = eParams.getChildren("custom-template");
			if (!templateList.isEmpty()){
				for(Element eParam : templateList)
				{
					this.templates.put(
							eParam.getAttributeValue(CustomMessageTemplate.TYPE),
							CustomMessageTemplate.create(
									eParam.getAttributeValue(CustomMessageTemplate.TYPE),
									eParam.getAttributeValue(CustomMessageTemplate.TEMPLATE),
									Boolean.parseBoolean(eParam.getAttributeValue(CustomMessageTemplate.ENABLED))
									)
							);
				}
			}
		}

        if(e.getChild(CONTENT) != null) {
            setHasCustomContent(true);
            Element eContent = e.getChild(CONTENT);

            this.content.setEnabled(true);

            if (eContent.getAttribute(ICON_URL) != null){
                this.content.setIconUrl(eContent.getAttributeValue(ICON_URL));
            }
            if (eContent.getAttribute(BOT_NAME) != null){
                this.content.setBotName(eContent.getAttributeValue(BOT_NAME));
            }
            if (eContent.getAttribute(SHOW_BUILD_AGENT) != null){
                this.content.setShowBuildAgent(Boolean.parseBoolean(eContent.getAttributeValue(SHOW_BUILD_AGENT)));
            }
			if (eContent.getAttribute(SHOW_BUILD_STATUS) != null){
				this.content.setShowBuildStatus(Boolean.parseBoolean(eContent.getAttributeValue(SHOW_BUILD_STATUS)));
			}
			if (eContent.getAttribute(SHOW_TITLE) != null){
				this.content.setShowTitle(Boolean.parseBoolean(eContent.getAttributeValue(SHOW_TITLE)));
			}
            if (eContent.getAttribute("showElapsedBuildTime") != null){
                this.content.setShowElapsedBuildTime(Boolean.parseBoolean(eContent.getAttributeValue("showElapsedBuildTime")));
            }
            if (eContent.getAttribute(SHOW_COMMITS) != null){
                this.content.setShowCommits(Boolean.parseBoolean(eContent.getAttributeValue(SHOW_COMMITS)));
            }
            if (eContent.getAttribute(SHOW_COMMITTERS) != null){
                this.content.setShowCommitters(Boolean.parseBoolean(eContent.getAttributeValue(SHOW_COMMITTERS)));
            }
            if (eContent.getAttribute(MAX_COMMITS_TO_DISPLAY) != null){
                this.content.setMaxCommitsToDisplay(Integer.parseInt(eContent.getAttributeValue(MAX_COMMITS_TO_DISPLAY)));
            }
            if (eContent.getAttribute(SHOW_FAILURE_REASON) != null){
                this.content.setShowFailureReason(Boolean.parseBoolean(eContent.getAttributeValue(SHOW_FAILURE_REASON)));
            }
        }

		
	}

    /**
     * SlackNotificationsConfig constructor. Unchecked version. Use with caution!!
     * This constructor does not check if the payloadFormat is valid.
     * It will still allow you to add the format, but the slacknotifications might not
     * fire at runtime if the payloadFormat configured is not available.
     *
     * @param channel
     * @param enabled
     * @param states
     */
    public SlackNotificationConfig(String token,
								   String channel,
								   String teamName,
								   Boolean enabled,
								   BuildState states,
								   boolean buildTypeAllEnabled,
								   boolean buildTypeSubProjects,
								   Set<String> enabledBuildTypes,
								   boolean mentionChannelEnabled,
								   boolean mentionSlackUserEnabled) {
        this.content = new SlackNotificationContentConfig();
        int Min = 1000000, Max = 1000000000;
        Integer Rand = Min + (int) (Math.random() * ((Max - Min) + 1));
        this.uniqueKey = Rand.toString();
        this.templates = new TreeMap<String, CustomMessageTemplate>();
        this.setToken(token);
        this.setChannel(channel);
        this.setTeamName(teamName);
        this.setEnabled(enabled);
        this.setBuildStates(states);
        this.subProjectsEnabled = buildTypeSubProjects;
        this.allBuildTypesEnabled = buildTypeAllEnabled;
        this.setMentionChannelEnabled(mentionChannelEnabled);
		this.setMentionSlackUserEnabled(mentionSlackUserEnabled);

        if (!this.allBuildTypesEnabled) {
            this.enabledBuildTypesSet = enabledBuildTypes;
        }
    }
	
	public Element getAsElement(){
		Element el = new Element("slackNotification");
		el.setAttribute(CHANNEL, this.getChannel());
		if(StringUtil.isNotEmpty(this.getToken())) {
			el.setAttribute(TOKEN, this.getToken());
		}

        if(StringUtil.isNotEmpty(this.getTeamName())) {
            el.setAttribute(TEAM_NAME, this.getTeamName());
        }
		el.setAttribute(ENABLED, String.valueOf(this.enabled));
        el.setAttribute(CHANNEL_ENABLED_MESSAGE, String.valueOf(this.getMentionChannelEnabled()));
		el.setAttribute(SLACK_USER_ENABLED_MESSAGE, String.valueOf(this.getMentionSlackUserEnabled()));

		Element statesEl = new Element(STATES);
		for (BuildStateEnum state : states.getStateSet()){
			Element e = new Element("state");
			e.setAttribute("type", state.getShortName());
			e.setAttribute(ENABLED, Boolean.toString(states.enabled(state)));
			statesEl.addContent(e);
		}
		el.addContent(statesEl);
		
		Element buildsEl = new Element(BUILD_TYPES);
		buildsEl.setAttribute(ENABLED_FOR_ALL, Boolean.toString(isEnabledForAllBuildsInProject()));
		buildsEl.setAttribute(ENABLED_FOR_SUBPROJECTS, Boolean.toString(isEnabledForSubProjects()));
		
		if (!this.enabledBuildTypesSet.isEmpty()){
			for (String i : enabledBuildTypesSet){
				Element e = new Element("build-type");
				e.setAttribute("id", i);
				buildsEl.addContent(e);
			}
		}
		el.addContent(buildsEl);
		
		if (this.templates.size() > 0){
			Element templatesEl = new Element(CUSTOM_TEMPLATES);
			for (CustomMessageTemplate t : this.templates.values()){
				templatesEl.addContent(t.getAsElement());
			}
			el.addContent(templatesEl);
		}

        if(this.hasCustomContent()){
            Element customContentEl = new Element(CONTENT);
            customContentEl.setAttribute(ICON_URL, this.content.getIconUrl());
            customContentEl.setAttribute(BOT_NAME, this.content.getBotName());
            customContentEl.setAttribute(MAX_COMMITS_TO_DISPLAY, Integer.toString(this.content.getMaxCommitsToDisplay()));
            customContentEl.setAttribute(SHOW_BUILD_AGENT, this.content.getShowBuildAgent().toString());
			customContentEl.setAttribute(SHOW_BUILD_STATUS, this.content.getShowBuildStatus().toString());
			customContentEl.setAttribute(SHOW_TITLE, this.content.getShowTitle().toString());
            customContentEl.setAttribute("showElapsedBuildTime", this.content.getShowElapsedBuildTime().toString());
            customContentEl.setAttribute(SHOW_COMMITS, this.content.getShowCommits().toString());
            customContentEl.setAttribute(SHOW_COMMITTERS, this.content.getShowCommitters().toString());
            customContentEl.setAttribute(SHOW_FAILURE_REASON, this.content.getShowFailureReason().toString());
            el.addContent(customContentEl);
        }
		
		return el;
	}
	
	// Getters and Setters..

	public boolean isEnabledForBuildType(SBuildType sBuildType){
		// If allBuildTypes enabled, return true, otherwise  return whether the build is in the list of enabled buildTypes. 
		return isEnabledForAllBuildsInProject() || enabledBuildTypesSet.contains(TeamCityIdResolver.getInternalBuildId(sBuildType));
	}
	
	boolean isSpecificBuildTypeEnabled(SBuildType sBuildType){
		// Just check if this build type is only enabled for a specific build. 
		return enabledBuildTypesSet.contains(TeamCityIdResolver.getInternalBuildId(sBuildType));
	}

	public String getBuildTypeCountAsFriendlyString() {
		if (this.allBuildTypesEnabled && !this.subProjectsEnabled) {
			return "All builds";
		} else if (this.allBuildTypesEnabled) {
			return "All builds & Sub-Projects";
		}

		String subProjectsString = "";
		if (this.subProjectsEnabled) {
			subProjectsString = " & All Sub-Project builds";
		}
		int enabledBuildTypeCount = this.enabledBuildTypesSet.size();
		if (enabledBuildTypeCount == 1) {
			return enabledBuildTypeCount + " build" + subProjectsString;
		}
		return enabledBuildTypeCount + " builds" + subProjectsString;

	}

	public Boolean getEnabled() {
		return enabled;
	}

	public void setEnabled(Boolean enabled) {
		this.enabled = enabled;
	}

	public BuildState getBuildStates() {
		return states;
	}

	public void setBuildStates(BuildState states) {
		this.states = states;
	}

	public String getToken() {
		return apiToken;
	}

	public void setToken(String apiToken) {
		this.apiToken = apiToken;
	}

	public String getChannel() {
		return channel;
	}

	public void setChannel(String channel) {
		this.channel = channel;
	}

    public String getTeamName() {
        return teamName;
    }

    public void setTeamName(String teamName) {
        this.teamName = teamName;
    }

	public String getUniqueKey() {
		return uniqueKey;
	}

	public void setUniqueKey(String uniqueKey) {
		this.uniqueKey = uniqueKey;
	}
	
	public String getEnabledListAsString(){
		if (!this.enabled){
			return "Disabled";
		} else if (states.allEnabled()){
			return "All Build Events";
		} else if (states.noneEnabled()) {
			return "None";
		} else {
			String enabledStates = "";
			if (states.enabled(BuildStateEnum.BUILD_STARTED)){
				enabledStates += ", Build Started";
			}
//			if (BuildState.enabled(BuildState.BUILD_FINISHED,this.statemask)){
//				enabledStates += ", Build Completed";
//			}
//			if (BuildState.enabled(BuildState.BUILD_CHANGED_STATUS,this.statemask)){
//				enabledStates += ", Build Changed Status";
//			}
			if (states.enabled(BuildStateEnum.BUILD_INTERRUPTED)){
				enabledStates += ", Build Interrupted";
			}
			if (states.enabled(BuildStateEnum.BEFORE_BUILD_FINISHED)){
				enabledStates += ", Build Almost Completed";
			}
			if (states.enabled(BuildStateEnum.RESPONSIBILITY_CHANGED)){
				enabledStates += ", Build Responsibility Changed";
			}
			if (states.enabled(BuildStateEnum.BUILD_FAILED)){
				if (states.enabled(BuildStateEnum.BUILD_BROKEN)){
					enabledStates += ", Build Broken";
				} else {
					enabledStates += ", Build Failed";
				}
			}
			if (states.enabled(BuildStateEnum.BUILD_SUCCESSFUL)){
				if (states.enabled(BuildStateEnum.BUILD_FIXED)){
					enabledStates += ", Build Fixed";
				} else {
					enabledStates += ", Build Successful";
				}
			}
			if (enabledStates.length() > 0){
				return enabledStates.substring(1);
			} else {
				return "None";
			}
		}
	}
	
	public String getSlackNotificationEnabledAsChecked() {
		if (this.enabled){
			return "checked ";
		}
		return ""; 
	}
	
	public String getStateAllAsChecked() {
		if (states.allEnabled()){
			return "checked ";
		}		
		return ""; 
	}
	
	public String getStateBuildStartedAsChecked() {
		if (states.enabled(BUILD_STARTED)){
			return "checked ";
		}
		return ""; 
	}
	
	public String getStateBuildFinishedAsChecked() {
		if (states.enabled(BUILD_FINISHED)){
			return "checked ";
		}
		return ""; 
	}

	public String getStateBeforeFinishedAsChecked() {
		if (states.enabled(BEFORE_BUILD_FINISHED)){
			return "checked ";
		}
		return ""; 
	}

	public String getStateResponsibilityChangedAsChecked() {
		if (states.enabled(RESPONSIBILITY_CHANGED)){
			return "checked ";
		}
		return ""; 
	}

	public String getStateBuildInterruptedAsChecked() {
		if (states.enabled(BUILD_INTERRUPTED)){
			return "checked ";
		}
		return ""; 
	}
	
	public String getStateBuildSuccessfulAsChecked() {
		if (states.enabled(BUILD_SUCCESSFUL)){
			return "checked ";
		}
		return ""; 
	}
	
	public String getStateBuildFixedAsChecked() {
		if (states.enabled(BUILD_FIXED)){
			return "checked ";
		}
		return ""; 
	}
	
	public String getStateBuildFailedAsChecked() {
		if (states.enabled(BUILD_FAILED)){
			return "checked ";
		}
		return ""; 
	}

	public String getStateBuildBrokenAsChecked() {
		if (states.enabled(BUILD_BROKEN)){
			return "checked ";
		}
		return ""; 
	}

	public Boolean isEnabledForAllBuildsInProject() {
		return allBuildTypesEnabled;
	}

	public void enableForAllBuildsInProject(Boolean allBuildTypesEnabled) {
		this.allBuildTypesEnabled = allBuildTypesEnabled;
	}
	
	public Boolean isEnabledForSubProjects() {
		return subProjectsEnabled;
	}
	
	public void enableForSubProjects(Boolean subProjectsEnabled) {
		this.subProjectsEnabled = subProjectsEnabled;
	}
	
	public void clearAllEnabledBuildsInProject(){
		this.enabledBuildTypesSet.clear();
	}
	
	public void enableBuildInProject(String buildTypeId) {
		this.enabledBuildTypesSet.add(buildTypeId);
	}

    public void setMentionChannelEnabled(boolean mentionChannelEnabled) {
        this.mentionChannelEnabled = mentionChannelEnabled;
    }

    public boolean getMentionChannelEnabled() {
        return mentionChannelEnabled;
    }

	public void setMentionSlackUserEnabled(boolean mentionSlackUserEnabled) {
		this.mentionSlackUserEnabled = mentionSlackUserEnabled;
	}

	public boolean getMentionSlackUserEnabled() {
		return mentionSlackUserEnabled;
	}

    public boolean hasCustomContent() {
        return customContent;
    }

    public void setHasCustomContent(boolean customContent) {
        this.customContent = customContent;
    }

    public SlackNotificationContentConfig getContent() {
        return content;
    }

    public void setContent(SlackNotificationContentConfig content) {
        this.content = content;
    }
}
