from __future__ import annotations

from pydantic import BaseModel, Field, model_validator

from .battery import Battery
from .constraint import Constraint
from .fault_management import FaultManagement
from .groundstation import GroundStationRegistry
from .instrument import Payload
from .observation_categories import ObservationCategories
from .recorder import OnboardRecorder
from .solar_panel import SolarPanelSet
from .spacecraft_bus import SpacecraftBus
from .visualization import VisualizationConfig


class MissionConfig(BaseModel):
    """
    Configuration class for the spacecraft and its subsystems.
    """

    name: str = "Default Config"
    spacecraft_bus: SpacecraftBus = Field(default_factory=SpacecraftBus)
    solar_panel: SolarPanelSet = Field(default_factory=SolarPanelSet)
    payload: Payload = Field(default_factory=Payload)
    battery: Battery = Field(default_factory=Battery)
    constraint: Constraint = Field(default_factory=Constraint)
    ground_stations: GroundStationRegistry = Field(
        default_factory=GroundStationRegistry
    )
    recorder: OnboardRecorder = Field(default_factory=OnboardRecorder)
    fault_management: FaultManagement = Field(default_factory=FaultManagement)
    observation_categories: ObservationCategories = (
        ObservationCategories.default_categories()
    )
    visualization: VisualizationConfig = Field(
        default_factory=VisualizationConfig, exclude=True
    )

    @model_validator(mode="after")
    def init_fault_management_defaults(self) -> MissionConfig:
        """Initialize default fault thresholds if none provided.

        Currently sets up a battery_level threshold using the battery
        max_depth_of_discharge as the minimum allowed charge level for YELLOW
        and (max_depth_of_discharge + 0.1) as RED for demonstration.
        Also sets up recorder_fill_fraction threshold using the recorder's
        yellow and red thresholds.
        Users can override via config serialization.
        """
        if self.fault_management is None:
            return self
        # Only add battery threshold if not already present
        if not any(t.name == "battery_level" for t in self.fault_management.thresholds):
            # Yellow alert at minimum allowed charge level (1.0 - max_depth_of_discharge)
            yellow = 1.0 - self.battery.max_depth_of_discharge
            # Red alert at 10% below the minimum allowed charge level
            red = max(yellow - 0.1, 0.0)  # Ensure non-negative
            self.fault_management.add_threshold(
                name="battery_level", yellow=yellow, red=red, direction="below"
            )
        # Add recorder threshold if not already present
        if not any(
            t.name == "recorder_fill_fraction" for t in self.fault_management.thresholds
        ):
            # Use recorder's built-in thresholds
            self.fault_management.add_threshold(
                name="recorder_fill_fraction",
                yellow=self.recorder.yellow_threshold,
                red=self.recorder.red_threshold,
                direction="above",
            )
        return self

    def data_generated(self, duration_seconds: float) -> float:
        """Calculate total data generated by spacecraft bus and payload over a duration.

        Args:
            duration_seconds: Duration of data generation in seconds.

        Returns:
            float: Total amount of data generated in Gigabits.
        """
        bus_data = self.spacecraft_bus.data_generated(duration_seconds)
        payload_data = self.payload.data_generated(duration_seconds)
        return bus_data + payload_data

    @classmethod
    def from_json_file(cls, filepath: str) -> MissionConfig:
        """Load configuration from a JSON file."""
        with open(filepath) as f:
            return cls.model_validate_json(f.read())

    def to_json_file(self, filepath: str) -> None:
        """Save configuration to a JSON file."""
        with open(filepath, "w") as f:
            f.write(self.model_dump_json(indent=4))
