package io.openems.backend.alerting.message;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.gson.JsonObject;

import io.openems.backend.alerting.Message;
import io.openems.backend.common.alerting.SumStateAlertingSetting;
import io.openems.backend.common.mail.MailContext;
import io.openems.common.channel.Level;
import io.openems.common.utils.JsonUtils;

public class SumStateMessage extends Message {

	public static final String TEMPLATE = "alerting_sum_state";

	private final List<SumStateAlertingSetting> recipients;

	private Level sumState;
	private ZonedDateTime stateSince;

	public SumStateMessage(String edgeId, Level sumState, ZonedDateTime stateSince,
			List<SumStateAlertingSetting> recipients) {
		super(edgeId);
		this.stateSince = stateSince;
		this.sumState = sumState;
		this.recipients = new ArrayList<>(recipients);
	}

	public Level getSumState() {
		return this.sumState;
	}

	public ZonedDateTime getStateSince() {
		return this.stateSince;
	}

	private OptionalInt minimumSetting() {
		return this.recipients.stream().mapToInt(r -> r.getDelay(this.getSumState())).filter(i -> i > 0).min();
	}

	@Override
	public ZonedDateTime getNotifyStamp() {
		var min = this.minimumSetting().orElse(0);
		return this.getStateSince().plusMinutes(min);
	}

	public void setSumState(Level sumState, ZonedDateTime now) {
		this.stateSince = now;
		this.sumState = sumState;
	}

	public String getEdgeId() {
		return super.getId();
	}

	/**
	 * Get a stream of settings, which are closest to be sent.
	 * 
	 * @return {@link Stream} of recipients
	 */
	private Stream<SumStateAlertingSetting> currentRecipientStream() {
		var min = this.minimumSetting();
		if (min.isEmpty()) {
			return Stream.empty();
		}
		return this.recipients.stream() //
				.filter(r -> r.getDelay(this.getSumState()) == min.getAsInt());
	}

	/**
	 * Get the list of settings, which are closest to be sent.
	 * 
	 * @return {@link List} of recipients
	 */
	public List<SumStateAlertingSetting> getCurrentRecipients() {
		return this.currentRecipientStream().toList();
	}

	@Override
	public MailContext getContext() {
		return new SumStateEdgeMailContext(this);
	}

	@Override
	public String toString() {
		final var localNotify = this.getNotifyStamp().withZoneSameInstant(ZoneId.systemDefault()).toString();
		final var localSince = this.getStateSince().withZoneSameInstant(ZoneId.systemDefault()).toString();

		final var rec = this.currentRecipientStream() //
				.map(SumStateAlertingSetting::userLogin) //
				.collect(Collectors.joining(","));

		return SumStateMessage.class.getSimpleName() + "{for=" + this.getEdgeId() + ", to=[" + rec + "], at="
				+ localNotify //
				+ ", state=" + this.getSumState().getName() + ", since=" + localSince + "}";
	}

	/**
	 * Update and prepare message for a next schedule.
	 *
	 * @return true if should be rescheduled;
	 */
	public boolean update() {
		this.recipients.removeAll(this.getCurrentRecipients());
		return !this.isEmpty();
	}

	public boolean isEmpty() {
		return this.minimumSetting().isEmpty();
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof SumStateMessage other //
				&& this.getEdgeId().equals(other.getEdgeId()) //
				&& this.stateSince.equals(other.stateSince) //
				&& this.sumState.equals(other.sumState) //
				&& this.recipients.equals(other.recipients);
	}

	@Override
	public int hashCode() {
		return Objects.hash(this.getEdgeId(), this.stateSince, this.sumState, this.recipients);
	}

	private static class SumStateEdgeMailContext extends MailContext {

		private final String state;

		public SumStateEdgeMailContext(SumStateMessage msg) {
			super(msg.getEdgeId(), msg.currentRecipientStream() //
					.map(SumStateAlertingSetting::userLogin) //
					.toList());
			this.state = msg.getSumState().getName();
		}

		@Override
		public JsonObject toJson() {
			return JsonUtils.buildJsonObject(super.toJson()) //
					.addProperty("state", this.state) //
					.build();
		}
	}
}
