package io.openems.edge.energy;

import static io.openems.common.jscalendar.JSCalendar.RecurrenceFrequency.DAILY;
import static io.openems.common.test.TestUtils.createDummyClock;
import static io.openems.common.utils.DateUtils.roundDownToQuarter;
import static io.openems.common.utils.ReflectionUtils.getValueViaReflection;
import static io.openems.edge.energy.EnergySchedulerTestUtils.dummyEssEmergencyCapacityReserve;
import static io.openems.edge.energy.EnergySchedulerTestUtils.dummyEssFixActivePower;
import static io.openems.edge.energy.EnergySchedulerTestUtils.dummyEssLimitTotalDischarge;
import static io.openems.edge.energy.EnergySchedulerTestUtils.dummyEssTimeOfUseTariff;
import static io.openems.edge.energy.EnergySchedulerTestUtils.dummyEvseCluster;
import static io.openems.edge.energy.api.EnergyConstants.SUM_PRODUCTION;
import static io.openems.edge.energy.api.EnergyConstants.SUM_UNMANAGED_CONSUMPTION;
import static io.openems.edge.energy.api.Environment.PRODUCTION;
import static io.openems.edge.energy.api.LogVerbosity.TRACE;
import static io.openems.edge.energy.api.Version.V2_ENERGY_SCHEDULABLE;
import static io.openems.edge.energy.optimizer.TestData.CONSUMPTION_PREDICTION_QUARTERLY;
import static io.openems.edge.energy.optimizer.TestData.HOURLY_PRICES_SUMMER;
import static io.openems.edge.energy.optimizer.TestData.PRODUCTION_PREDICTION_QUARTERLY;
import static java.time.temporal.ChronoUnit.DAYS;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;

import org.junit.jupiter.api.Test;

import com.google.common.collect.ImmutableMap;

import io.openems.common.jscalendar.JSCalendar;
import io.openems.common.test.DummyConfigurationAdmin;
import io.openems.edge.common.meta.GridBuySoftLimit;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyMeta;
import io.openems.edge.controller.ess.fixactivepower.enums.Mode;
import io.openems.edge.controller.ess.timeofusetariff.ControlMode;
import io.openems.edge.controller.evse.cluster.DistributionStrategy;
import io.openems.edge.controller.evse.single.CombinedAbilities;
import io.openems.edge.controller.evse.single.Params;
import io.openems.edge.controller.evse.single.Types.Hysteresis;
import io.openems.edge.energy.optimizer.Optimizer;
import io.openems.edge.evse.api.chargepoint.Profile.ChargePointAbilities;
import io.openems.edge.evse.api.electricvehicle.Profile.ElectricVehicleAbilities;
import io.openems.edge.predictor.api.prediction.Prediction;
import io.openems.edge.predictor.api.test.DummyPredictor;
import io.openems.edge.predictor.api.test.DummyPredictorManager;
import io.openems.edge.scheduler.api.test.DummyScheduler;
import io.openems.edge.timedata.api.Timedata;
import io.openems.edge.timedata.test.DummyTimedata;
import io.openems.edge.timeofusetariff.test.DummyTariffManager;
import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider;

public class EnergySchedulerImplTest {

	@Test
	void test() throws Exception {
		create(createDummyClock());
	}

