"""
Machine configuration and hardware mapping models.
"""

from datetime import datetime, timezone
from enum import Enum
from typing import Optional

from sqlalchemy import Column, DateTime, Float, Integer, String, Text, Boolean, JSON
from sqlalchemy.orm import relationship

from ..database import Base


class MachineConfiguration(Base):
    """
    Machine configuration parameters and limits.
    Based on the original Machine.csv structure.
    """
    __tablename__ = "machine_configurations"

    id = Column(Integer, primary_key=True, index=True)
    
    # Temperature limits (°C)
    tank_max_heat = Column(Float, nullable=False, default=60.0, comment="Tank maximum heating temperature (°C)")
    pump_max_heat = Column(Float, nullable=False, default=55.0, comment="Pump maximum heating temperature (°C)")
    pump_min_heat = Column(Float, nullable=False, default=25.0, comment="Pump minimum heating temperature (°C)")
    absolute_max_temp = Column(Float, nullable=False, default=80.0, comment="Absolute maximum temperature (°C)")
    absolute_min_temp = Column(Float, nullable=False, default=10.0, comment="Absolute minimum temperature (°C)")
    
    # Process delays (seconds)
    pump_delay = Column(Float, nullable=False, default=10.0, comment="Pump startup delay (seconds)")
    mixer_delay = Column(Float, nullable=False, default=5.0, comment="Mixer startup delay (seconds)")
    heating_delay = Column(Float, nullable=False, default=60.0, comment="Heating phase delay (seconds)")
    cooling_delay = Column(Float, nullable=False, default=120.0, comment="Cooling phase delay (seconds)")
    pouring_delay = Column(Float, nullable=False, default=30.0, comment="Pouring phase delay (seconds)")
    
    # PID control parameters for different zones
    pid_parameters = Column(JSON, nullable=True, comment="PID control parameters by zone")
    
    # Safety limits
    max_neutral_current = Column(Float, nullable=False, default=16.0, comment="Maximum neutral current (A)")
    max_motor1_current = Column(Float, nullable=False, default=10.0, comment="Maximum motor 1 current (A)")
    max_motor2_current = Column(Float, nullable=False, default=10.0, comment="Maximum motor 2 current (A)")
    grid_voltage_nominal = Column(Float, nullable=False, default=230.0, comment="Nominal grid voltage (V)")
    grid_frequency_nominal = Column(Float, nullable=False, default=50.0, comment="Nominal grid frequency (Hz)")
    
    # Configuration metadata
    name = Column(String(100), nullable=False, unique=True, index=True, comment="Configuration name")
    description = Column(Text, nullable=True, comment="Configuration description")
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    is_active = Column(Boolean, nullable=False, default=True, comment="Configuration is active")
    version = Column(Integer, nullable=False, default=1, comment="Configuration version")

    def __repr__(self) -> str:
        return f"<MachineConfiguration(id={self.id}, name='{self.name}', tank_max_heat={self.tank_max_heat})>"
    
    def validate_parameters(self) -> bool:
        """Validate machine configuration parameters."""
        # Temperature validations
        if self.tank_max_heat > self.absolute_max_temp:
            return False
        if self.pump_max_heat > self.absolute_max_temp:
            return False
        if self.pump_min_heat < self.absolute_min_temp:
            return False
        if self.pump_min_heat >= self.pump_max_heat:
            return False
        
        # Safety limit validations
        if self.max_neutral_current <= 0 or self.max_motor1_current <= 0 or self.max_motor2_current <= 0:
            return False
        
        # Timing validations
        if any(delay < 0 for delay in [self.pump_delay, self.mixer_delay, self.heating_delay, self.cooling_delay, self.pouring_delay]):
            return False
        
        return True
    
    @classmethod
    def from_csv_row(cls, row: dict) -> "MachineConfiguration":
        """Create MachineConfiguration from CSV row data (migration utility)."""
        return cls(
            id=int(row.get("ID", 1)),
            name=f"Config_{row.get('ID', 1)}",
            tank_max_heat=float(row.get("TankMaxHeat", 60.0)),
            pump_max_heat=float(row.get("PumbMaxHeat", 55.0)),  # Note: original CSV has typo "Pumb"
            pump_min_heat=float(row.get("PumbMinHeat", 25.0)),
            absolute_max_temp=float(row.get("AbsMaxTemp", 80.0)),
            absolute_min_temp=float(row.get("AbsMinTemp", 10.0)),
            pump_delay=float(row.get("PumbDelay", 10.0)),
            mixer_delay=float(row.get("MixerDelay", 5.0)),
            heating_delay=float(row.get("HeatingDelay", 60.0)),
            cooling_delay=float(row.get("CoolingDelay", 120.0)),
            pouring_delay=float(row.get("PouringDelay", 30.0)),
        )


