package io.openems.edge.io.shelly.common;

import static io.openems.common.utils.JsonUtils.getAsJsonObject;
import static io.openems.common.utils.JsonUtils.getAsOptionalJsonObject;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import com.google.gson.JsonObject;

import io.openems.common.bridge.http.api.BridgeHttp;
import io.openems.common.exceptions.OpenemsError;
import io.openems.edge.common.channel.BooleanWriteChannel;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.WriteChannel;
import io.openems.edge.common.component.OpenemsComponent;

public class Utils {

	private Utils() {
	}

	/**
	 * Reads updates available status from shelly status response.
	 * 
	 * @param json Status response from shelly device
	 * @return true if update is available
	 * @throws OpenemsError.OpenemsNamedException thrown if json is invalid
	 */
	public static boolean readUpdatesAvailableStatusFromStatusResponse(JsonObject json)
			throws OpenemsError.OpenemsNamedException {
		var sysInfo = getAsJsonObject(json, "sys");
		var update = getAsJsonObject(sysInfo, "available_updates");
		var stable = getAsOptionalJsonObject(update, "stable");
		return stable.isPresent();
	}

	/**
	 * Generates a standard Debug-Log string for Shellys with one relay and power
	 * meter.
	 * 
	 * @param relayChannels      the Relay-Channel
	 * @param activePowerChannel the ActivePower-Channel
	 * @return suitable for {@link OpenemsComponent#debugLog()}
	 */
	public static String generateDebugLog(Channel<Boolean>[] relayChannels, Channel<Integer> activePowerChannel) {
		var b = new StringBuilder();
		for (int i = 0; i < relayChannels.length; i++) {
			var relayChannel = relayChannels[i];
			relayChannel.value().asOptional().ifPresentOrElse(v -> b.append(v ? "x" : "-"), () -> b.append("?"));
			if (i < relayChannels.length - 1) {
				b.append("|");
			}
		}
		b.append("|").append(activePowerChannel.value().asString());
		return b.toString();
	}

	/**
	 * Generates a standard Debug-Log string for Shellys with multiple relays.
	 * 
	 * @param digitalOutputChannels the Relay-Channels
	 * @return suitable for {@link OpenemsComponent#debugLog()}
	 */
	public static String generateDebugLog(BooleanWriteChannel[] digitalOutputChannels) {
		// TODO share code with AbstractKmtronicRelay.debugLog()
		return stream(digitalOutputChannels) //
				.map(c -> c.value().asOptional().map(v -> v //
						? "x" //
						: "-") //
						.orElse("?")) //
				.collect(joining(" "));
	}

	/**
	 * Executes a write command to a specified relay channel by constructing and
	 * sending an HTTP request based on the channel's current and intended state.
	 * This method compares the current state with the desired state, and only
	 * proceeds with the HTTP request if they differ, ensuring no unnecessary
	 * commands are sent. The method returns a CompletableFuture that completes when
	 * the HTTP request is finished. It completes normally if the HTTP request
	 * succeeds, and exceptionally if the request fails due to errors.
	 *
	 * @param relayChannel the channel for the relay, specifying the current and
	 *                     desired states
	 * @param baseUrl      the base URL for constructing the final endpoint URL
	 * @param httpBridge   the HTTP bridge to send the request
	 * @param index        the index of the DigitalChannel to write to (used for the
	 *                     URL)
	 * @return CompletableFuture{@code <Void>} that completes when the HTTP
	 *         operation completes. Completes exceptionally if there is an error in
	 *         the HTTP request.
	 */
	public static CompletableFuture<Void> executeWrite(WriteChannel<Boolean> relayChannel, String baseUrl,
			BridgeHttp httpBridge, Integer index) {
		if (baseUrl == null) {
			return CompletableFuture.completedFuture(null);
		}
		Boolean readValue = relayChannel.value().get();
		Optional<Boolean> writeValue = relayChannel.getNextWriteValueAndReset();

		if (writeValue.isEmpty()) {
			// No action needed
			return CompletableFuture.completedFuture(null);
		}
		if (Objects.equals(readValue, writeValue.get())) {
			// No change in state
			return CompletableFuture.completedFuture(null);
		}

		final String url = baseUrl + "/rpc/Switch.Set?id=" + index + "&on=" + (writeValue.get() ? "true" : "false");
		return httpBridge.get(url).thenAccept(stringHttpResponse -> {
			// map to void
		});
	}

}