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

import static de.srsoftware.umbrella.core.constants.Field.*;
import static java.text.MessageFormat.format;

import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.Util;
import de.srsoftware.umbrella.core.api.Owner;
import de.srsoftware.umbrella.core.constants.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

public class Poll implements Mappable {
	public static class Option implements Mappable{

		private int id;

		Integer status;
		private String description;
		private String name;
		private final Set<String> dirtyFields = new HashSet<>();
		public Option(int id, String name, String description, Integer status) {
			this.id          = id;
			this.name        = name;
			this.description = description;
			this.status      = status;
		}
		public String description(){
			return description;
		}

		public Option description(String newVal){
			description = newVal;
			dirtyFields.add(DESCRIPTION);
			return this;
		}

		public int id(){
			return id;
		}

		public Option id(Integer newVal){
			this.id = newVal;
			dirtyFields.add(ID);
			return this;
		}

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

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

		public String name(){
			return name;
		}

		public Option name(String newVal){
			name = newVal;
			dirtyFields.add(NAME);
			return this;
		}

		public static Option of(ResultSet rs) throws SQLException {
			var id          = rs.getInt(ID);
			var name        = rs.getString(NAME);
			var description = rs.getString(DESCRIPTION);
			var status      = rs.getInt(STATUS);

			return new Option(id,name,description,status);
		}

		public Integer status(){
			return status;
		}

		public Option status(int newValue){
			this.status = newValue;
			dirtyFields.add(STATUS);
			return this;
		}

		@Override
		public Map<String, Object> toMap() {
			return Map.of(
					ID,id,
					NAME,name,
					DESCRIPTION, Util.mapMarkdown(description),
					STATUS,status
			);
		}

		@Override
		public String toString() {
			return format("Option \"{0}\"",name);
		}


	}
	public static class Evaluation {
		private static class Histogram extends HashMap<Integer,Integer>{
			public Double average() {
				var sum = 0;
				var count = 0;
				for (var entry : entrySet()){
					var weight = entry.getKey();
					var votes  = entry.getValue();
					count += votes;
					sum   += weight * votes;
				}
				if (count < 1) return null;
				return sum/(double) count;
			}
		}
		private static class OptionStats extends HashMap<Integer,Histogram>{}
		private static class Stats extends HashMap<Double,OptionStats>{
			public Stats(OptionStats optionStats) {
				for (var entry : optionStats.entrySet()){
					var optionId = entry.getKey();
					var histo    = entry.getValue();
					var average  = histo.average();
					var os = get(average);
					if (os == null) put(average,os = new OptionStats());
					os.put(optionId,histo);
				}
			}
		}
		/*

		{
			options : {
				1: option 1
				2: option 2
			},
			stat : {
				0.571 : { // average
					1 : { // option
						-2 : 0, // weight → selections
						-1 : 1,
						 0 : 2
						 1 : 3
						 2 : 1
					}
					2 :
				}
			}
		}

		 */

		private final Map<Integer, Option> options = new HashMap<>();
		private final OptionStats optionStats = new OptionStats();

		public Evaluation(Poll poll) {
			for (var option : poll.options){
				options.put(option.id,option);
				var histo = new Histogram();
				for (var w : poll.weights.keySet()) histo.put(w,0);
				optionStats.put(option.id,histo);
			}

		}

		public void count(ResultSet rs) throws SQLException {
			var optionId  = rs.getInt(OPTION_ID);
			//var userId  = rs.getObject(USER);
			var weight    = rs.getInt(WEIGHT);
			var histogram = optionStats.get(optionId);
			histogram.put(weight,histogram.get(weight)+1);
		}

		public Map<Double,?> toMap() {
			return new Stats(optionStats);
		}

	}
	private Owner owner;
	private String id, name, description;

	private boolean isPrivate;
	private List<Option> options;
	private Map<Integer,String> weights;
	private Map<UmbrellaUser,Permission> permissions;
	private Set<String> dirtyFields = new HashSet<>();
	public Poll(String id, Owner owner, String name, String description, boolean isPrivate, List<Option> options, Map<Integer,String> weights){
		this.id          = id;
		this.owner       = owner;
		this.name        = name;
		this.description = description;
		this.isPrivate   = isPrivate;
		this.options     = new ArrayList<>();
		this.weights     = new HashMap<>();
		this.permissions = new HashMap<>();
	}
	public String description(){
		return description;
	}

	public Poll description(String newVal){
		description = newVal;
		dirtyFields.add(DESCRIPTION);
		return this;
	}

	public String id(){
		return id;
	}

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

	public boolean isDirty(String ... fields){
		for (var field : fields){
			if (dirtyFields.contains(field)) return true;
		}
		return false;
	}

	public boolean isPrivate(){
		return isPrivate;
	}

	private Map<Long, Map<String, Object>> mapPermissions() {
		var result = new HashMap<Long,Map<String,Object>>();
		for (var entry : permissions.entrySet()){
			var user = entry.getKey();
			var data = user.toMap();
			data.put(Field.PERMISSION,entry.getValue().toMap());
			result.put(user.id(),data);
		}
		return result;
	}

	public String name(){
		return name;
	}

	public Poll name(String newVal){
		name = newVal;
		dirtyFields.add(NAME);
		return this;
	}

	public static Poll of(ResultSet rs) throws SQLException {
		var id          = rs.getString(ID);
		var userId      = rs.getLong(Field.USER_ID);
		var name        = rs.getString(NAME);
		var description = rs.getString(Field.DESCRIPTION);
		var isPrivate   = rs.getBoolean(Field.PRIVATE);
		var owner       = ModuleRegistry.userService().loadUser(userId);

		return new Poll(id,owner,name,description,isPrivate,new ArrayList<>(),new HashMap<>());
	}

	public List<Option> options(){
		return options;
	}

	public Owner owner(){
		return owner;
	}

	public Poll setPrivate(boolean newValue) {
		if (newValue != isPrivate) dirtyFields.add(PRIVATE);
		isPrivate = newValue;
		return this;
	}

	public Poll setWeight(Integer weight, String description) {
		weights.put(weight,description);
		dirtyFields.add(WEIGHTS);
		return this;
	}

	public Map<UmbrellaUser, Permission> permissions(){
		return permissions;
	}

	@Override
	public Map<String, Object> toMap() {
		return Map.of(
				ID, id,
				Field.OWNER, owner.toMap(),
				NAME,name,
				Field.DESCRIPTION, Util.mapMarkdown(description),
				Field.OPTIONS, options.stream().collect(Collectors.toMap(Option::id,Option::toMap)),
				Field.PERMISSION, mapPermissions(),
				Field.PRIVATE, isPrivate,
				Field.WEIGHTS, weights
		);
	}

	public Map<Integer, String> weights(){
		return weights;
	}
}