class HardwareType(str, Enum):
    """Types of hardware components."""
    TEMPERATURE_SENSOR = "temperature_sensor"
    DIGITAL_INPUT = "digital_input"
    DIGITAL_OUTPUT = "digital_output"
    ANALOG_INPUT = "analog_input"
    ANALOG_OUTPUT = "analog_output"
    MOTOR_CONTROL = "motor_control"
    SAFETY_RELAY = "safety_relay"


class HardwareMapping(Base):
    """
    Hardware component to Modbus address mapping.
    Based on the original Mapping.csv structure.
    """
    __tablename__ = "hardware_mappings"

    id = Column(Integer, primary_key=True, index=True)
    
    # Hardware identification
    component_name = Column(String(50), nullable=False, unique=True, index=True, comment="Hardware component name")
    component_type = Column(String(20), nullable=False, comment="Hardware component type")
    description = Column(Text, nullable=True, comment="Component description")
    
    # Modbus addressing
    modbus_address = Column(Integer, nullable=False, comment="Modbus register address")
    bit_position = Column(Integer, nullable=True, comment="Bit position for digital I/O (0-15)")
    data_type = Column(String(20), nullable=False, default="uint16", comment="Data type (uint16, float32, bool, etc.)")
    
    # Scaling and conversion
    scale_factor = Column(Float, nullable=False, default=1.0, comment="Scaling factor for value conversion")
    offset = Column(Float, nullable=False, default=0.0, comment="Offset for value conversion")
    units = Column(String(20), nullable=True, comment="Engineering units (°C, A, V, etc.)")
    
    # Operational parameters  
    read_frequency = Column(Float, nullable=False, default=1.0, comment="Read frequency (Hz)")
    is_readable = Column(Boolean, nullable=False, default=True, comment="Component can be read")
    is_writable = Column(Boolean, nullable=False, default=False, comment="Component can be written")
    
    # Safety and limits
    min_value = Column(Float, nullable=True, comment="Minimum safe value")
    max_value = Column(Float, nullable=True, comment="Maximum safe value")
    alarm_low = Column(Float, nullable=True, comment="Low alarm threshold")
    alarm_high = Column(Float, nullable=True, comment="High alarm threshold")
    
    # Configuration metadata
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    is_active = Column(Boolean, nullable=False, default=True, comment="Mapping is active")

    def __repr__(self) -> str:
        return f"<HardwareMapping(name='{self.component_name}', address={self.modbus_address}, type='{self.component_type}')>"
    
    def convert_raw_value(self, raw_value: int) -> float:
        """Convert raw Modbus value to engineering units."""
        if self.component_type == "digital_input" or self.component_type == "digital_output":
            # Extract bit value if bit_position is specified
            if self.bit_position is not None:
                return float((raw_value >> self.bit_position) & 1)
            return float(raw_value)
        
        # Apply scaling and offset for analog values
        return (raw_value * self.scale_factor) + self.offset
    
    def convert_to_raw_value(self, engineering_value: float) -> int:
        """Convert engineering value to raw Modbus value."""
        if self.component_type == "digital_input" or self.component_type == "digital_output":
            return int(engineering_value)
        
        # Remove offset and apply inverse scaling
        raw_value = (engineering_value - self.offset) / self.scale_factor
        return int(round(raw_value))
    
    def is_within_limits(self, value: float) -> bool:
        """Check if value is within safe operating limits."""
        if self.min_value is not None and value < self.min_value:
            return False
        if self.max_value is not None and value > self.max_value:
            return False
        return True
    
    def is_alarm_condition(self, value: float) -> tuple[bool, Optional[str]]:
        """Check for alarm conditions."""
        if self.alarm_low is not None and value <= self.alarm_low:
            return True, "LOW_ALARM"
        if self.alarm_high is not None and value >= self.alarm_high:
            return True, "HIGH_ALARM"
        return False, None
    
    @classmethod
    def from_csv_row(cls, row: dict) -> "HardwareMapping":
        """Create HardwareMapping from CSV row data (migration utility)."""
        return cls(
            component_name=row.get("Name", "Unknown"),
            component_type=row.get("Type", "unknown"),
            description=row.get("Description", ""),
            modbus_address=int(row.get("Address", 0)),
            bit_position=int(row.get("Bit", 0)) if row.get("Bit") else None,
            data_type=row.get("DataType", "uint16"),
            scale_factor=float(row.get("Scale", 1.0)),
            offset=float(row.get("Offset", 0.0)),
            units=row.get("Units", ""),
            min_value=float(row.get("MinValue")) if row.get("MinValue") else None,
            max_value=float(row.get("MaxValue")) if row.get("MaxValue") else None,
            alarm_low=float(row.get("AlarmLow")) if row.get("AlarmLow") else None,
            alarm_high=float(row.get("AlarmHigh")) if row.get("AlarmHigh") else None,
        )