package io.openems.edge.core.appmanager.dependency.aggregatetask;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ServiceScope;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonPrimitive;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.session.Language;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.common.user.User;
import io.openems.edge.core.appmanager.AppConfiguration;
import io.openems.edge.core.appmanager.ComponentUtil;
import io.openems.edge.core.appmanager.OpenemsAppInstance;
import io.openems.edge.core.appmanager.TranslationUtil;
import io.openems.edge.core.appmanager.dependency.AppManagerAppHelperImpl;

@Component(//
		service = { //
				AggregateTask.class, //
				SchedulerAggregateTask.class, //
				SchedulerAggregateTaskImpl.class //
		}, //
		scope = ServiceScope.SINGLETON //
)
public class SchedulerAggregateTaskImpl implements SchedulerAggregateTask {

	private record SchedulerExecutionConfiguration(//
			List<String> insertOrder //
	) implements AggregateTask.AggregateTaskExecutionConfiguration {

		private SchedulerExecutionConfiguration {
			Objects.requireNonNull(insertOrder);
		}

		@Override
		public String identifier() {
			return "Scheduler";
		}

		@Override
		public JsonElement toJson() {
			if (this.insertOrder.isEmpty()) {
				return JsonNull.INSTANCE;
			}
			return JsonUtils.buildJsonObject() //
					.add("insertOrder", this.insertOrder.stream() //
							.map(JsonPrimitive::new) //
							.collect(JsonUtils.toJsonArray()))
					.build();
		}

	}

	private final ComponentAggregateTask aggregateTask;
	private final ComponentUtil componentUtil;

	private List<String> order;
	private List<String> removeIds;

	@Activate
	public SchedulerAggregateTaskImpl(//
			@Reference ComponentAggregateTask aggregateTask, //
			@Reference ComponentUtil componentUtil //
	) {
		this.aggregateTask = aggregateTask;
		this.componentUtil = componentUtil;
	}

	@Override
	public void reset() {
		this.order = new LinkedList<>();
		this.removeIds = new LinkedList<>();
	}

	@Override
	public void aggregate(//
			final SchedulerConfiguration currentConfiguration, //
			final SchedulerConfiguration lastConfiguration //
	) {
		if (currentConfiguration != null) {
			this.order = this.componentUtil.insertSchedulerOrder(this.order, currentConfiguration.componentOrder());
		}
		if (lastConfiguration != null) {
			var schedulerIdDiff = new ArrayList<>(lastConfiguration.componentOrder());
			if (currentConfiguration != null) {
				schedulerIdDiff.removeAll(currentConfiguration.componentOrder());
			}
			this.removeIds.addAll(schedulerIdDiff);
		}
	}

	@Override
	public void create(User user, List<AppConfiguration> otherAppConfigurations) throws OpenemsNamedException {
		if (!this.anyCreateChanges()) {
			this.delete(user, otherAppConfigurations);
			return;
		}
		this.order = this.componentUtil.insertSchedulerOrder(this.componentUtil.getSchedulerIds(), this.order);
		this.componentUtil.updateSchedulerFromComponentConfig(user, this.order,
				this.aggregateTask.getCreatedComponents());

		this.delete(user, otherAppConfigurations);
	}

	@Override
	public void delete(User user, List<AppConfiguration> otherAppConfigurations) throws OpenemsNamedException {
		if (!this.anyDeleteChanges()) {
			return;
		}
		var otherIds = AppConfiguration
				.flatMap(otherAppConfigurations, SchedulerAggregateTask.class, SchedulerConfiguration::componentOrder)
				.toList();
		this.removeIds.removeAll(otherIds);
		this.removeIds.addAll(this.aggregateTask.getDeletedComponents());

		this.componentUtil.removeIdsInSchedulerIfExisting(user, this.removeIds);
	}

	@Override
	public AggregateTaskExecutionConfiguration getExecutionConfiguration() {
		return new SchedulerExecutionConfiguration(this.order);
	}

	@Override
	public String getGeneralFailMessage(Language l) {
		final var bundle = AppManagerAppHelperImpl.getTranslationBundle(l);
		return TranslationUtil.getTranslation(bundle, "canNotUpdateScheduler");
	}

	@Override
	public AggregateTaskExecuteConstraints getExecuteConstraints() {
		return new AggregateTaskExecuteConstraints(Set.of(//
				// Needs to run after the AggregateTask.ComponentAggregateTask to also remove
				// ids in the scheduler of components which got deleted
				ComponentAggregateTask.class //
		));
	}

	@Override
	public void validate(//
			final List<String> errors, //
			final AppConfiguration appConfiguration, //
			final SchedulerConfiguration configuration, //
			final Map<OpenemsAppInstance, AppConfiguration> allConfigurations //
	) {
		if (configuration.componentOrder().isEmpty()) {
			return;
		}

		// Prepare Queue
		var controllers = new LinkedList<>(this.componentUtil.removeIdsWhichNotExistFromComponentConfig(
				configuration.componentOrder(), appConfiguration.getComponents()));

		if (controllers.isEmpty()) {
			return;
		}

		List<String> schedulerIds;
		try {
			schedulerIds = this.componentUtil.getSchedulerIds();
		} catch (OpenemsNamedException e) {
			errors.add(e.getMessage());
			return;
		}

		var nextControllerId = controllers.poll();

		// Remove found Controllers from Queue in order
		for (var controllerId : schedulerIds) {
			if (controllerId.equals(nextControllerId)) {
				nextControllerId = controllers.poll();
			}
		}
		if (nextControllerId != null) {
			errors.add("Controller [" + nextControllerId + "] is not/wrongly configured in Scheduler");
		}
	}

	private boolean anyCreateChanges() {
		return !this.order.isEmpty();
	}

	private boolean anyDeleteChanges() {
		return !this.removeIds.isEmpty();
	}

}
