"""
Safety monitoring service for chocolate tempering machine.
Provides real-time safety monitoring with automatic responses and alarm management.
"""

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

from ...shared.config import settings
from ...shared.database import db_manager
from ...shared.models.system import ErrorLog, ErrorSeverity, ErrorCategory
from ..hardware.hardware_manager import hardware_manager


logger = logging.getLogger(__name__)


class AlarmPriority(str, Enum):
    """Alarm priority levels."""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class AlarmState(str, Enum):
    """Alarm states."""
    ACTIVE = "active"
    ACKNOWLEDGED = "acknowledged"
    CLEARED = "cleared"
    SUPPRESSED = "suppressed"


@dataclass
class SafetyAlarm:
    """Safety alarm information."""
    id: str
    title: str
    message: str
    category: ErrorCategory
    priority: AlarmPriority
    state: AlarmState = AlarmState.ACTIVE
    timestamp: datetime = field(default_factory=datetime.now)
    acknowledgement_time: Optional[datetime] = None
    acknowledgement_user: Optional[str] = None
    auto_clear: bool = False
    clear_condition: Optional[Callable[[], bool]] = None
    suppression_time: Optional[datetime] = None
    occurrence_count: int = 1
    first_occurrence: datetime = field(default_factory=datetime.now)


@dataclass
class SafetyLimits:
    """Safety operating limits configuration."""
    # Temperature limits (°C)
    max_tank_temperature: float = 80.0
    max_fountain_temperature: float = 70.0
    min_temperature: float = 5.0
    temperature_rate_limit: float = 5.0  # °C/min max rate of change
    
    # Electrical limits
    max_voltage_deviation: float = 0.15  # ±15%
    max_frequency_deviation: float = 0.02  # ±2%
    max_neutral_current: float = 16.0  # A
    max_motor_current: float = 10.0  # A
    
    # Process limits
    max_process_duration: float = 14400.0  # 4 hours in seconds
    max_phase_duration: float = 3600.0  # 1 hour in seconds
    
    # Communication limits
    max_communication_errors: int = 10
    communication_timeout: float = 5.0  # seconds


