package com.pmease.quickbuild.plugin.tracker.redmine;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.hibernate.validator.constraints.NotEmpty;

import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.pmease.quickbuild.Context;
import com.pmease.quickbuild.QuickbuildException;
import com.pmease.quickbuild.annotation.Editable;
import com.pmease.quickbuild.annotation.ExpressionProvider;
import com.pmease.quickbuild.annotation.Multiline;
import com.pmease.quickbuild.annotation.ScriptApi;
import com.pmease.quickbuild.annotation.Scriptable;
import com.pmease.quickbuild.plugin.report.engine.util.CaseInsensitiveMap;
import com.pmease.quickbuild.plugin.tracker.core.TrackerException;
import com.pmease.quickbuild.plugin.tracker.core.extensionpoint.Issue;
import com.pmease.quickbuild.plugin.tracker.core.step.AbstractIssueStep;
import com.pmease.quickbuild.plugin.tracker.core.utils.StringHelper;
import com.pmease.quickbuild.plugin.tracker.core.utils.TrackerHelper;

@Editable(name="Create Redmine Issue", category="Issue Tracker", description=
		"This step creates a bug/issue in defined Redmine server")
public class CreateIssueStep extends AbstractIssueStep {

	private static final long serialVersionUID = 1L;

	private String issueSummary;
	private String issueDescription;
	private String issuePriority;
	private String trackerType;
	private String author;
	private String assignedTo;
	
	@Override
	public void run() {
		onBeforeRun();
		
		RedmineCli cli = RedmineCli.newInstance();

		String extraOptions = getExtraAttributes();
		Map<String, String> map = StringHelper.getOptionsFromDefinition(extraOptions);
		map.put(RedmineHelper.SUBJECT, getIssueSummary());
		
		String str = getIssueDescription();
		if (!Strings.isNullOrEmpty(str)) {
			map.put(RedmineHelper.DESCRIPTION, str);
		}
		
		str = getIssuePriority();
		if (!Strings.isNullOrEmpty(str)) {
			BiMap<Integer, String> bm = cli.getPriorities();
			Integer id = bm.inverse().get(str);
			if (id == null) {
				throw new QuickbuildException("Priority " + str + " doesn't exist");
			}
			
			map.put(RedmineHelper.PRIORITY_ID, String.valueOf(id));
		}
		
		str = getTrackerType();
		if (!Strings.isNullOrEmpty(str)) {
			BiMap<Integer, String> bm = cli.getTrackers();
			Integer id = bm.inverse().get(str);
			if (id == null) {
				throw new QuickbuildException("Tracker " + str + " doesn't exist");
			}
			
			map.put(RedmineHelper.TRACKER_ID, String.valueOf(id));
		}
		
		str = getAuthor();
		if (!Strings.isNullOrEmpty(str)) {
			Integer id = cli.findUserId(str);
			map.put(RedmineHelper.AUTHOR_ID, String.valueOf(id));
		}
		
		str = getAssignedTo();
		if (!Strings.isNullOrEmpty(str)) {
			Integer id = cli.findUserId(str);
			map.put(RedmineHelper.ASSIGNED_TO_ID, String.valueOf(id));
		}
		
		// change name to id
		//
		adjustCustomFields(cli, map);
		
		Issue issue = cli.createIssue(map);
		
		getIssueActions().put(issue.getIssueKey(), "create");
		Context.getLogger().info("Issue: " + issue + " created successfully.");
		onAfterRun();
	}

