from unittest.mock import Mock

import numpy as np
import pytest

from akkudoktoreos.config.configabc import TimeWindow, TimeWindowSequence
from akkudoktoreos.devices.genetic.battery import Battery
from akkudoktoreos.devices.genetic.homeappliance import HomeAppliance
from akkudoktoreos.devices.genetic.inverter import Inverter
from akkudoktoreos.optimization.genetic.genetic import GeneticSimulation
from akkudoktoreos.optimization.genetic.geneticdevices import (
    ElectricVehicleParameters,
    HomeApplianceParameters,
    InverterParameters,
    SolarPanelBatteryParameters,
)
from akkudoktoreos.optimization.genetic.geneticparams import (
    GeneticEnergyManagementParameters,
    GeneticOptimizationParameters,
)
from akkudoktoreos.optimization.genetic.geneticsolution import GeneticSimulationResult
from akkudoktoreos.utils.datetimeutil import to_duration, to_time

start_hour = 1


# Example initialization of necessary components
@pytest.fixture
def genetic_simulation(config_eos) -> GeneticSimulation:
    """Fixture to create an EnergyManagement instance with given test parameters."""
    # Assure configuration holds the correct values
    config_eos.merge_settings_from_dict(
        {"prediction": {"hours": 48}, "optimization": {"hours": 24}}
    )
    assert config_eos.prediction.hours == 48
    assert config_eos.optimization.horizon_hours == 24

    # Initialize the battery and the inverter
    akku = Battery(
        SolarPanelBatteryParameters(
            device_id="battery1",
            capacity_wh=5000,
            initial_soc_percentage=80,
            min_soc_percentage=10,
        ),
        prediction_hours=config_eos.prediction.hours,
    )
    akku.reset()

    inverter = Inverter(
        InverterParameters(
            device_id="inverter1", max_power_wh=10000, battery_id=akku.parameters.device_id
        ),
        battery=akku,
    )

    # Flexible consumer (fixed start at slot 2 for this deterministic test)
    home_appliance = HomeAppliance(
        HomeApplianceParameters(
            device_id="dishwasher1",
            consumption_wh=2000,
            duration_h=2,
            time_windows=None,
        ),
        optimization_hours=config_eos.optimization.horizon_hours,
        prediction_hours=config_eos.prediction.hours,
    )
    home_appliance.build_load_curve([2])

    # Example initialization of electric car battery
    eauto = Battery(
        ElectricVehicleParameters(
            device_id="ev1", capacity_wh=26400, initial_soc_percentage=10, min_soc_percentage=10
        ),
        prediction_hours=config_eos.prediction.hours,
    )
    eauto.set_charge_per_hour(np.full(config_eos.prediction.hours, 1))

    # Parameters based on previous example data
    pv_prognose_wh = [
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        8.05,
        352.91,
        728.51,
        930.28,
        1043.25,
        1106.74,
        1161.69,
        6018.82,
        5519.07,
        3969.88,
        3017.96,
        1943.07,
        1007.17,
        319.67,
        7.88,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        5.04,
        335.59,
        705.32,
        1121.12,
        1604.79,
        2157.38,
        1433.25,
        5718.49,
        4553.96,
        3027.55,
        2574.46,
        1720.4,
        963.4,
        383.3,
        0,
        0,
        0,
    ]

    strompreis_euro_pro_wh = [
        0.0003384,
        0.0003318,
        0.0003284,
        0.0003283,
        0.0003289,
        0.0003334,
        0.0003290,
        0.0003302,
        0.0003042,
        0.0002430,
        0.0002280,
        0.0002212,
        0.0002093,
        0.0001879,
        0.0001838,
        0.0002004,
        0.0002198,
        0.0002270,
        0.0002997,
        0.0003195,
        0.0003081,
        0.0002969,
        0.0002921,
        0.0002780,
        0.0003384,
        0.0003318,
        0.0003284,
        0.0003283,
        0.0003289,
        0.0003334,
        0.0003290,
        0.0003302,
        0.0003042,
        0.0002430,
        0.0002280,
        0.0002212,
        0.0002093,
        0.0001879,
        0.0001838,
        0.0002004,
        0.0002198,
        0.0002270,
        0.0002997,
        0.0003195,
        0.0003081,
        0.0002969,
        0.0002921,
        0.0002780,
    ]

    einspeiseverguetung_euro_pro_wh = 0.00007
    preis_euro_pro_wh_akku = 0.0001

    gesamtlast = [
        676.71,
        876.19,
        527.13,
        468.88,
        531.38,
        517.95,
        483.15,
        472.28,
        1011.68,
        995.00,
        1053.07,
        1063.91,
        1320.56,
        1132.03,
        1163.67,
        1176.82,
        1216.22,
        1103.78,
        1129.12,
        1178.71,
        1050.98,
        988.56,
        912.38,
        704.61,
        516.37,
        868.05,
        694.34,
        608.79,
        556.31,
        488.89,
        506.91,
        804.89,
        1141.98,
        1056.97,
        992.46,
        1155.99,
        827.01,
        1257.98,
        1232.67,
        871.26,
        860.88,
        1158.03,
        1222.72,
        1221.04,
        949.99,
        987.01,
        733.99,
        592.97,
    ]

    # Initialize the energy management system with the respective parameters
    simulation = GeneticSimulation()
    simulation.prepare(
        GeneticEnergyManagementParameters(
            pv_prognose_wh=pv_prognose_wh,
            strompreis_euro_pro_wh=strompreis_euro_pro_wh,
            einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
            preis_euro_pro_wh_akku=preis_euro_pro_wh_akku,
            gesamtlast=gesamtlast,
        ),
        optimization_hours=config_eos.optimization.horizon_hours,
        prediction_hours=config_eos.prediction.hours,
        inverter=inverter,
        ev=eauto,
        home_appliances=[home_appliance],
    )

    # Init for test
    assert simulation.ac_charge_hours is not None
    assert simulation.dc_charge_hours is not None
    assert simulation.bat_discharge_hours is not None
    assert simulation.bat_grid_export_hours is not None
    assert simulation.ev_charge_hours is not None
    simulation.ac_charge_hours[start_hour] = 1.0
    simulation.dc_charge_hours[start_hour] = 1.0
    simulation.bat_discharge_hours[start_hour] = 1.0
    simulation.ev_charge_hours[start_hour] = 1.0

    return simulation


