/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model;

import static de.srsoftware.umbrella.core.constants.Field.*;

import de.srsoftware.tools.Pair;
import de.srsoftware.umbrella.core.constants.Field;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;

public class PositionList extends HashMap<Integer,Position> {
	private long docId;

	public PositionList add(Position pos) {
		put(pos.num(),pos);
		pos.setDocId(docId);
		return this;
	}

	public Position addItem(JSONObject item) {
		var itemCode = item.getString(Field.CODE);
		var unit = item.getString(UNIT);
		var name = item.getString(NAME);
		var description = item.getString(DESCRIPTION);
		var unitPrice = item.getLong(UNIT_PRICE);
		var tax = item.getInt(TAX);
		var position = new Position(size()+1,itemCode,1.0,unit,name,description,unitPrice,tax,null,false);
		add(position);
		return position;
	}

	public Position addTask(JSONObject task) {
		var amount = task.getLong("est_time");
		var name = task.getString(NAME);
		var description = task.getString(DESCRIPTION);
		var unitPrice = 0;
		var tax = 19; // TODO: make adjustable
		var position = new Position(size()+1,null,amount,null,name,description,unitPrice,tax,null,false);
		add(position);
		return position;
	}


	public Position addTime(JSONObject time) {
		var id = time.getLong(ID);
		var start = time.getLong(START_TIME);
		var end = time.getLong(END_TIME);
		var amount = (end - start)/3600d;
		var name = time.getString(SUBJECT);
		var desc = time.getString(DESCRIPTION).trim();
		var description = new StringBuilder();
		if (!desc.isEmpty()) description.append(desc);
		if (time.has(TASKS)){
			var tasks = time.getJSONObject(TASKS);
			var taskKeys = tasks.keySet();
			if (taskKeys.size()>1) { // description normally came from first task
				if (!desc.isEmpty()) description.append("\n\n");
				for (var taskId : tasks.keySet()) {
					var task = tasks.getJSONObject(taskId);
					description.append("* ").append(task.getString(NAME)).append("\n");
				}
			}
		}
		var unitPrice = 0;
		var tax = 19; // TODO: make adjustable
		var position = new Position(size()+1,null,amount,null,name,description.toString(),unitPrice,tax,id,false);
		add(position);
		return position;
	}

	public Map<Integer,Object> asMap(boolean renderMarkdown) {
		var map = new HashMap<Integer,Object>();
		for (var entry : entrySet()) {
			var pos = entry.getValue();
			map.put(entry.getKey(),renderMarkdown ? pos.renderToMap() : pos.toMap());
		}
		return map;
	}

	public Pair<Integer> getMoveablePair(JSONObject data) throws UmbrellaException {
		if (!data.has("pos")) throw new UmbrellaException(400,"Missing nested value move.pos!");
		if (!data.has("step")) throw new UmbrellaException(400,"Missing nested value move.step!");
		if (!(data.get("pos") instanceof Number pos)) throw new UmbrellaException(400,"Invalid value for move.pos");
		if (!(data.get("step") instanceof Number step)) throw new UmbrellaException(400,"Invalid value for move.step");
		int one = pos.intValue();
		int other = one + (step.longValue() < 0 ? -1 : 1);
		if (!containsKey(one) || !containsKey(other)) throw new UmbrellaException(400,"Invalid value for move.pos");
		return new Pair<>(one,other);
	}

	public long grossSum(boolean includeOptionals) {
		var gross = netSum(includeOptionals);
		for (var taxSum : taxNetSums(includeOptionals).entrySet()) {
			var percent = taxSum.getKey();
			var netSum = taxSum.getValue();
			gross += Math.round(netSum * percent / 100d);
		}
		return gross;
	}

	public boolean isDirty() {
		return values().stream().anyMatch(Position::isDirty);
	}

	public long netSum(boolean includeOptionals) {
		long sum = 0;
		for (var pos : values()){
			if (includeOptionals || !pos.optional()) sum += pos.netPrice();
		}
		return sum;
	}

	public void patch(JSONObject json) {
		for (var key : json.keySet()){
			try {
				var pos = Integer.parseInt(key);
				if (get(pos) instanceof Position position && json.get(key) instanceof JSONObject data) position.patch(data);
			} catch (NumberFormatException ignored){}
		}
	}

	public void setDocId(long id) {
		docId = id;
	}

	/**
	 * Map from Percent to net sums of relevant positions
	 * @param includeOptionals
	 * @return
	 */
	public Map<Integer,Long> taxNetSums(boolean includeOptionals){
		var netSums = new HashMap<Integer,Long>();
		for (var pos : values()){
			if (includeOptionals || !pos.optional()) {
				var tax = pos.tax();
				var netSumForTax = netSums.get(tax);
				if (netSumForTax == null) netSumForTax = 0L;
				netSumForTax += pos.netPrice();
				netSums.put(tax, netSumForTax);
			}
		}
		return netSums;
	}


}