	private void adjustCustomFields(RedmineCli cli, Map<String, String> map) {
		Map<String, String> customMap = Maps.newHashMap();
		CaseInsensitiveMap<String> cm = (CaseInsensitiveMap<String>) map;
		for (Map.Entry<String, String> entry : map.entrySet()) {
			if (entry.getKey().startsWith(RedmineHelper.CUSTOM_FIELD)) {
				Long id = TrackerHelper.extractLong(entry.getKey());
				if (id == null) {
					customMap.put(cm.getOriginalKey(entry.getKey()), entry.getValue());
				}
			}
		}
		
		if (!customMap.isEmpty()) {
			BiMap<Integer, String> fields = null;
			try {
				fields = cli.getCustomFields();
			} catch (TrackerException e) {
				throw new QuickbuildException("Unable to get custom fields response, " +
						"if you are using custom field name, only Redmine 2.4 or later are supported.", e);
			}
			
			Map<String, Integer> names = fields.inverse();
			for (Map.Entry<String, String> entry : customMap.entrySet()) {
				String key = entry.getKey();
				map.remove(key);
				
				String name = key.substring(RedmineHelper.CUSTOM_FIELD.length());
				Integer id = names.get(name);
				map.put(RedmineHelper.CUSTOM_FIELD + id, entry.getValue());
			}
		}
	}
	
	@Editable(name = "Issue Subject", order = 1000)
	@NotEmpty
	@Scriptable
	@ScriptApi("Get issue summary")
	public String getIssueSummary() {
		return issueSummary;
	}

	public void setIssueSummary(String issueSummary) {
		this.issueSummary = issueSummary;
	}

	@Editable(name="Issue Description", order=1100)
	@Multiline
	@Scriptable
	@ScriptApi("Get issue description")
	public String getIssueDescription() {
		return issueDescription;
	}

	public void setIssueDescription(String issueDescription) {
		this.issueDescription = issueDescription;
	}

	@Editable(name="Tracker Type", order=1200, description = "Specify the issue tracker")
	@Scriptable
	@ScriptApi("Get issue tracker type")
	@ExpressionProvider("getAvailableTypes")
	public String getTrackerType() {
		return trackerType;
	}

	public void setTrackerType(String trackerType) {
		this.trackerType = trackerType;
	}
	
	@Editable(name="Issue Priority", description = "Specify the issue priority", order=1300)
	@Scriptable
	@ScriptApi("Get issue priority")
	@ExpressionProvider("getAvailablePriorities")
	public String getIssuePriority() {
		return issuePriority;
	}

	public void setIssuePriority(String issuePriority) {
		this.issuePriority = issuePriority;
	}

	@Editable(name="Author", description = "Specify the author name of the created issue", order=1500)
	@Scriptable
	@ScriptApi("Get author who will create the issue")
	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	@Editable(name="Assigned to", description = "Specify the assignee of the created issue", order=1600)
	@Scriptable
	@ScriptApi("Get assigned to user")
	public String getAssignedTo() {
		return assignedTo;
	}

	public void setAssignedTo(String assignedTo) {
		this.assignedTo = assignedTo;
	}

	@Override
	@Editable(order=3000, name="Extra Attributes", description="Specify extra attributes of the issue, " +
			"with one attribute per line. For example:<br/>" +
			"<code>field_1=value1<br/>" +
			"field2=value2</code>" +
			"For custom fields, you can specify in format <i>cf_</i>{id} or <i>cf_</i>{name} like:<br/>" +
			"<code>cf_1 or cf_my field</code>, see more information on page: http://www.redmine.org/projects/redmine/wiki/Rest_Issues")
	@Multiline
	@Scriptable
	@ScriptApi("Get extra attributes which will be used to create issue.")
	public String getExtraAttributes() {
		return super.getExtraAttributes();
	}
	
	@SuppressWarnings("unused")
	private static Map<String, String> getAvailablePriorities() {
		RedmineCli cli = RedmineCli.newInstance();
		BiMap<Integer, String> map = cli.getPriorities();
		return biMapToMap(map);
	}
	
	private static Map<String, String> biMapToMap(BiMap<Integer, String> map) {
		Map<String, String> result = Maps.newLinkedHashMap();
		List<Integer> list = Lists.newArrayList(map.keySet());
		Collections.sort(list);
		for (Integer each : list) {
			result.put(map.get(each), map.get(each));
		}
		
		return result;
	}
	
	@SuppressWarnings("unused")
	private static Map<String, String> getAvailableTypes() {
		RedmineCli cli = RedmineCli.newInstance();
		BiMap<Integer, String> map = cli.getTrackers();
		return biMapToMap(map);
	}
}
