"""
Recipe controller for managing chocolate tempering process execution.
Orchestrates recipe execution, state management, and process monitoring.
"""

import asyncio
import logging
from typing import Optional, Dict, Any, List
from datetime import datetime, timezone
from uuid import uuid4

from ...shared.database import get_db, db_manager
from ...shared.models.recipe import Recipe
from ...shared.models.process import ProcessSession, ProcessStatus, ProcessLog, LogLevel
from .state_machine import TemperingStateMachine, ProcessContext, ProcessMetrics
from ..hardware.hardware_manager import hardware_manager


logger = logging.getLogger(__name__)


class RecipeExecutionError(Exception):
    """Exception raised during recipe execution."""
    pass


class RecipeController:
    """
    Recipe controller manages the complete lifecycle of tempering process execution.
    Handles recipe loading, process control, monitoring, and data logging.
    """
    
    def __init__(self):
        self.current_session: Optional[ProcessSession] = None
        self.state_machine: Optional[TemperingStateMachine] = None
        self.recipe: Optional[Recipe] = None
        self.session_id: Optional[str] = None
        self.monitoring_task: Optional[asyncio.Task] = None
        self._is_running = False
    
    async def start_recipe(self, recipe_id: int, user_id: Optional[str] = None) -> str:
        """
        Start executing a recipe.
        Returns session ID for tracking.
        """
        try:
            # Check if another recipe is already running
            if self._is_running:
                raise RecipeExecutionError("Another recipe is already running")
            
            logger.info(f"Starting recipe execution: recipe_id={recipe_id}, user={user_id}")
            
            # Load recipe from database
            async with db_manager.get_async_session() as session:
                self.recipe = await session.get(Recipe, recipe_id)
                if not self.recipe:
                    raise RecipeExecutionError(f"Recipe {recipe_id} not found")
                
                if not self.recipe.is_active:
                    raise RecipeExecutionError(f"Recipe {recipe_id} is not active")
                
                # Validate recipe parameters
                if not self.recipe.validate_temperatures():
                    raise RecipeExecutionError(f"Recipe {recipe_id} has invalid temperature parameters")
            
            # Check hardware readiness
            is_safe, issues = await hardware_manager.is_safe_to_operate()
            if not is_safe:
                raise RecipeExecutionError(f"Hardware not ready: {', '.join(issues)}")
            
            # Create process session
            self.session_id = str(uuid4())
            await self._create_process_session(user_id)
            
            # Create process context
            context = ProcessContext(
                recipe_id=recipe_id,
                session_id=self.session_id,
                recipe_parameters=self.recipe.to_dict(),
                current_metrics=ProcessMetrics(phase_start_time=datetime.now()),
                user_id=user_id,
                start_time=datetime.now()
            )
            
            # Initialize state machine
            self.state_machine = TemperingStateMachine(context)
            
            if not await self.state_machine.initialize():
                raise RecipeExecutionError("Failed to initialize state machine")
            
            # Start the process
            await self._start_process_execution()
            
            logger.info(f"Recipe execution started successfully: session_id={self.session_id}")
            return self.session_id
            
        except Exception as e:
            logger.error(f"Failed to start recipe execution: {e}")
            await self._handle_startup_error(str(e))
            raise
    
    async def stop_recipe(self, user_id: Optional[str] = None, reason: str = "Manual stop") -> bool:
        """
        Stop the currently running recipe.
        """
        try:
            if not self._is_running or not self.state_machine:
                logger.warning("No recipe is currently running")
                return False
            
            logger.info(f"Stopping recipe execution: session_id={self.session_id}, reason={reason}")
            
            # Stop state machine control loop
            await self.state_machine.stop_control_loop()
            
            # Emergency stop hardware for safety
            await hardware_manager.emergency_stop()
            
            # Update process session
            await self._finalize_process_session(ProcessStatus.ABORTED, reason, user_id)
            
            # Log the stop event
            await self._log_process_event(LogLevel.INFO, f"Recipe stopped: {reason}")
            
            # Clean up
            await self._cleanup_process()
            
            logger.info("Recipe execution stopped successfully")
            return True
            
        except Exception as e:
            logger.error(f"Error stopping recipe: {e}")
            return False
    
    async def pause_recipe(self, user_id: Optional[str] = None) -> bool:
        """
        Pause the currently running recipe.
        """
        try:
            if not self._is_running or not self.state_machine:
                return False
            
            logger.info(f"Pausing recipe execution: session_id={self.session_id}")
            
            # Stop control loop but don't emergency stop
            await self.state_machine.stop_control_loop()
            
            # Update session status
            async with db_manager.get_async_session() as session:
                if self.current_session:
                    self.current_session.status = ProcessStatus.PAUSED
                    session.add(self.current_session)
                    await session.commit()
            
            await self._log_process_event(LogLevel.INFO, f"Recipe paused by {user_id or 'system'}")
            
            logger.info("Recipe execution paused")
            return True
            
        except Exception as e:
            logger.error(f"Error pausing recipe: {e}")
            return False
    
    async def resume_recipe(self, user_id: Optional[str] = None) -> bool:
        """
        Resume a paused recipe.
        """
        try:
            if not self.current_session or self.current_session.status != ProcessStatus.PAUSED:
                return False
            
            logger.info(f"Resuming recipe execution: session_id={self.session_id}")
            
            # Check hardware is still safe
            is_safe, issues = await hardware_manager.is_safe_to_operate()
            if not is_safe:
                await self._log_process_event(LogLevel.ERROR, f"Cannot resume - hardware issues: {', '.join(issues)}")
                return False
            
            # Resume control loop
            await self.state_machine.start_control_loop()
            
            # Update session status
            async with db_manager.get_async_session() as session:
                self.current_session.status = ProcessStatus.RUNNING
                session.add(self.current_session)
                await session.commit()
            
            await self._log_process_event(LogLevel.INFO, f"Recipe resumed by {user_id or 'system'}")
            
            logger.info("Recipe execution resumed")
            return True
            
        except Exception as e:
            logger.error(f"Error resuming recipe: {e}")
            return False
    
    async def emergency_stop(self, user_id: Optional[str] = None) -> bool:
        """
        Execute emergency stop of the current recipe.
        """
        try:
            logger.critical(f"EMERGENCY STOP activated: session_id={self.session_id}")
            
            # Immediate hardware emergency stop
            await hardware_manager.emergency_stop()
            
            # Stop state machine if running
            if self.state_machine:
                try:
                    self.state_machine.emergency_stop()
                except:
                    pass  # Force stop regardless of state
                await self.state_machine.stop_control_loop()
            
            # Update process session
            await self._finalize_process_session(
                ProcessStatus.ABORTED, 
                "Emergency stop activated", 
                user_id
            )
            
            # Log critical event
            await self._log_process_event(
                LogLevel.CRITICAL, 
                f"EMERGENCY STOP activated by {user_id or 'system'}"
            )
            
            # Clean up
            await self._cleanup_process()
            
            logger.critical("Emergency stop completed")
            return True
            
        except Exception as e:
            logger.critical(f"Error during emergency stop: {e}")
            return False
    
    async def get_process_status(self) -> Optional[Dict[str, Any]]:
        """
        Get current process status and metrics.
        """
        if not self.state_machine or not self.current_session:
            return None
        
        try:
            # Get state machine status
            sm_status = self.state_machine.get_process_status()
            
            # Get hardware status
            hw_status = hardware_manager.get_hardware_status()
            
            # Get session information
            session_info = {
                "session_id": self.current_session.id,
                "recipe_name": self.recipe.name if self.recipe else "Unknown",
                "status": self.current_session.status.value,
                "started_at": self.current_session.started_at.isoformat() if self.current_session.started_at else None,
                "duration_seconds": self.current_session.duration,
                "started_by": self.current_session.started_by,
            }
            
            return {
                "session": session_info,
                "state_machine": sm_status,
                "hardware": {
                    "temperatures": {k: v.value for k, v in hw_status.temperatures.items()},
                    "motors": {k: {"state": v.state.value, "enabled": v.is_enabled} 
                              for k, v in hw_status.motors.items()},
                    "safety": {
                        "emergency_stop": hw_status.safety.emergency_stop_active,
                        "cover_closed": hw_status.safety.cover_sensor_closed,
                        "alarms": hw_status.safety.temperature_alarms + hw_status.safety.current_alarms,
                    },
                    "communication_health": hw_status.communication_health,
                    "system_status": hw_status.system_status.value,
                }
            }
            
        except Exception as e:
            logger.error(f"Error getting process status: {e}")
            return None
    
    async def get_active_sessions(self) -> List[Dict[str, Any]]:
        """Get list of all active process sessions."""
        try:
            async with db_manager.get_async_session() as session:
                # This would query active sessions from database
                # For now, return current session if active
                if self.current_session and self.current_session.status in [
                    ProcessStatus.RUNNING, ProcessStatus.PAUSED, ProcessStatus.STARTING
                ]:
                    return [self.current_session.to_dict()]
                return []
                
        except Exception as e:
            logger.error(f"Error getting active sessions: {e}")
            return []
    
    async def _create_process_session(self, user_id: Optional[str]) -> None:
        """Create a new process session in the database."""
        try:
            async with db_manager.get_async_session() as session:
                self.current_session = ProcessSession(
                    id=self.session_id,
                    recipe_id=self.recipe.id,
                    status=ProcessStatus.STARTING,
                    started_at=datetime.now(timezone.utc),
                    started_by=user_id,
                    process_parameters=self.recipe.to_dict(),
                    target_heating_temp=self.recipe.heating_goal,
                    target_cooling_temp=self.recipe.cooling_goal,
                )
                
                session.add(self.current_session)
                await session.commit()
                
                logger.info(f"Process session created: {self.session_id}")
                
        except Exception as e:
            logger.error(f"Failed to create process session: {e}")
            raise
    
    async def _start_process_execution(self) -> None:
        """Start the actual process execution."""
        try:
            # Update session status
            async with db_manager.get_async_session() as session:
                self.current_session.status = ProcessStatus.RUNNING
                session.add(self.current_session)
                await session.commit()
            
            # Start state machine
            self.state_machine.start_process()
            await self.state_machine.start_control_loop()
            
            # Start monitoring task
            self.monitoring_task = asyncio.create_task(self._monitoring_loop())
            
            self._is_running = True
            
            # Log start event
            await self._log_process_event(LogLevel.INFO, "Recipe execution started")
            
        except Exception as e:
            logger.error(f"Failed to start process execution: {e}")
            raise
    
    async def _monitoring_loop(self) -> None:
        """Background monitoring loop for process execution."""
        while self._is_running:
            try:
                # Update process session with current metrics
                await self._update_session_metrics()
                
                # Check for process completion or error conditions
                await self._check_process_completion()
                
                # Sleep until next monitoring cycle
                await asyncio.sleep(5.0)  # Monitor every 5 seconds
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in monitoring loop: {e}")
                await asyncio.sleep(1.0)
    
    async def _update_session_metrics(self) -> None:
        """Update process session with current metrics."""
        if not self.current_session or not self.state_machine:
            return
        
        try:
            # Get current metrics from state machine
            context = self.state_machine.context
            metrics = context.current_metrics
            
            async with db_manager.get_async_session() as session:
                # Update session with current metrics
                self.current_session.current_phase = self.state_machine.get_current_phase()
                self.current_session.achieved_heating_temp = metrics.current_temperature
                self.current_session.error_count = metrics.error_count
                self.current_session.warning_count = metrics.warning_count
                
                session.add(self.current_session)
                await session.commit()
                
        except Exception as e:
            logger.error(f"Error updating session metrics: {e}")
    
    async def _check_process_completion(self) -> None:
        """Check if process has completed and handle completion."""
        if not self.state_machine:
            return
        
        current_state = self.state_machine.current_state.name.lower()
        
        if current_state == "completed":
            await self._handle_process_completion()
        elif current_state == "error":
            await self._handle_process_error()
        elif current_state == "emergency_stopped":
            await self._handle_emergency_stop()
    
    async def _handle_process_completion(self) -> None:
        """Handle successful process completion."""
        logger.info(f"Process completed successfully: session_id={self.session_id}")
        
        await self._finalize_process_session(
            ProcessStatus.COMPLETED,
            "Process completed successfully",
            None
        )
        
        await self._log_process_event(LogLevel.INFO, "Process completed successfully")
        await self._cleanup_process()
    
    async def _handle_process_error(self) -> None:
        """Handle process error condition."""
        logger.error(f"Process error detected: session_id={self.session_id}")
        
        await self._finalize_process_session(
            ProcessStatus.ERROR,
            "Process error occurred",
            None
        )
        
        await self._log_process_event(LogLevel.ERROR, "Process error occurred")
        await self._cleanup_process()
    
    async def _handle_emergency_stop(self) -> None:
        """Handle emergency stop condition."""
        logger.critical(f"Emergency stop condition: session_id={self.session_id}")
        
        await self._finalize_process_session(
            ProcessStatus.ABORTED,
            "Emergency stop condition",
            None
        )
        
        await self._log_process_event(LogLevel.CRITICAL, "Emergency stop condition")
        await self._cleanup_process()
    
    async def _finalize_process_session(
        self, 
        status: ProcessStatus, 
        reason: str, 
        user_id: Optional[str]
    ) -> None:
        """Finalize process session with final status."""
        try:
            async with db_manager.get_async_session() as session:
                if self.current_session:
                    self.current_session.status = status
                    self.current_session.completed_at = datetime.now(timezone.utc)
                    self.current_session.stop_reason = reason
                    self.current_session.stopped_by = user_id
                    
                    session.add(self.current_session)
                    await session.commit()
                    
        except Exception as e:
            logger.error(f"Error finalizing process session: {e}")
    
    async def _log_process_event(self, level: LogLevel, message: str) -> None:
        """Log a process event to the database."""
        try:
            if not self.current_session:
                return
            
            async with db_manager.get_async_session() as session:
                log_entry = ProcessLog(
                    session_id=self.current_session.id,
                    level=level,
                    message=message,
                    component="RecipeController",
                    phase=self.state_machine.get_current_phase() if self.state_machine else None,
                )
                
                session.add(log_entry)
                await session.commit()
                
        except Exception as e:
            logger.error(f"Error logging process event: {e}")
    
    async def _handle_startup_error(self, error_message: str) -> None:
        """Handle errors during startup."""
        try:
            if self.current_session:
                await self._finalize_process_session(
                    ProcessStatus.ERROR,
                    f"Startup error: {error_message}",
                    None
                )
            
            await self._cleanup_process()
            
        except Exception as e:
            logger.error(f"Error handling startup error: {e}")
    
    async def _cleanup_process(self) -> None:
        """Clean up process resources."""
        try:
            self._is_running = False
            
            # Cancel monitoring task
            if self.monitoring_task:
                self.monitoring_task.cancel()
                try:
                    await self.monitoring_task
                except asyncio.CancelledError:
                    pass
            
            # Clean up state machine
            if self.state_machine:
                await self.state_machine.stop_control_loop()
                self.state_machine = None
            
            # Reset instance variables
            self.current_session = None
            self.recipe = None
            self.session_id = None
            self.monitoring_task = None
            
            logger.info("Process cleanup completed")
            
        except Exception as e:
            logger.error(f"Error during process cleanup: {e}")


# Global recipe controller instance
recipe_controller = RecipeController()