import numpy as np

from akkudoktoreos.config.configabc import TimeWindow, TimeWindowSequence
from akkudoktoreos.optimization.genetic0.genetic0devices import (
    Genetic0HomeApplianceParameters,
)
from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration, to_time


class Genetic0HomeAppliance:
    def __init__(
        self,
        parameters: Genetic0HomeApplianceParameters,
        optimization_hours: int,
        prediction_hours: int,
    ):
        self.parameters: Genetic0HomeApplianceParameters = parameters
        self.prediction_hours = prediction_hours
        self._setup()

    def _setup(self) -> None:
        """Sets up the home appliance parameters based provided parameters."""
        self.load_curve = np.zeros(self.prediction_hours)  # Initialize the load curve with zeros
        self.duration_h = self.parameters.duration_h
        self.consumption_wh = self.parameters.consumption_wh
        # setup possible start times
        if self.parameters.time_windows is None:
            self.parameters.time_windows = TimeWindowSequence(
                windows=[
                    TimeWindow(
                        start_time=to_time("00:00"),
                        duration=to_duration(f"{self.prediction_hours} hours"),
                    ),
                ]
            )
        start_datetime = to_datetime().set(hour=0, minute=0, second=0)
        duration = to_duration(f"{self.duration_h} hours")
        self.start_allowed: list[bool] = []
        for hour in range(0, self.prediction_hours):
            self.start_allowed.append(
                self.parameters.time_windows.contains(
                    start_datetime.add(hours=hour), duration=duration
                )
            )
        start_earliest = self.parameters.time_windows.earliest_start_time(duration, start_datetime)
        if start_earliest:
            self.start_earliest = start_earliest.hour
        else:
            self.start_earliest = 0
        start_latest = self.parameters.time_windows.latest_start_time(duration, start_datetime)
        if start_latest:
            self.start_latest = start_latest.hour
        else:
            self.start_latest = 23

    def set_starting_time(self, start_hour: int, global_start_hour: int = 0) -> int:
        """Sets the start time of the device and generates the corresponding load curve.

        :param start_hour: The hour at which the device should start.
        """
        if not self.start_allowed[start_hour]:
            # It is not allowed (by the time windows) to start the application at this time
            if global_start_hour <= self.start_latest:
                # There is a time window left to start the appliance. Use it
                start_hour = self.start_latest
            else:
                # There is no time window left to run the application
                # Set the start into tomorrow
                start_hour = self.start_earliest + 24

        self.reset_load_curve()

        # Calculate power per hour based on total consumption and duration
        power_per_hour = self.consumption_wh / self.duration_h  # Convert to watt-hours

        # Set the power for the duration of use in the load curve array
        if start_hour < len(self.load_curve):
            end_hour = min(start_hour + self.duration_h, self.prediction_hours)
            self.load_curve[start_hour:end_hour] = power_per_hour

        return start_hour

    def reset_load_curve(self) -> None:
        """Resets the load curve."""
        self.load_curve = np.zeros(self.prediction_hours)

    def get_load_curve(self) -> np.ndarray:
        """Returns the current load curve."""
        return self.load_curve

    def get_load_for_hour(self, hour: int) -> float:
        """Returns the load for a specific hour.

        :param hour: The hour for which the load is queried.
        :return: The load in watts for the specified hour.
        """
        if hour < 0 or hour >= self.prediction_hours:
            raise ValueError(
                f"The specified hour {hour} is outside the available time frame {self.prediction_hours}."
            )

        return self.load_curve[hour]
