"""
Process execution and monitoring models.
"""

from datetime import datetime, timezone
from enum import Enum
from typing import Optional
from uuid import uuid4, UUID

from sqlalchemy import Column, DateTime, Enum as SQLEnum, Float, ForeignKey, Integer, String, Text, Boolean, JSON
from sqlalchemy.dialects.postgresql import UUID as PostgresUUID
from sqlalchemy.orm import relationship
from sqlalchemy.types import TypeDecorator, CHAR

from ..database import Base
from .recipe import RecipePhase


class GUID(TypeDecorator):
    """Platform-independent GUID type."""
    impl = CHAR
    cache_ok = True

    def load_dialect_impl(self, dialect):
        if dialect.name == 'postgresql':
            return dialect.type_descriptor(PostgresUUID())
        else:
            return dialect.type_descriptor(CHAR(36))

    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        elif dialect.name == 'postgresql':
            return str(value)
        else:
            if not isinstance(value, UUID):
                return str(UUID(value))
            else:
                return str(value)

    def process_result_value(self, value, dialect):
        if value is None:
            return value
        else:
            if not isinstance(value, UUID):
                return UUID(value)
            return value


class ProcessStatus(str, Enum):
    """Process execution status."""
    IDLE = "idle"
    STARTING = "starting"
    RUNNING = "running"
    PAUSED = "paused"
    STOPPING = "stopping"
    COMPLETED = "completed"
    ABORTED = "aborted"
    ERROR = "error"


class ProcessSession(Base):
    """
    Process execution session tracking.
    Records complete tempering process runs.
    """
    __tablename__ = "process_sessions"

    # Primary identification
    id = Column(GUID(), primary_key=True, default=uuid4, unique=True, nullable=False)
    session_name = Column(String(100), nullable=True, comment="Human-readable session name")
    
    # Recipe association
    recipe_id = Column(Integer, ForeignKey("recipes.id"), nullable=False, index=True)
    recipe = relationship("Recipe", back_populates="process_sessions")
    
    # Process state
    status = Column(SQLEnum(ProcessStatus), nullable=False, default=ProcessStatus.IDLE, index=True)
    current_phase = Column(SQLEnum(RecipePhase), nullable=True, index=True)
    phase_start_time = Column(DateTime(timezone=True), nullable=True)
    
    # Timing information
    started_at = Column(DateTime(timezone=True), nullable=True)
    completed_at = Column(DateTime(timezone=True), nullable=True)
    paused_duration = Column(Integer, nullable=False, default=0, comment="Total paused time in seconds")
    
    # Process parameters (snapshot of recipe at execution time)
    process_parameters = Column(JSON, nullable=True, comment="Recipe parameters when process started")
    
    # Results and metrics
    target_heating_temp = Column(Float, nullable=True, comment="Target heating temperature (°C)")
    target_cooling_temp = Column(Float, nullable=True, comment="Target cooling temperature (°C)")
    achieved_heating_temp = Column(Float, nullable=True, comment="Actual achieved heating temperature (°C)")
    achieved_cooling_temp = Column(Float, nullable=True, comment="Actual achieved cooling temperature (°C)")
    
    # Quality metrics
    temperature_accuracy = Column(Float, nullable=True, comment="Average temperature accuracy (°C)")
    cycle_efficiency = Column(Float, nullable=True, comment="Process efficiency percentage")
    energy_consumption = Column(Float, nullable=True, comment="Total energy consumption (kWh)")
    
    # Error and stop information
    stop_reason = Column(String(200), nullable=True, comment="Reason for process stop/abort")
    error_count = Column(Integer, nullable=False, default=0, comment="Number of errors during process")
    warning_count = Column(Integer, nullable=False, default=0, comment="Number of warnings during process")
    
    # User information
    started_by = Column(String(50), nullable=True, comment="User who started the process")
    stopped_by = Column(String(50), nullable=True, comment="User who stopped the process")
    
    # Relationships
    temperature_readings = relationship("TemperatureReading", back_populates="session")
    process_logs = relationship("ProcessLog", back_populates="session")

    def __repr__(self) -> str:
        return f"<ProcessSession(id={self.id}, recipe_id={self.recipe_id}, status='{self.status}', phase='{self.current_phase}')>"
    
    @property
    def duration(self) -> Optional[int]:
        """Get process duration in seconds."""
        if self.started_at is None:
            return None
        
        end_time = self.completed_at or datetime.now(timezone.utc)
        duration = (end_time - self.started_at).total_seconds()
        return int(duration - self.paused_duration)
    
    @property
    def is_active(self) -> bool:
        """Check if process is currently active."""
        return self.status in [ProcessStatus.STARTING, ProcessStatus.RUNNING, ProcessStatus.PAUSED]
    
    def get_phase_duration(self) -> Optional[int]:
        """Get current phase duration in seconds."""
        if self.phase_start_time is None:
            return None
        return int((datetime.now(timezone.utc) - self.phase_start_time).total_seconds())


