"""
Hardware manager for chocolate tempering machine control.
Provides high-level interface to all hardware components via Modbus.
"""

import asyncio
import logging
from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from enum import Enum

from .modbus_client import modbus_client, ModbusResponse
from ...shared.config import settings
from ...shared.database import get_sync_db
from ...shared.models.machine import HardwareMapping


logger = logging.getLogger(__name__)


class ComponentStatus(str, Enum):
    """Hardware component operational status."""
    ONLINE = "online"
    OFFLINE = "offline"
    ERROR = "error"
    MAINTENANCE = "maintenance"
    UNKNOWN = "unknown"


class MotorState(str, Enum):
    """Motor operational states."""
    STOPPED = "stopped"
    STARTING = "starting"
    RUNNING = "running"
    STOPPING = "stopping"
    ERROR = "error"


@dataclass
class TemperatureReading:
    """Temperature sensor reading with metadata."""
    value: float
    timestamp: datetime
    sensor_name: str
    units: str = "°C"
    is_valid: bool = True
    error_message: Optional[str] = None


@dataclass
class MotorStatus:
    """Motor status with control information."""
    name: str
    state: MotorState = MotorState.STOPPED
    is_enabled: bool = False
    current: Optional[float] = None
    speed_percent: Optional[float] = None
    runtime_hours: float = 0.0
    last_started: Optional[datetime] = None
    error_count: int = 0
    last_error: Optional[str] = None


@dataclass 
class SafetyStatus:
    """Safety system status."""
    emergency_stop_active: bool = False
    cover_sensor_closed: bool = True
    temperature_alarms: List[str] = field(default_factory=list)
    current_alarms: List[str] = field(default_factory=list)
    communication_errors: int = 0
    last_safety_check: Optional[datetime] = None


@dataclass
class HardwareStatus:
    """Complete hardware system status."""
    temperatures: Dict[str, TemperatureReading] = field(default_factory=dict)
    motors: Dict[str, MotorStatus] = field(default_factory=dict)
    safety: SafetyStatus = field(default_factory=SafetyStatus)
    communication_health: float = 100.0  # Percentage
    last_update: Optional[datetime] = None
    system_status: ComponentStatus = ComponentStatus.UNKNOWN


