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

import static java.text.MessageFormat.format;

import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.constants.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Transaction implements Mappable {
	private long accountId, id;
	private LocalDateTime date;
	private IdOrString source, destination;
	private double amount;
	private String purpose;
	private Set<String> tags;
	private HashSet<String> dirtyFields = new HashSet<>();

	public Transaction(long id, long accountId, LocalDateTime date, IdOrString source, IdOrString destination, double amount, String purpose, Set<String> tags){
		this.id          = id;
		this.accountId   = accountId;
		this.date        = date;
		this.source      = source;
		this.destination = destination;
		this.amount      = amount;
		this.purpose     = purpose;
		this.tags        = tags == null ? new HashSet<>() : tags;
	}

	public long accountId(){
		return accountId;
	}

	public double amount(){
		return amount;
	}

	public Transaction amount(double newVal){
		amount = newVal;
		dirtyFields.add(Field.AMOUNT);
		return this;
	}

	public Transaction clearDirtyState(){
		dirtyFields.clear();
		return this;
	}

	public LocalDateTime date(){
		return date;
	}

	public Transaction date(LocalDateTime newVal){
		date = newVal;
		dirtyFields.add(Field.DATE);
		return this;
	}

	public Transaction date(LocalDate date) {
		return date(this.date.withYear(date.getYear()).withMonth(date.getMonthValue()).withDayOfMonth(date.getDayOfMonth()));
	}

	public IdOrString destination(){
		return destination;
	}

	public Transaction destination(IdOrString newVal){
		destination = newVal;
		dirtyFields.add(Field.DESTINATION);
		return this;
	}

	public long id(){
		return id;
	}

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

	public static Transaction of(ResultSet rs) throws SQLException {
		var accountId   = rs.getLong(Field.ACCOUNT);
		var timestamp   = rs.getLong(Field.TIMESTAMP);
		var date        = LocalDateTime.ofEpochSecond(timestamp,0, ZoneOffset.UTC);
		var source      = IdOrString.of(rs.getString(Field.SOURCE));
		var destination = IdOrString.of(rs.getString(Field.DESTINATION));
		var amount      = rs.getDouble(Field.AMOUNT);
		var purpose     = rs.getString(Field.DESCRIPTION);
		var id          = rs.getLong(Field.ID);
		return new Transaction(id, accountId, date, source, destination, amount, purpose, null);
	}

	public String purpose(){
		return purpose;
	}

	public Transaction purpose(String newVal){
		purpose = newVal;
		dirtyFields.add(Field.PURPOSE);
		return this;
	}

	public IdOrString source(){
		return source;
	}

	public Transaction source(IdOrString newVal){
		source = newVal;
		dirtyFields.add(Field.SOURCE);
		return this;
	}

	public Set<String> tags(){
		return tags;
	}

	@Override
	public Map<String, Object> toMap() {
		return Map.of(
				Field.ID, id,
				Field.ACCOUNT, accountId,
				Field.DATE, date.toLocalDate(),
				Field.SOURCE, source.toMap(),
				Field.DESTINATION, destination.toMap(),
				Field.AMOUNT, amount,
				Field.PURPOSE, purpose,
				Field.TAGS, tags.stream().sorted().toList()
		);
	}

	@Override
	public String toString() {
		return format("Transaction ({0} -[{1}]-> {2})",source,amount,destination);
	}

	public Transaction withId(long id) {
		return new Transaction(id, accountId, date, source, destination, amount, purpose, new HashSet<>(tags));
	}
}