def test_simulation(genetic_simulation):
    """Test the EnergyManagement simulation method."""
    simulation = genetic_simulation

    # Simulate starting from hour 1 (this value can be adjusted)

    result = simulation.simulate(start_hour=start_hour)

    # visualisiere_ergebnisse(
    #     simulation.gesamtlast,
    #     simulation.pv_prognose_wh,
    #     simulation.strompreis_euro_pro_wh,
    #     result,
    #     simulation.akku.discharge_array+simulation.akku.charge_array,
    #     None,
    #     simulation.pv_prognose_wh,
    #     start_hour,
    #     48,
    #     np.full(48, 0.0),
    #     filename="visualization_results.pdf",
    #     extra_data=None,
    # )

    # Assertions to validate results
    assert result is not None, "Result should not be None"
    assert isinstance(result, dict), "Result should be a dictionary"
    assert "Last_Wh_pro_Stunde" in result, "Result should contain 'Last_Wh_pro_Stunde'"

    """
    Check the result of the simulation based on expected values.
    """
    # Example result returned from the simulation (used for assertions)
    assert result is not None, "Result should not be None."

    # Check that the result is a dictionary
    assert isinstance(result, dict), "Result should be a dictionary."
    assert GeneticSimulationResult(**result) is not None

    # Check the length of the main arrays
    assert (
        len(result["Last_Wh_pro_Stunde"]) == 47
    ), "The length of 'Last_Wh_pro_Stunde' should be 48."
    assert (
        len(result["Netzeinspeisung_Wh_pro_Stunde"]) == 47
    ), "The length of 'Netzeinspeisung_Wh_pro_Stunde' should be 48."
    assert (
        len(result["Netzbezug_Wh_pro_Stunde"]) == 47
    ), "The length of 'Netzbezug_Wh_pro_Stunde' should be 48."
    assert (
        len(result["Kosten_Euro_pro_Stunde"]) == 47
    ), "The length of 'Kosten_Euro_pro_Stunde' should be 48."
    assert (
        len(result["akku_soc_pro_stunde"]) == 47
    ), "The length of 'akku_soc_pro_stunde' should be 48."

    # Verify specific values in the 'Last_Wh_pro_Stunde' array
    assert (
        result["Last_Wh_pro_Stunde"][1] == 1527.13
    ), "The value at index 1 of 'Last_Wh_pro_Stunde' should be 1527.13."
    assert (
        result["Last_Wh_pro_Stunde"][2] == 1468.88
    ), "The value at index 2 of 'Last_Wh_pro_Stunde' should be 1468.88."
    assert (
        result["Last_Wh_pro_Stunde"][12] == 1132.03
    ), "The value at index 12 of 'Last_Wh_pro_Stunde' should be 1132.03."

    # Verify that the value at index 0 is 'None'
    # Check that 'Netzeinspeisung_Wh_pro_Stunde' and 'Netzbezug_Wh_pro_Stunde' are consistent
    assert (
        result["Netzbezug_Wh_pro_Stunde"][1] == 1527.13
    ), "The value at index 1 of 'Netzbezug_Wh_pro_Stunde' should be 1527.13."

    # Verify the total balance
    assert (
        abs(result["Gesamtbilanz_Euro"] - 7.025236588371921) < 1e-5
    ), "Total balance should reflect the shared per-slot battery power limit."

    # Check total revenue and total costs
    assert (
        abs(result["Gesamteinnahmen_Euro"] - 2.3247787887715) < 1e-5
    ), "Total revenue should respect the shared per-slot battery power limit."
    assert (
        abs(result["Gesamtkosten_Euro"] - 9.350015377143421) < 1e-5
    ), "Total costs should respect the shared per-slot battery power limit."

    # Check the losses
    assert abs(result["Gesamt_Verluste"] - 1620.0) < 1e-5, "Total losses should be 1620.0 ."

    # Check the values in 'akku_soc_pro_stunde'
    assert (
        result["akku_soc_pro_stunde"][-1] == 98.0
    ), "The value at index -1 of 'akku_soc_pro_stunde' should be 98.0."
    assert (
        result["akku_soc_pro_stunde"][1] == 98.0
    ), "The value at index 1 of 'akku_soc_pro_stunde' should be 98.0."

    # Check home appliances
    assert (
        sum(simulation.home_appliances[0].get_load_curve()) == 2000
    ), "The sum of 'simulation.home_appliances[0].get_load_curve()' should be 2000."

    assert (
        np.nansum(
            np.where(
                result["Home_appliance_wh_per_hour"] is None,
                np.nan,
                np.array(result["Home_appliance_wh_per_hour"]),
            )
        )
        == 2000
    ), "The sum of 'Home_appliance_wh_per_hour' should be 2000."

    print("All tests passed successfully.")