class LogLevel(str, Enum):
    """Log message severity levels."""
    DEBUG = "debug"
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


class ProcessLog(Base):
    """
    Process execution log entries.
    Records all significant events during process execution.
    """
    __tablename__ = "process_logs"

    id = Column(Integer, primary_key=True, index=True)
    
    # Session association
    session_id = Column(GUID(), ForeignKey("process_sessions.id"), nullable=False, index=True)
    session = relationship("ProcessSession", back_populates="process_logs")
    
    # Log entry details
    timestamp = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), index=True)
    level = Column(SQLEnum(LogLevel), nullable=False, default=LogLevel.INFO, index=True)
    message = Column(Text, nullable=False, comment="Log message")
    component = Column(String(50), nullable=True, comment="System component that generated the log")
    
    # Process context
    phase = Column(SQLEnum(RecipePhase), nullable=True, comment="Recipe phase when log was generated")
    temperature_tank = Column(Float, nullable=True, comment="Tank temperature at time of log (°C)")
    temperature_fountain = Column(Float, nullable=True, comment="Fountain temperature at time of log (°C)")
    
    # Additional data
    additional_data = Column(JSON, nullable=True, comment="Additional structured data")

    def __repr__(self) -> str:
        return f"<ProcessLog(id={self.id}, session_id={self.session_id}, level='{self.level}', timestamp={self.timestamp})>"


class TemperatureReading(Base):
    """
    Temperature sensor readings during process execution.
    High-frequency data collection for monitoring and analysis.
    """
    __tablename__ = "temperature_readings"

    id = Column(Integer, primary_key=True, index=True)
    
    # Session association
    session_id = Column(GUID(), ForeignKey("process_sessions.id"), nullable=False, index=True)
    session = relationship("ProcessSession", back_populates="temperature_readings")
    
    # Timing
    timestamp = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), index=True)
    phase = Column(SQLEnum(RecipePhase), nullable=True, index=True)
    
    # Temperature readings (°C)
    tank_bottom = Column(Float, nullable=True, comment="Tank bottom temperature sensor (°C)")
    tank_wall = Column(Float, nullable=True, comment="Tank wall temperature sensor (°C)")  
    pump = Column(Float, nullable=True, comment="Pump temperature sensor (°C)")
    fountain = Column(Float, nullable=True, comment="Fountain temperature sensor (°C)")
    
    # Ambient conditions
    ambient_temp = Column(Float, nullable=True, comment="Ambient temperature (°C)")
    humidity = Column(Float, nullable=True, comment="Ambient humidity (%)")
    
    # Process targets for comparison
    target_temperature = Column(Float, nullable=True, comment="Target temperature for current phase (°C)")
    temperature_error = Column(Float, nullable=True, comment="Temperature error from target (°C)")
    
    # Control outputs
    heating_output = Column(Float, nullable=True, comment="Heating control output (%)")
    cooling_output = Column(Float, nullable=True, comment="Cooling control output (%)")

    def __repr__(self) -> str:
        return f"<TemperatureReading(id={self.id}, session_id={self.session_id}, timestamp={self.timestamp})>"
    
    @property
    def average_tank_temp(self) -> Optional[float]:
        """Calculate average tank temperature from available sensors."""
        temps = [t for t in [self.tank_bottom, self.tank_wall] if t is not None]
        return sum(temps) / len(temps) if temps else None
    
    def get_temperature_by_zone(self, zone: str) -> Optional[float]:
        """Get temperature reading for a specific zone."""
        zone_map = {
            "tank_bottom": self.tank_bottom,
            "tank_wall": self.tank_wall,
            "pump": self.pump,
            "fountain": self.fountain,
            "ambient": self.ambient_temp,
        }
        return zone_map.get(zone.lower())