class SafetyMonitor:
    """
    Comprehensive safety monitoring system for the tempering machine.
    Monitors all safety-critical parameters and manages alarm conditions.
    """
    
    def __init__(self):
        self.active_alarms: Dict[str, SafetyAlarm] = {}
        self.alarm_history: List[SafetyAlarm] = []
        self.safety_limits = SafetyLimits()
        self.monitoring_task: Optional[asyncio.Task] = None
        self.is_running = False
        
        # Safety state tracking
        self.last_temperature_readings: Dict[str, Tuple[datetime, float]] = {}
        self.consecutive_errors = 0
        self.last_safety_check = datetime.now()
        self.emergency_stop_active = False
        
        # Alarm callbacks
        self.alarm_callbacks: List[Callable[[SafetyAlarm], None]] = []
        
        # Statistics
        self.safety_stats = {
            "total_alarms": 0,
            "critical_alarms": 0,
            "emergency_stops": 0,
            "uptime_start": datetime.now(),
            "last_critical_alarm": None,
        }
    
    async def initialize(self) -> bool:
        """Initialize safety monitoring system."""
        try:
            logger.info("Initializing safety monitoring system")
            
            # Load safety limits from configuration
            self._load_safety_limits()
            
            # Initialize hardware monitoring
            if not await self._verify_hardware_safety():
                logger.error("Hardware safety verification failed")
                return False
            
            # Start monitoring
            await self.start_monitoring()
            
            logger.info("Safety monitoring system initialized successfully")
            return True
            
        except Exception as e:
            logger.error(f"Failed to initialize safety monitor: {e}")
            return False
    
    async def start_monitoring(self) -> None:
        """Start safety monitoring background task."""
        if self.monitoring_task and not self.monitoring_task.done():
            return
        
        self.is_running = True
        self.monitoring_task = asyncio.create_task(self._monitoring_loop())
        logger.info("Safety monitoring started")
    
    async def stop_monitoring(self) -> None:
        """Stop safety monitoring background task."""
        self.is_running = False
        if self.monitoring_task:
            self.monitoring_task.cancel()
            try:
                await self.monitoring_task
            except asyncio.CancelledError:
                pass
        logger.info("Safety monitoring stopped")
    
    async def shutdown(self) -> None:
        """Shutdown safety monitoring system."""
        logger.info("Shutting down safety monitoring system")
        await self.stop_monitoring()
        
        # Log shutdown event
        await self._create_alarm(
            "SAFETY_SHUTDOWN",
            "Safety monitoring shutdown",
            "Safety monitoring system shutdown initiated",
            ErrorCategory.SYSTEM,
            AlarmPriority.MEDIUM
        )
    
    async def _monitoring_loop(self) -> None:
        """Main safety monitoring loop."""
        while self.is_running:
            try:
                # Perform comprehensive safety checks
                await self._check_temperature_safety()
                await self._check_electrical_safety()
                await self._check_communication_safety()
                await self._check_process_safety()
                await self._check_hardware_safety()
                
                # Update alarm states
                await self._update_alarm_states()
                
                # Check for emergency conditions
                await self._check_emergency_conditions()
                
                # Update statistics
                self._update_safety_statistics()
                
                # Wait for next monitoring cycle
                await asyncio.sleep(settings.safety.error_check_interval)
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in safety monitoring loop: {e}")
                self.consecutive_errors += 1
                await asyncio.sleep(0.5)  # Short delay before retry
    
    async def _check_temperature_safety(self) -> None:
        """Check temperature-related safety conditions."""
        try:
            temperatures = await hardware_manager.get_all_temperatures()
            
            for sensor_name, reading in temperatures.items():
                if not reading.is_valid:
                    await self._create_alarm(
                        f"TEMP_SENSOR_{sensor_name.upper()}",
                        f"Temperature sensor {sensor_name} error",
                        f"Temperature sensor {sensor_name}: {reading.error_message}",
                        ErrorCategory.TEMPERATURE,
                        AlarmPriority.HIGH
                    )
                    continue
                
                temp = reading.value
                
                # Check absolute temperature limits
                if temp > self.safety_limits.max_tank_temperature:
                    await self._create_alarm(
                        f"TEMP_HIGH_{sensor_name.upper()}",
                        f"High temperature alarm - {sensor_name}",
                        f"Temperature {temp:.1f}°C exceeds maximum {self.safety_limits.max_tank_temperature}°C",
                        ErrorCategory.TEMPERATURE,
                        AlarmPriority.CRITICAL,
                        auto_clear=True,
                        clear_condition=lambda t=temp, limit=self.safety_limits.max_tank_temperature: t <= limit - 2.0
                    )
                
                if temp < self.safety_limits.min_temperature:
                    await self._create_alarm(
                        f"TEMP_LOW_{sensor_name.upper()}",
                        f"Low temperature alarm - {sensor_name}",
                        f"Temperature {temp:.1f}°C below minimum {self.safety_limits.min_temperature}°C",
                        ErrorCategory.TEMPERATURE,
                        AlarmPriority.MEDIUM
                    )
                
                # Check rate of temperature change
                await self._check_temperature_rate(sensor_name, temp)
                
                # Update temperature history
                self.last_temperature_readings[sensor_name] = (datetime.now(), temp)
                
        except Exception as e:
            logger.error(f"Error checking temperature safety: {e}")
            await self._create_alarm(
                "TEMP_CHECK_ERROR",
                "Temperature safety check error",
                f"Error performing temperature safety checks: {e}",
                ErrorCategory.SYSTEM,
                AlarmPriority.HIGH
            )
    
    async def _check_temperature_rate(self, sensor_name: str, current_temp: float) -> None:
        """Check temperature rate of change."""
        if sensor_name not in self.last_temperature_readings:
            return
        
        last_time, last_temp = self.last_temperature_readings[sensor_name]
        time_diff = (datetime.now() - last_time).total_seconds() / 60.0  # minutes
        
        if time_diff > 0.1:  # Minimum time difference
            rate = abs(current_temp - last_temp) / time_diff  # °C/min
            
            if rate > self.safety_limits.temperature_rate_limit:
                await self._create_alarm(
                    f"TEMP_RATE_{sensor_name.upper()}",
                    f"Temperature rate alarm - {sensor_name}",
                    f"Temperature changing too rapidly: {rate:.1f}°C/min > {self.safety_limits.temperature_rate_limit}°C/min",
                    ErrorCategory.TEMPERATURE,
                    AlarmPriority.HIGH
                )
    
    async def _check_electrical_safety(self) -> None:
        """Check electrical safety parameters."""
        try:
            hw_status = hardware_manager.get_hardware_status()
            
            # This would read actual electrical parameters from hardware
            # For now, we'll simulate based on hardware communication health
            
            if hw_status.communication_health < 90:
                await self._create_alarm(
                    "ELECTRICAL_COMM",
                    "Electrical communication issue",
                    f"Communication health degraded: {hw_status.communication_health:.1f}%",
                    ErrorCategory.COMMUNICATION,
                    AlarmPriority.MEDIUM
                )
            
            # Check for current alarms from hardware
            for alarm_msg in hw_status.safety.current_alarms:
                alarm_id = f"CURRENT_{hash(alarm_msg) % 10000}"
                await self._create_alarm(
                    alarm_id,
                    "Electrical current alarm",
                    alarm_msg,
                    ErrorCategory.POWER,
                    AlarmPriority.HIGH
                )
                
        except Exception as e:
            logger.error(f"Error checking electrical safety: {e}")
    
    async def _check_communication_safety(self) -> None:
        """Check communication system safety."""
        try:
            comm_stats = hardware_manager.get_communication_statistics()
            
            # Check communication error rate
            if comm_stats.get("failed_requests", 0) > self.safety_limits.max_communication_errors:
                await self._create_alarm(
                    "COMM_ERRORS",
                    "High communication error rate",
                    f"Communication errors: {comm_stats.get('failed_requests', 0)}",
                    ErrorCategory.COMMUNICATION,
                    AlarmPriority.HIGH
                )
            
            # Check communication timeout
            last_successful = datetime.fromisoformat(comm_stats.get("last_successful_read", datetime.now().isoformat()))
            time_since_success = (datetime.now() - last_successful).total_seconds()
            
            if time_since_success > self.safety_limits.communication_timeout:
                await self._create_alarm(
                    "COMM_TIMEOUT",
                    "Communication timeout",
                    f"No successful communication for {time_since_success:.1f}s",
                    ErrorCategory.COMMUNICATION,
                    AlarmPriority.CRITICAL
                )
                
        except Exception as e:
            logger.error(f"Error checking communication safety: {e}")
    
    async def _check_process_safety(self) -> None:
        """Check process execution safety."""
        try:
            # This would check with recipe controller for process safety
            # For now, we'll implement basic checks
            
            # Check if emergency stop is active
            hw_status = hardware_manager.get_hardware_status()
            
            if hw_status.safety.emergency_stop_active and not self.emergency_stop_active:
                await self._create_alarm(
                    "EMERGENCY_STOP",
                    "Emergency stop activated",
                    "Hardware emergency stop has been activated",
                    ErrorCategory.SAFETY,
                    AlarmPriority.CRITICAL
                )
                self.emergency_stop_active = True
                
            elif not hw_status.safety.emergency_stop_active and self.emergency_stop_active:
                # Emergency stop cleared
                self.emergency_stop_active = False
                await self._clear_alarm("EMERGENCY_STOP")
            
            # Check cover sensor
            if not hw_status.safety.cover_sensor_closed:
                await self._create_alarm(
                    "COVER_OPEN",
                    "Safety cover open",
                    "Safety cover is open - operation not safe",
                    ErrorCategory.SAFETY,
                    AlarmPriority.HIGH
                )
            else:
                await self._clear_alarm("COVER_OPEN")
                
        except Exception as e:
            logger.error(f"Error checking process safety: {e}")
    
    async def _check_hardware_safety(self) -> None:
        """Check hardware component safety."""
        try:
            is_safe, issues = await hardware_manager.is_safe_to_operate()
            
            if not is_safe:
                for i, issue in enumerate(issues):
                    await self._create_alarm(
                        f"HARDWARE_ISSUE_{i}",
                        "Hardware safety issue",
                        issue,
                        ErrorCategory.HARDWARE,
                        AlarmPriority.HIGH
                    )
                    
        except Exception as e:
            logger.error(f"Error checking hardware safety: {e}")
    
    async def _check_emergency_conditions(self) -> None:
        """Check for conditions requiring emergency stop."""
        emergency_conditions = []
        
        # Check for critical alarms
        for alarm in self.active_alarms.values():
            if alarm.priority == AlarmPriority.CRITICAL and alarm.state == AlarmState.ACTIVE:
                emergency_conditions.append(alarm.title)
        
        # If we have critical conditions, trigger emergency stop
        if emergency_conditions and not self.emergency_stop_active:
            logger.critical(f"Emergency conditions detected: {', '.join(emergency_conditions)}")
            
            # Trigger emergency stop through hardware manager
            await hardware_manager.emergency_stop()
            
            # Create emergency stop alarm
            await self._create_alarm(
                "AUTO_EMERGENCY_STOP",
                "Automatic emergency stop",
                f"Emergency stop triggered by: {', '.join(emergency_conditions)}",
                ErrorCategory.SAFETY,
                AlarmPriority.CRITICAL
            )
            
            self.safety_stats["emergency_stops"] += 1
    
    async def _create_alarm(
        self,
        alarm_id: str,
        title: str,
        message: str,
        category: ErrorCategory,
        priority: AlarmPriority,
        auto_clear: bool = False,
        clear_condition: Optional[Callable[[], bool]] = None
    ) -> None:
        """Create or update a safety alarm."""
        try:
            # Check if alarm already exists
            if alarm_id in self.active_alarms:
                # Update existing alarm
                alarm = self.active_alarms[alarm_id]
                alarm.occurrence_count += 1
                alarm.timestamp = datetime.now()
                logger.warning(f"Alarm updated: {alarm_id} (count: {alarm.occurrence_count})")
            else:
                # Create new alarm
                alarm = SafetyAlarm(
                    id=alarm_id,
                    title=title,
                    message=message,
                    category=category,
                    priority=priority,
                    auto_clear=auto_clear,
                    clear_condition=clear_condition
                )
                
                self.active_alarms[alarm_id] = alarm
                self.alarm_history.append(alarm)
                self.safety_stats["total_alarms"] += 1
                
                if priority == AlarmPriority.CRITICAL:
                    self.safety_stats["critical_alarms"] += 1
                    self.safety_stats["last_critical_alarm"] = datetime.now()
                
                logger.warning(f"Alarm created: {alarm_id} - {title}")
            
            # Log to database
            await self._log_alarm_to_database(alarm)
            
            # Notify callbacks
            for callback in self.alarm_callbacks:
                try:
                    callback(alarm)
                except Exception as e:
                    logger.error(f"Error in alarm callback: {e}")
                    
        except Exception as e:
            logger.error(f"Error creating alarm: {e}")
    
    async def _clear_alarm(self, alarm_id: str, user: Optional[str] = None) -> bool:
        """Clear an active alarm."""
        try:
            if alarm_id in self.active_alarms:
                alarm = self.active_alarms[alarm_id]
                alarm.state = AlarmState.CLEARED
                alarm.acknowledgement_time = datetime.now()
                alarm.acknowledgement_user = user or "SYSTEM"
                
                # Remove from active alarms
                del self.active_alarms[alarm_id]
                
                logger.info(f"Alarm cleared: {alarm_id} by {user or 'SYSTEM'}")
                return True
                
        except Exception as e:
            logger.error(f"Error clearing alarm: {e}")
        
        return False
    
    async def _update_alarm_states(self) -> None:
        """Update alarm states and handle auto-clearing."""
        alarms_to_clear = []
        
        for alarm_id, alarm in self.active_alarms.items():
            # Check auto-clear condition
            if alarm.auto_clear and alarm.clear_condition:
                try:
                    if alarm.clear_condition():
                        alarms_to_clear.append(alarm_id)
                except Exception as e:
                    logger.error(f"Error checking clear condition for {alarm_id}: {e}")
        
        # Clear alarms that meet their clear conditions
        for alarm_id in alarms_to_clear:
            await self._clear_alarm(alarm_id, "AUTO")
    
    async def acknowledge_alarm(self, alarm_id: str, user: str) -> bool:
        """Acknowledge an alarm."""
        try:
            if alarm_id in self.active_alarms:
                alarm = self.active_alarms[alarm_id]
                alarm.state = AlarmState.ACKNOWLEDGED
                alarm.acknowledgement_time = datetime.now()
                alarm.acknowledgement_user = user
                
                logger.info(f"Alarm acknowledged: {alarm_id} by {user}")
                return True
                
        except Exception as e:
            logger.error(f"Error acknowledging alarm: {e}")
        
        return False
    
    async def suppress_alarm(self, alarm_id: str, duration_minutes: int, user: str) -> bool:
        """Suppress an alarm for a specified duration."""
        try:
            if alarm_id in self.active_alarms:
                alarm = self.active_alarms[alarm_id]
                alarm.state = AlarmState.SUPPRESSED
                alarm.suppression_time = datetime.now() + timedelta(minutes=duration_minutes)
                
                logger.info(f"Alarm suppressed: {alarm_id} for {duration_minutes}min by {user}")
                return True
                
        except Exception as e:
            logger.error(f"Error suppressing alarm: {e}")
        
        return False
    
    def get_active_alarms(self, priority_filter: Optional[AlarmPriority] = None) -> List[SafetyAlarm]:
        """Get list of active alarms, optionally filtered by priority."""
        alarms = list(self.active_alarms.values())
        
        if priority_filter:
            alarms = [alarm for alarm in alarms if alarm.priority == priority_filter]
        
        # Sort by priority and timestamp
        priority_order = {
            AlarmPriority.CRITICAL: 0,
            AlarmPriority.HIGH: 1,
            AlarmPriority.MEDIUM: 2,
            AlarmPriority.LOW: 3
        }
        
        return sorted(alarms, key=lambda a: (priority_order[a.priority], a.timestamp))
    
    def get_alarm_summary(self) -> Dict[str, Any]:
        """Get summary of alarm status."""
        active_by_priority = {}
        for priority in AlarmPriority:
            active_by_priority[priority.value] = len([
                a for a in self.active_alarms.values() if a.priority == priority
            ])
        
        return {
            "total_active_alarms": len(self.active_alarms),
            "active_by_priority": active_by_priority,
            "total_alarms_today": len([
                a for a in self.alarm_history 
                if a.timestamp.date() == datetime.now().date()
            ]),
            "emergency_stop_active": self.emergency_stop_active,
            "last_safety_check": self.last_safety_check.isoformat(),
            "consecutive_errors": self.consecutive_errors,
            "statistics": self.safety_stats.copy()
        }
    
    def add_alarm_callback(self, callback: Callable[[SafetyAlarm], None]) -> None:
        """Add callback function to be called when alarms are created."""
        self.alarm_callbacks.append(callback)
    
    async def _verify_hardware_safety(self) -> bool:
        """Verify hardware is in a safe state for operation."""
        is_safe, issues = await hardware_manager.is_safe_to_operate()
        
        if not is_safe:
            for issue in issues:
                logger.error(f"Hardware safety issue: {issue}")
        
        return is_safe
    
    def _load_safety_limits(self) -> None:
        """Load safety limits from configuration."""
        self.safety_limits = SafetyLimits(
            max_tank_temperature=settings.temperature.absolute_max_temp,
            min_temperature=settings.temperature.absolute_min_temp,
            max_neutral_current=settings.safety.max_neutral_current,
            max_motor_current=settings.safety.max_motor1_current,
            communication_timeout=settings.safety.communication_timeout,
        )
    
    def _update_safety_statistics(self) -> None:
        """Update safety monitoring statistics."""
        self.last_safety_check = datetime.now()
        self.consecutive_errors = 0  # Reset if we got here successfully
    
    async def _log_alarm_to_database(self, alarm: SafetyAlarm) -> None:
        """Log alarm to database for audit trail."""
        try:
            async with db_manager.get_async_session() as session:
                error_log = ErrorLog(
                    error_code=alarm.id,
                    category=alarm.category,
                    severity=ErrorSeverity(alarm.priority.value.upper()) if alarm.priority.value.upper() in [s.value for s in ErrorSeverity] else ErrorSeverity.MEDIUM,
                    title=alarm.title,
                    message=alarm.message,
                    component="SafetyMonitor",
                    occurrence_count=alarm.occurrence_count,
                    first_occurrence=alarm.first_occurrence,
                    last_occurrence=alarm.timestamp,
                )
                
                session.add(error_log)
                await session.commit()
                
        except Exception as e:
            logger.error(f"Error logging alarm to database: {e}")


# Global safety monitor instance
safety_monitor = SafetyMonitor()