def test_direct_marketing_curtails_negative_feed_in(config_eos, monkeypatch):
    config_eos.merge_settings_from_dict(
        {"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
    )

    inverter = Inverter(InverterParameters(device_id="inverter1", max_power_wh=1000.0))
    monkeypatch.setattr(
        inverter.self_consumption_predictor,
        "calculate_expected_direct_consumption",
        Mock(side_effect=min),
    )

    simulation = GeneticSimulation()
    simulation.prepare(
        GeneticEnergyManagementParameters(
            pv_prognose_wh=[500.0, 500.0],
            strompreis_euro_pro_wh=[-0.0001, -0.0001],
            einspeiseverguetung_euro_pro_wh=[-0.0001, -0.0001],
            preis_euro_pro_wh_akku=0.0,
            gesamtlast=[0.0, 0.0],
        ),
        optimization_hours=config_eos.optimization.horizon_hours,
        prediction_hours=config_eos.prediction.hours,
        inverter=inverter,
        direct_marketing_enabled=True,
    )

    result = simulation.simulate(start_hour=0)

    assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == 0.0
    assert result["Einnahmen_Euro_pro_Stunde"][0] == 0.0
    assert result["Verluste_Pro_Stunde"][0] == pytest.approx(500.0)


def _direct_marketing_battery_export_simulation(
    config_eos,
    levelized_cost_of_storage_kwh: float = 0.0,
    dc_to_ac_efficiency: float = 1.0,
) -> GeneticSimulation:
    config_eos.merge_settings_from_dict(
        {"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
    )

    battery = Battery(
        SolarPanelBatteryParameters(
            device_id="battery1",
            capacity_wh=1000,
            initial_soc_percentage=100,
            min_soc_percentage=0,
            charging_efficiency=1.0,
            discharging_efficiency=1.0,
            levelized_cost_of_storage_kwh=levelized_cost_of_storage_kwh,
            max_charge_power_w=500,
        ),
        prediction_hours=config_eos.prediction.hours,
    )
    inverter = Inverter(
        InverterParameters(
            device_id="inverter1",
            max_power_wh=500.0,
            battery_id=battery.parameters.device_id,
            dc_to_ac_efficiency=dc_to_ac_efficiency,
        ),
        battery=battery,
    )

    simulation = GeneticSimulation()
    simulation.prepare(
        GeneticEnergyManagementParameters(
            pv_prognose_wh=[0.0, 0.0],
            strompreis_euro_pro_wh=[0.0, 0.0],
            einspeiseverguetung_euro_pro_wh=[0.0002, 0.0002],
            preis_euro_pro_wh_akku=0.0,
            gesamtlast=[0.0, 0.0],
        ),
        optimization_hours=config_eos.optimization.horizon_hours,
        prediction_hours=config_eos.prediction.hours,
        inverter=inverter,
        direct_marketing_enabled=True,
    )
    return simulation


def test_direct_marketing_discharge_allowed_does_not_export_battery(config_eos):
    simulation = _direct_marketing_battery_export_simulation(config_eos)
    assert simulation.bat_discharge_hours is not None
    simulation.bat_discharge_hours[0] = 1

    result = simulation.simulate(start_hour=0)

    assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == 0.0
    assert simulation.battery is not None
    assert simulation.battery.current_soc_percentage() == 100.0


def test_direct_marketing_battery_grid_export_uses_separate_signal(config_eos):
    simulation = _direct_marketing_battery_export_simulation(config_eos)
    assert simulation.bat_grid_export_hours is not None
    simulation.bat_grid_export_hours[0] = 1

    result = simulation.simulate(start_hour=0)

    assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == pytest.approx(500.0)
    assert result["Einnahmen_Euro_pro_Stunde"][0] == pytest.approx(0.1)
    assert simulation.battery is not None
    assert simulation.battery.current_soc_percentage() == 50.0


def test_battery_lcos_is_charged_once_on_delivered_energy(config_eos):
    simulation = _direct_marketing_battery_export_simulation(
        config_eos,
        levelized_cost_of_storage_kwh=0.12,
        dc_to_ac_efficiency=0.8,
    )
    assert simulation.bat_grid_export_hours is not None
    simulation.bat_grid_export_hours[0] = 1

    result = simulation.simulate(start_hour=0)

    # The battery delivers 500 Wh DC, so LCOS is 0.5 kWh * 0.12 EUR/kWh
    # = 0.06 EUR exactly once. After the 80% inverter, 400 Wh AC reaches
    # the grid and earns 400 Wh * 0.0002 EUR/Wh = 0.08 EUR.
    assert result["Kosten_Euro_pro_Stunde"][0] == pytest.approx(0.06)
    assert result["Gesamtkosten_Euro"] == pytest.approx(0.06)
    assert result["Einnahmen_Euro_pro_Stunde"][0] == pytest.approx(0.08)
    assert result["Gesamtbilanz_Euro"] == pytest.approx(-0.02)
