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


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

import de.srsoftware.tools.Mappable;
import java.util.*;
import org.json.JSONObject;

public final class Position implements Mappable {
	public static final System.Logger LOG = System.getLogger(Position.class.getSimpleName());
	private long docId;
	private final int num;
	private String itemCode;
	private double amount;
	private String unit;
	private String title;
	private String description;
	private long unitPrice;
	private int tax;
	private final Long timeId;
	private boolean optional;
	private Set<String> dirtyFields = new HashSet<>();

	public Position(int num, String itemCode, double amount, String unit, String title, String description, long unitPrice, int tax, Long timeId, boolean optional) {
		this.num = num;
		this.itemCode = itemCode;
		this.amount = amount;
		this.unit = unit;
		this.title = title;
		this.description = description;
		this.unitPrice = unitPrice;
		this.tax = tax;
		this.timeId = timeId;
		this.optional = optional;
	}

	public double amount() {
		return amount;
	}

	public void clean() {
		dirtyFields.clear();
	}

	public String description() {
		return description;
	}

	public long docId() {
		return docId;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this) return true;
		if (obj == null || obj.getClass() != this.getClass()) return false;
		var that = (Position) obj;
		return this.docId == that.docId &&
				this.num == that.num &&
				Objects.equals(this.itemCode, that.itemCode) &&
				Double.doubleToLongBits(this.amount) == Double.doubleToLongBits(that.amount) &&
				Objects.equals(this.unit, that.unit) &&
				Objects.equals(this.title, that.title) &&
				Objects.equals(this.description, that.description) &&
				this.unitPrice == that.unitPrice &&
				this.tax == that.tax &&
				this.timeId == that.timeId &&
				this.optional == that.optional;
	}

	public long grossPrice() {
		var taxRate = (100 + tax) / 100d;
		var sum = unitPrice * amount * taxRate;
		return Math.round(sum);
	}

	@Override
	public int hashCode() {
		return Objects.hash(docId, num, itemCode, amount, unit, title, description, unitPrice, tax, timeId, optional);
	}

	public boolean isDirty() {
		return !dirtyFields.isEmpty();
	}

	public boolean isDirty(String field){
		return dirtyFields.contains(field);
	}

	public boolean isNew(){
		return dirtyFields.contains(DOCUMENT_ID);
	}

	public String itemCode() {
		return itemCode;
	}

	public long netPrice() {
		return Math.round(amount * unitPrice);
	}

	public int num() {
		return num;
	}

	public boolean optional() {
		return optional;
	}

	public void patch(JSONObject json) {
		for (var key : json.keySet()) {
			switch (key) {
				case AMOUNT:      amount = json.getDouble(key); break;
				case DESCRIPTION: description = json.getString(key); break;
				case ITEM:        itemCode = json.getString(key); break;
				case OPTIONAL:    optional = json.getBoolean(key); break;
				case TAX:         tax = json.getInt(key); break;
				case TITLE:       title = json.getString(key); break;
				case UNIT:        unit = json.getString(key); break;
				case UNIT_PRICE:  unitPrice = json.getLong(key); break;
				default: key = null;
			}
			if (key != null) dirtyFields.add(key);
		}
	}

	public Position setDocId(long docId) {
		this.docId = docId;
		dirtyFields.add(DOCUMENT_ID);
		return this;
	}

	public Position setItemCode(String newValue) {
		this.itemCode = newValue;
		dirtyFields.add(ITEM);
		return this;
	}

	public Position setUnit(String newValue) {
		unit = newValue;
		dirtyFields.add(UNIT);
		return this;
	}

	public void setUnitPrice(long price) {
		unitPrice = price;
		dirtyFields.add(UNIT_PRICE);
	}

	public int tax() {
		return tax;
	}

	public long taxAmount() {
		var taxRate = tax / 100d;
		var tax = unitPrice * amount * taxRate;
		return Math.round(tax);
	}

	public Long timeId() {
		return timeId;
	}

	public String title() {
		return title;
	}

	@Override
	public Map<String, Object> toMap() {
		var map = new HashMap<String, Object>();
		map.put(DOCUMENT_ID, docId);
		map.put(NUMBER, num);
		map.put(ITEM, itemCode);
		map.put(AMOUNT, amount);
		map.put(UNIT, unit);
		map.put(TITLE, title);
		map.put(DESCRIPTION, description);
		map.put(UNIT_PRICE, unitPrice);
		map.put(TAX, tax);
		map.put(TIME_ID, timeId);
		map.put(OPTIONAL, optional);
		map.put(NET_PRICE, netPrice());
		return map;
	}

	public Map<String, Object> renderToMap() {
		var map = new HashMap<String, Object>();
		map.put(DOCUMENT_ID, docId);
		map.put(NUMBER, num);
		map.put(ITEM, itemCode);
		map.put(AMOUNT, amount);
		map.put(UNIT, unit);
		map.put(TITLE, title);
		map.put(DESCRIPTION, mapMarkdown(description));
		map.put(UNIT_PRICE, unitPrice);
		map.put(TAX, tax);
		map.put(TIME_ID, timeId);
		map.put(OPTIONAL, optional);
		map.put(NET_PRICE, netPrice());
		return map;
	}


	@Override
	public String toString() {
		return "Position[" +
				"docId=" + docId + ", " +
				"num=" + num + ", " +
				"itemCode=" + itemCode + ", " +
				"amount=" + amount + ", " +
				"unit=" + unit + ", " +
				"title=" + title + ", " +
				"description=" + description + ", " +
				"unitPrice=" + unitPrice + ", " +
				"tax=" + tax + ", " +
				"timeId=" + timeId + ", " +
				"optional=" + optional + ']';
	}

	public String unit() {
		return unit;
	}

	public long unitPrice() {
		return unitPrice;
	}
}