	/**
	 * Creates a {@link EnergySchedulerImplTest} instance.
	 * 
	 * @param clock a {@link Clock}
	 * @return the object
	 * @throws Exception on error
	 */
	public static EnergySchedulerImpl create(Clock clock) throws Exception {
		final var now = roundDownToQuarter(Instant.now(clock));
		final var midnight = now.truncatedTo(DAYS);
		final var componentManager = new DummyComponentManager(clock);
		final var sum = new DummySum() //
				.withEssCapacity(10000) //
				.withEssSoc(50);
		final var predictor0 = new DummyPredictor("predictor0", componentManager,
				Prediction.from(sum, SUM_PRODUCTION, midnight, PRODUCTION_PREDICTION_QUARTERLY), SUM_PRODUCTION);
		final var predictor1 = new DummyPredictor("predictor1", componentManager,
				Prediction.from(sum, SUM_UNMANAGED_CONSUMPTION, midnight, CONSUMPTION_PREDICTION_QUARTERLY),
				SUM_UNMANAGED_CONSUMPTION);
		final var timeOfUseTariff = DummyTimeOfUseTariffProvider.fromHourlyPrices(clock, HOURLY_PRICES_SUMMER);
		final var tariffManager = new DummyTariffManager() //
				.withTariffGridBuyProvider(timeOfUseTariff);

		final var sut = new EnergySchedulerImpl();
		new ComponentTest(sut) //
				.addReference("cm", new DummyConfigurationAdmin()) //
				.addReference("componentManager", componentManager) //
				.addReference("meta", new DummyMeta()//
						.withGridBuySoftLimit(JSCalendar.Tasks.<GridBuySoftLimit>create(clock)//
								.add(t -> t//
										.setStart("08:00") //
										.setDuration(Duration.ofHours(12)) //
										.addRecurrenceRule(b -> b //
												.setFrequency(DAILY)) //
										.setPayload(new GridBuySoftLimit(2000))) //
								.add(t -> t//
										.setPayload(new GridBuySoftLimit(6000))) //
								.build())) //
				.addReference("predictorManager", new DummyPredictorManager(predictor0, predictor1)) //
				.addReference("timedata", new DummyTimedata("timedata0")) //
				.addReference("timeOfUseTariff", timeOfUseTariff) //
				.addReference("tariffManager", tariffManager) //
				.addReference("scheduler", new DummyScheduler("scheduler0")) //
				.addReference("addSchedulable", dummyEssEmergencyCapacityReserve("ctrlEmergencyCapacityReserve0", 20)) //
				.addReference("addSchedulable", dummyEssLimitTotalDischarge("ctrlLimitTotalDischarge0", 0)) //
				.addReference("addSchedulable",
						dummyEssFixActivePower("ctrlFixActivePower0", Mode.MANUAL_ON, -1000, null)) //
				.addReference("addSchedulable",
						dummyEssTimeOfUseTariff("ctrlEssTimeOfUseTariff0", ControlMode.CHARGE_CONSUMPTION.modes, null,
								null)) //
				.addReference("addSchedulable",
						dummyEvseCluster("ctrlEvseCluster0", DistributionStrategy.EQUAL_POWER,
								ImmutableMap.<String, Params>builder() //
										.put("ctrlEvseSingle0", new Params("ctrlEvseSingle0", "evse0",
												io.openems.edge.controller.evse.single.Mode.FORCE, null, 0, null,
												new io.openems.edge.controller.evse.single.Types.History(),
												Hysteresis.INACTIVE, null, false, //
												CombinedAbilities.createFrom(//
														ChargePointAbilities.create().build(),
														ElectricVehicleAbilities.create().build()) //
														.build(),
												JSCalendar.Tasks.empty()))
										.put("ctrlEvseSingle1", new Params("ctrlEvseSingle1", "evse1",
												io.openems.edge.controller.evse.single.Mode.FORCE, null, 0, null,
												new io.openems.edge.controller.evse.single.Types.History(),
												Hysteresis.INACTIVE, null, false, //
												CombinedAbilities.createFrom(//
														ChargePointAbilities.create().build(),
														ElectricVehicleAbilities.create().build()) //
														.build(),
												JSCalendar.Tasks.empty()))
										.build())) //
				.addReference("sum", sum) //
				.activate(MyConfig.create() //
						.setId("_energy") //
						.setEnabled(false) //
						.setLogVerbosity(TRACE) //
						.setVersion(V2_ENERGY_SCHEDULABLE) //
						.setEnvironment(PRODUCTION) //
						.build()) //
				.next(new TestCase());
		return sut;
	}

	/**
	 * Gets the {@link Optimizer} via Java Reflection.
	 * 
	 * @param energyScheduler the {@link EnergySchedulerImpl}
	 * @return the object
	 * @throws Exception on error
	 */
	public static Optimizer getOptimizer(EnergySchedulerImpl energyScheduler) throws Exception {
		return getValueViaReflection(energyScheduler, "optimizer");
	}

	/**
	 * Gets the {@link Timedata} via Java Reflection.
	 * 
	 * @param energyScheduler the {@link EnergySchedulerImpl}
	 * @return the object
	 * @throws Exception on error
	 */
	public static DummyTimedata getTimedata(EnergySchedulerImpl energyScheduler) throws Exception {
		return getValueViaReflection(energyScheduler, "timedata");
	}
}