class HardwareManager:
    """
    High-level hardware manager for chocolate tempering machine.
    Manages all hardware components through Modbus communication.
    """
    
    def __init__(self):
        self.hardware_mappings: Dict[str, HardwareMapping] = {}
        self.component_cache: Dict[str, Any] = {}
        self.last_read_time: Dict[str, datetime] = {}
        self.read_intervals: Dict[str, float] = {}
        self.error_counts: Dict[str, int] = {}
        
        # Hardware status
        self.current_status = HardwareStatus()
        
        # Control state
        self._running = False
        self._update_task: Optional[asyncio.Task] = None
        
        # Known hardware components (from original system analysis)
        self.temperature_sensors = {
            "tank_bottom": 8,    # Modbus address 8
            "tank_wall": 9,      # Modbus address 9  
            "pump": 10,          # Modbus address 10
            "fountain": 11,      # Modbus address 11
        }
        
        self.digital_outputs = {
            "mixer_motor": {"address": 0, "bit": 0},
            "fountain_motor": {"address": 0, "bit": 1},
            "tank_heater": {"address": 0, "bit": 2},
            "pump_heater": {"address": 0, "bit": 3},
            "cooling_valve": {"address": 0, "bit": 4},
            "vibration_motor": {"address": 0, "bit": 5},
            "mold_heater": {"address": 0, "bit": 6},
        }
        
        self.digital_inputs = {
            "emergency_stop": {"address": 1, "bit": 0},
            "cover_sensor": {"address": 1, "bit": 1},
            "pedal_switch": {"address": 1, "bit": 2},
            "level_sensor": {"address": 1, "bit": 3},
        }
        
        self.analog_inputs = {
            "grid_voltage": 12,
            "grid_frequency": 13,
            "neutral_current": 14,
            "motor1_current": 15,
            "motor2_current": 16,
        }
        
    async def initialize(self) -> bool:
        """Initialize hardware manager and establish communications."""
        try:
            logger.info("Initializing hardware manager")
            
            # Connect to Modbus
            if not await modbus_client.connect():
                logger.error("Failed to connect to Modbus RTU")
                return False
            
            # Load hardware mappings from database
            await self._load_hardware_mappings()
            
            # Initialize component status
            await self._initialize_components()
            
            # Start monitoring task
            await self.start_monitoring()
            
            logger.info("Hardware manager initialized successfully")
            return True
            
        except Exception as e:
            logger.error(f"Hardware manager initialization failed: {e}")
            return False
    
    async def start_monitoring(self) -> None:
        """Start hardware monitoring background task."""
        if self._update_task and not self._update_task.done():
            return
        
        self._running = True
        self._update_task = asyncio.create_task(self._monitoring_loop())
        logger.info("Hardware monitoring started")
    
    async def stop_monitoring(self) -> None:
        """Stop hardware monitoring background task."""
        self._running = False
        if self._update_task:
            self._update_task.cancel()
            try:
                await self._update_task
            except asyncio.CancelledError:
                pass
        logger.info("Hardware monitoring stopped")
    
    async def shutdown(self) -> None:
        """Shutdown hardware manager and close connections."""
        logger.info("Shutting down hardware manager")
        
        # Stop monitoring
        await self.stop_monitoring()
        
        # Turn off all motors and heaters for safety
        await self.emergency_stop()
        
        # Disconnect Modbus
        await modbus_client.disconnect()
        
        logger.info("Hardware manager shutdown complete")
    
    async def _monitoring_loop(self) -> None:
        """Main monitoring loop for hardware status updates."""
        while self._running:
            try:
                # Update temperatures
                await self._update_temperatures()
                
                # Update motor states
                await self._update_motor_states()
                
                # Update safety status
                await self._update_safety_status()
                
                # Update electrical parameters
                await self._update_electrical_status()
                
                # Update overall system status
                self._update_system_status()
                
                # Wait for next update cycle
                await asyncio.sleep(settings.process.temperature_read_interval)
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in hardware monitoring loop: {e}")
                await asyncio.sleep(1.0)  # Short delay before retry
    
    async def _update_temperatures(self) -> None:
        """Update temperature sensor readings."""
        for sensor_name, address in self.temperature_sensors.items():
            try:
                response = await modbus_client.read_input_registers(address, 1)
                
                if response.success and response.value:
                    # Convert raw reading to temperature (assuming 0.1°C resolution)
                    temperature = response.value[0] * 0.1
                    
                    reading = TemperatureReading(
                        value=temperature,
                        timestamp=datetime.now(),
                        sensor_name=sensor_name,
                        is_valid=True
                    )
                    
                    # Validate temperature range
                    if not (settings.temperature.absolute_min_temp <= temperature <= settings.temperature.absolute_max_temp):
                        reading.is_valid = False
                        reading.error_message = f"Temperature {temperature}°C outside valid range"
                        logger.warning(reading.error_message)
                    
                    self.current_status.temperatures[sensor_name] = reading
                    self.error_counts[sensor_name] = 0
                    
                else:
                    self.error_counts[sensor_name] = self.error_counts.get(sensor_name, 0) + 1
                    logger.error(f"Failed to read temperature sensor {sensor_name}: {response.error}")
                    
            except Exception as e:
                self.error_counts[sensor_name] = self.error_counts.get(sensor_name, 0) + 1
                logger.error(f"Exception reading temperature sensor {sensor_name}: {e}")
    
    async def _update_motor_states(self) -> None:
        """Update motor status information."""
        try:
            # Read digital output states to get motor enable status
            response = await modbus_client.read_coils(0, 8)  # Read first 8 outputs
            
            if response.success and response.value:
                motor_states = response.value
                
                for motor_name, config in self.digital_outputs.items():
                    if "motor" in motor_name:
                        bit_index = config["bit"]
                        is_enabled = bool(motor_states[bit_index]) if bit_index < len(motor_states) else False
                        
                        # Update motor status
                        if motor_name not in self.current_status.motors:
                            self.current_status.motors[motor_name] = MotorStatus(name=motor_name)
                        
                        motor_status = self.current_status.motors[motor_name]
                        motor_status.is_enabled = is_enabled
                        motor_status.state = MotorState.RUNNING if is_enabled else MotorState.STOPPED
                        
        except Exception as e:
            logger.error(f"Failed to update motor states: {e}")
    
    async def _update_safety_status(self) -> None:
        """Update safety system status."""
        try:
            # Read digital inputs for safety sensors
            response = await modbus_client.read_discrete_inputs(1, 4)
            
            if response.success and response.value:
                inputs = response.value
                
                self.current_status.safety.emergency_stop_active = bool(inputs[0]) if len(inputs) > 0 else False
                self.current_status.safety.cover_sensor_closed = bool(inputs[1]) if len(inputs) > 1 else True
                
                self.current_status.safety.last_safety_check = datetime.now()
                
        except Exception as e:
            logger.error(f"Failed to update safety status: {e}")
    
    async def _update_electrical_status(self) -> None:
        """Update electrical parameters (voltage, current, frequency)."""
        try:
            # Read electrical parameters
            for param_name, address in self.analog_inputs.items():
                response = await modbus_client.read_input_registers(address, 1)
                
                if response.success and response.value:
                    raw_value = response.value[0]
                    
                    # Convert based on parameter type
                    if "voltage" in param_name:
                        value = raw_value * 0.1  # 0.1V resolution
                    elif "current" in param_name:
                        value = raw_value * 0.01  # 0.01A resolution
                    elif "frequency" in param_name:
                        value = raw_value * 0.01  # 0.01Hz resolution
                    else:
                        value = raw_value
                    
                    # Check for alarms
                    if "current" in param_name:
                        limit_map = {
                            "neutral_current": settings.safety.max_neutral_current,
                            "motor1_current": settings.safety.max_motor1_current,
                            "motor2_current": settings.safety.max_motor2_current,
                        }
                        
                        if param_name in limit_map and value > limit_map[param_name]:
                            alarm_msg = f"{param_name} over limit: {value}A > {limit_map[param_name]}A"
                            if alarm_msg not in self.current_status.safety.current_alarms:
                                self.current_status.safety.current_alarms.append(alarm_msg)
                                logger.warning(alarm_msg)
                    
        except Exception as e:
            logger.error(f"Failed to update electrical status: {e}")
    
    def _update_system_status(self) -> None:
        """Update overall system status based on component health."""
        self.current_status.last_update = datetime.now()
        
        # Calculate communication health
        total_errors = sum(self.error_counts.values())
        total_reads = len(self.error_counts) * 10  # Assume 10 reads per component
        self.current_status.communication_health = max(0, (total_reads - total_errors) / max(1, total_reads) * 100)
        
        # Determine overall system status
        if self.current_status.safety.emergency_stop_active:
            self.current_status.system_status = ComponentStatus.ERROR
        elif total_errors > 10:
            self.current_status.system_status = ComponentStatus.ERROR
        elif self.current_status.communication_health < 80:
            self.current_status.system_status = ComponentStatus.OFFLINE
        else:
            self.current_status.system_status = ComponentStatus.ONLINE
    
    # Temperature Control Methods
    
    async def get_temperature(self, sensor_name: str) -> Optional[TemperatureReading]:
        """Get current temperature reading for a specific sensor."""
        return self.current_status.temperatures.get(sensor_name)
    
    async def get_all_temperatures(self) -> Dict[str, TemperatureReading]:
        """Get all current temperature readings."""
        return self.current_status.temperatures.copy()
    
    async def get_average_tank_temperature(self) -> Optional[float]:
        """Get average temperature of tank sensors."""
        tank_sensors = ["tank_bottom", "tank_wall"]
        valid_temps = []
        
        for sensor in tank_sensors:
            reading = await self.get_temperature(sensor)
            if reading and reading.is_valid:
                valid_temps.append(reading.value)
        
        return sum(valid_temps) / len(valid_temps) if valid_temps else None
    
    # Motor Control Methods
    
    async def set_motor_state(self, motor_name: str, enabled: bool) -> bool:
        """Enable or disable a motor."""
        if motor_name not in self.digital_outputs:
            logger.error(f"Unknown motor: {motor_name}")
            return False
        
        config = self.digital_outputs[motor_name]
        address = config["address"]
        bit = config["bit"]
        
        try:
            # Read current state
            response = await modbus_client.read_coils(address, 8)
            
            if not response.success:
                logger.error(f"Failed to read current motor states: {response.error}")
                return False
            
            # Modify the specific bit
            current_states = response.value[:]
            current_states[bit] = enabled
            
            # Write back the modified state
            write_response = await modbus_client.write_multiple_coils(address, current_states)
            
            if write_response.success:
                logger.info(f"Motor {motor_name} {'enabled' if enabled else 'disabled'}")
                return True
            else:
                logger.error(f"Failed to set motor {motor_name} state: {write_response.error}")
                return False
                
        except Exception as e:
            logger.error(f"Exception setting motor {motor_name} state: {e}")
            return False
    
    async def enable_motor(self, motor_name: str) -> bool:
        """Enable a specific motor."""
        return await self.set_motor_state(motor_name, True)
    
    async def disable_motor(self, motor_name: str) -> bool:
        """Disable a specific motor."""
        return await self.set_motor_state(motor_name, False)
    
    async def disable_all_motors(self) -> bool:
        """Disable all motors for safety."""
        success = True
        
        for motor_name in self.digital_outputs:
            if "motor" in motor_name:
                result = await self.disable_motor(motor_name)
                success = success and result
        
        return success
    
    # Heater Control Methods
    
    async def set_heater_state(self, heater_name: str, enabled: bool) -> bool:
        """Enable or disable a heater."""
        heater_map = {
            "tank_heater": "tank_heater",
            "pump_heater": "pump_heater", 
            "mold_heater": "mold_heater",
        }
        
        if heater_name not in heater_map:
            logger.error(f"Unknown heater: {heater_name}")
            return False
        
        output_name = heater_map[heater_name]
        return await self.set_motor_state(output_name, enabled)  # Same method as motors
    
    async def enable_heater(self, heater_name: str) -> bool:
        """Enable a specific heater."""
        return await self.set_heater_state(heater_name, True)
    
    async def disable_heater(self, heater_name: str) -> bool:
        """Disable a specific heater."""  
        return await self.set_heater_state(heater_name, False)
    
    async def disable_all_heaters(self) -> bool:
        """Disable all heaters for safety."""
        heaters = ["tank_heater", "pump_heater", "mold_heater"]
        success = True
        
        for heater in heaters:
            result = await self.disable_heater(heater)
            success = success and result
        
        return success
    
    # Safety Methods
    
    async def emergency_stop(self) -> bool:
        """Execute emergency stop - disable all motors and heaters."""
        logger.warning("EMERGENCY STOP ACTIVATED")
        
        motor_success = await self.disable_all_motors()
        heater_success = await self.disable_all_heaters()
        
        success = motor_success and heater_success
        
        if success:
            logger.info("Emergency stop completed successfully")
        else:
            logger.error("Emergency stop failed to complete")
        
        return success
    
    async def is_safe_to_operate(self) -> Tuple[bool, List[str]]:
        """Check if system is safe to operate."""
        issues = []
        
        # Check emergency stop
        if self.current_status.safety.emergency_stop_active:
            issues.append("Emergency stop is active")
        
        # Check cover sensor
        if not self.current_status.safety.cover_sensor_closed:
            issues.append("Safety cover is open")
        
        # Check temperature limits
        for sensor_name, reading in self.current_status.temperatures.items():
            if not reading.is_valid:
                issues.append(f"Temperature sensor {sensor_name} error: {reading.error_message}")
        
        # Check communication health
        if self.current_status.communication_health < 50:
            issues.append("Poor communication with hardware")
        
        # Check for current alarms
        if self.current_status.safety.current_alarms:
            issues.extend(self.current_status.safety.current_alarms)
        
        return len(issues) == 0, issues
    
    # Status and Information Methods
    
    def get_hardware_status(self) -> HardwareStatus:
        """Get complete hardware status."""
        return self.current_status
    
    def get_communication_statistics(self) -> Dict[str, Any]:
        """Get Modbus communication statistics."""
        return modbus_client.get_statistics()
    
    async def _load_hardware_mappings(self) -> None:
        """Load hardware mappings from database."""
        try:
            # This would load from database in production
            # For now, we'll use the hardcoded mappings
            logger.info("Hardware mappings loaded from configuration")
        except Exception as e:
            logger.error(f"Failed to load hardware mappings: {e}")
    
    async def _initialize_components(self) -> None:
        """Initialize all hardware components."""
        # Initialize motor status objects
        for motor_name in self.digital_outputs:
            if "motor" in motor_name:
                self.current_status.motors[motor_name] = MotorStatus(name=motor_name)
        
        # Initialize error counters
        for sensor_name in self.temperature_sensors:
            self.error_counts[sensor_name] = 0


# Global hardware manager instance
hardware_manager = HardwareManager()