"""Alarm types, severities, and the ``Alarm`` runtime record.

The catalogue mirrors section 7 of the brief. Each ``AlarmType`` has a
canonical default ``AlarmSeverity`` and a hard-coded flag stating whether
that alarm, when fired, should also trigger automatic disengagement of
the autopilot (return to STANDBY).
"""

from __future__ import annotations

from datetime import UTC, datetime
from enum import StrEnum

from pydantic import BaseModel, ConfigDict, Field


class AlarmSeverity(StrEnum):
    """IEC-style four-level severity scheme."""

    EMERGENCY = "emergency"
    """Immediate danger; loudest annunciation, blocking modal on display."""

    HIGH = "high"
    """Operator must act within a few seconds."""

    LOW = "low"
    """Warning; degraded operation but no immediate danger."""

    INFO = "info"
    """Informational only; ack with one tap."""


class AlarmType(StrEnum):
    """The fixed catalogue of alarms emitted by the firmware.

    Adding an entry here is a deliberate, reviewed change — the firmware
    state machine and the display strings must be updated in lockstep.
    """

    OFF_COURSE = "off_course"
    OFF_COURSE_SEVERE = "off_course_severe"
    RUDDER_NOT_RESPONDING = "rudder_not_responding"
    HEADING_SENSOR_LOST = "heading_sensor_lost"
    ACTUATOR_OVERCURRENT = "actuator_overcurrent"
    VOLTAGE_LOW = "voltage_low"
    LIMIT_SWITCH_REACHED = "limit_switch_reached"
    WATCHDOG_TRIPPED = "watchdog_tripped"
    VMS_CRITICAL = "vms_critical"


# Canonical metadata per alarm type. Source of truth referenced by both
# the firmware (via code-generated header) and the Studio (via the GUI).
_CATALOGUE: dict[AlarmType, tuple[AlarmSeverity, bool, str]] = {
    AlarmType.OFF_COURSE:           (AlarmSeverity.LOW,       False, "Heading deviates from setpoint beyond threshold"),
    AlarmType.OFF_COURSE_SEVERE:    (AlarmSeverity.EMERGENCY, True,  "Heading deviation >30 deg for >5 s — auto-disengage"),
    AlarmType.RUDDER_NOT_RESPONDING:(AlarmSeverity.EMERGENCY, True,  "Rudder command sent but no feedback motion — auto-disengage"),
    AlarmType.HEADING_SENSOR_LOST:  (AlarmSeverity.EMERGENCY, True,  "NMEA 2000 PGN 127250 not received for >5 s — auto-disengage"),
    AlarmType.ACTUATOR_OVERCURRENT: (AlarmSeverity.HIGH,      True,  "Actuator current exceeded configured limit"),
    AlarmType.VOLTAGE_LOW:          (AlarmSeverity.HIGH,      True,  "Supply voltage below safe operating threshold"),
    AlarmType.LIMIT_SWITCH_REACHED: (AlarmSeverity.LOW,       False, "Rudder reached mechanical end-stop"),
    AlarmType.WATCHDOG_TRIPPED:     (AlarmSeverity.EMERGENCY, True,  "Firmware watchdog fired — controller reset to STANDBY"),
    AlarmType.VMS_CRITICAL:         (AlarmSeverity.EMERGENCY, True,  "VMS reported blackout or critical electrical overload"),
}


def default_severity(alarm_type: AlarmType) -> AlarmSeverity:
    """Return the canonical severity for ``alarm_type``."""
    return _CATALOGUE[alarm_type][0]


def triggers_auto_disengage(alarm_type: AlarmType) -> bool:
    """Return ``True`` iff this alarm forces the pilot into STANDBY."""
    return _CATALOGUE[alarm_type][1]


def default_message(alarm_type: AlarmType) -> str:
    """Return the canonical default human-readable description."""
    return _CATALOGUE[alarm_type][2]


class Alarm(BaseModel):
    """A single alarm event raised by the firmware at runtime.

    Persisted to the immutable audit log (brief section 14, rule #14).
    """

    model_config = ConfigDict(frozen=True, extra="forbid")

    type: AlarmType
    severity: AlarmSeverity
    message: str = Field(min_length=1, max_length=240)
    timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
    source: str = Field(
        default="firmware",
        description="Subsystem that raised the alarm (e.g. 'firmware', 'display', 'vms').",
        max_length=64,
    )
    acknowledged: bool = False
    auto_disengage_triggered: bool = False

    @classmethod
    def from_type(
        cls,
        alarm_type: AlarmType,
        *,
        message: str | None = None,
        source: str = "firmware",
    ) -> Alarm:
        """Convenience constructor using catalogue defaults."""
        return cls(
            type=alarm_type,
            severity=default_severity(alarm_type),
            message=message or default_message(alarm_type),
            source=source,
            auto_disengage_triggered=triggers_auto_disengage(alarm_type),
        )
