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

import static de.srsoftware.umbrella.core.Util.*;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Translatable.t;

import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.constants.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import org.json.JSONObject;

public class Time implements Mappable{
	private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
	private final HashSet<Long> taskIds = new HashSet<>();
	private long start;
	private Long end;
	private long id;
	private final long userId;
	private String description;
	private String subject;
	private State state;

	public enum State{
		Started(10),
		Open(20),
		Pending(40),
		Complete(60),
		Cancelled(100);

		private final int code;

		State(int code){
			this.code = code;
		}

		public int code(){
			return code;
		}

		public static State of(int code){
			return switch (code){
				case 10 -> Started;
				case 20 -> Open;
				case 40 -> Pending;
				case 60 -> Complete;
				case 100 -> Cancelled;
				default -> throw new IllegalArgumentException();
			};
		}
	}

	public Time(long id, long userId, String subject, String description, long start, Long end, State state, Collection<Long> taskIds){
		this.id=id;
		this.userId = userId;
		this.subject = subject;
		this.description = description;
		this.start = start;
		this.end = end;
		this.state = state;
		if (taskIds != null) this.taskIds.addAll(taskIds);
	}

	public String description(){
		return description;
	}

	public Time description(String newVal) {
		description = newVal;
		return this;
	}

	public Long end(){
		return end;
	}

	public Time end(Long newVal) {
		end = newVal;
		return this;
	}

	public long id(){
		return id;
	}

	public boolean isClosed() {
		return switch (state){
			case Complete, Cancelled -> true;
			case null, default -> false;
		};
	}

	public static Time of(ResultSet rs) throws SQLException {
		var start = rs.getLong(START_TIME);
		Long end = rs.getLong(END_TIME);

		if (end == 0) end = null;

		return new Time(
				rs.getLong(ID),
				rs.getLong(USER_ID),
				rs.getString(SUBJECT),
				rs.getString(DESCRIPTION),
				start,
				end,
				State.of(rs.getInt(STATE)),
				new HashSet<>()
		);
	}

	private LocalDateTime parse(String time){
		if (time.contains("T")) return LocalDateTime.parse(time);
		return LocalDateTime.parse(time,DT);
	}

	public Time patch(JSONObject json) {
		if (json.has(SUBJECT) && json.get(SUBJECT) instanceof String s) subject = s;
		if (json.has(DESCRIPTION)) {
			var o = json.get(DESCRIPTION);
			if (o instanceof JSONObject nested && nested.get(SOURCE) instanceof String src) o = src;
			if (o instanceof String d) description = d;
		}
		if (json.has(START_TIME) && json.get(START_TIME) instanceof Number st) start = st.longValue();
		if (json.has(END_TIME) && json.get(END_TIME) instanceof Number e) end = e.longValue();
        if (end == 0) end = null;
		if (end != null && end < start) throw invalidField(END_TIME,"after start_time");
		if (json.has(STATE)) {
			var o = json.get(STATE);
			try {
				switch (o) {
					case JSONObject nested:
						state = State.of(nested.getInt(Field.CODE));
						break;
					case String stateName:
						state = State.valueOf(stateName);
						break;
					case Number stateCode:
						state = State.of(stateCode.intValue());
						break;
					default:
						throw invalidField(STATE, t("state name or code"));
				}
			} catch (Exception e){
				throw unprocessable("\"{value}\" is not a valid state name or code!",VALUE,o);
			}
		}
		return this;
	}

	public void setId(long newValue) {
		id = newValue;
	}

	public long start(){
		return start;
	}



    public State state(){
	return state;
    }

	public Time stop(long endTime) {
		end   = endTime;
		state = State.Open;
		return this;
	}

	public String subject(){
		return subject;
	}

	public Time subject(String newValue) {
		subject = newValue;
		return this;
	}

	public Collection<Long> taskIds(){
		return taskIds;
	}

	@Override
	public Map<String, Object> toMap() {
		var map = new HashMap<String,Object>();
		map.put(ID,id);
		map.put(USER_ID,userId);
		map.put(SUBJECT,subject);
		map.put(DESCRIPTION,mapMarkdown(description));
		map.put(START_TIME,start);
		map.put(END_TIME,end);
		map.put(STATE,Map.of(STATUS_CODE,state.code, NAME,state.name()));
		map.put(DURATION,end == null ? null : (end - start)/3600d);
		map.put(TASK_IDS,taskIds);
		return map;
	}

	public long userId(){
		return userId;
	}
}
