"""
System configuration, error logging, and backup models.
"""

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

from sqlalchemy import Column, DateTime, Enum as SQLEnum, Float, Integer, String, Text, Boolean, JSON, LargeBinary

from ..database import Base


class ErrorSeverity(str, Enum):
    """Error severity levels."""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class ErrorCategory(str, Enum):
    """Error categories for classification."""
    HARDWARE = "hardware"
    COMMUNICATION = "communication"
    TEMPERATURE = "temperature"
    SAFETY = "safety"
    POWER = "power"
    PROCESS = "process"
    USER = "user"
    SYSTEM = "system"


class ErrorLog(Base):
    """
    System error and event logging.
    Captures all errors, warnings, and significant events.
    """
    __tablename__ = "error_logs"

    id = Column(Integer, primary_key=True, index=True)
    
    # Error identification
    error_code = Column(String(20), nullable=True, index=True, comment="Standardized error code")
    category = Column(SQLEnum(ErrorCategory), nullable=False, default=ErrorCategory.SYSTEM, index=True)
    severity = Column(SQLEnum(ErrorSeverity), nullable=False, default=ErrorSeverity.LOW, index=True)
    
    # Error details
    title = Column(String(200), nullable=False, comment="Error title/summary")
    message = Column(Text, nullable=False, comment="Detailed error message")
    component = Column(String(50), nullable=True, comment="System component where error occurred")
    
    # Context information
    timestamp = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), index=True)
    session_id = Column(String(36), nullable=True, comment="Associated process session ID")
    user_id = Column(Integer, nullable=True, comment="User who triggered the error (if applicable)")
    
    # Technical details
    stack_trace = Column(Text, nullable=True, comment="Error stack trace for debugging")
    additional_data = Column(JSON, nullable=True, comment="Additional diagnostic data")
    
    # Resolution tracking
    is_resolved = Column(Boolean, nullable=False, default=False, comment="Error has been resolved")
    resolution_notes = Column(Text, nullable=True, comment="Notes on how error was resolved")
    resolved_by = Column(String(50), nullable=True, comment="User who resolved the error")
    resolved_at = Column(DateTime(timezone=True), nullable=True, comment="When error was resolved")
    
    # Hardware/Process context
    temperature_tank = Column(Float, nullable=True, comment="Tank temperature when error occurred (°C)")
    temperature_fountain = Column(Float, nullable=True, comment="Fountain temperature when error occurred (°C)")
    current_phase = Column(String(20), nullable=True, comment="Process phase when error occurred")
    
    # Recurrence tracking
    occurrence_count = Column(Integer, nullable=False, default=1, comment="Number of times this error has occurred")
    first_occurrence = Column(DateTime(timezone=True), nullable=True, comment="When this error first occurred")
    last_occurrence = Column(DateTime(timezone=True), nullable=True, comment="When this error last occurred")

    def __repr__(self) -> str:
        return f"<ErrorLog(id={self.id}, category='{self.category}', severity='{self.severity}', title='{self.title}')>"
    
    def is_critical(self) -> bool:
        """Check if error is critical severity."""
        return self.severity == ErrorSeverity.CRITICAL
    
    def requires_immediate_attention(self) -> bool:
        """Check if error requires immediate operator attention."""
        return self.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL] and not self.is_resolved
    
    def mark_resolved(self, resolved_by: str, notes: Optional[str] = None) -> None:
        """Mark error as resolved."""
        self.is_resolved = True
        self.resolved_by = resolved_by
        self.resolved_at = datetime.now(timezone.utc)
        if notes:
            self.resolution_notes = notes


class SystemConfiguration(Base):
    """
    System-wide configuration parameters.
    Stores global settings and operational parameters.
    """
    __tablename__ = "system_configurations"

    id = Column(Integer, primary_key=True, index=True)
    
    # Configuration identification
    key = Column(String(100), nullable=False, unique=True, index=True, comment="Configuration parameter key")
    category = Column(String(50), nullable=False, index=True, comment="Configuration category")
    description = Column(Text, nullable=True, comment="Parameter description")
    
    # Value storage (polymorphic)
    value_string = Column(Text, nullable=True, comment="String value")
    value_number = Column(Float, nullable=True, comment="Numeric value")
    value_boolean = Column(Boolean, nullable=True, comment="Boolean value")
    value_json = Column(JSON, nullable=True, comment="JSON/object value")
    
    # Validation and constraints
    data_type = Column(String(20), nullable=False, default="string", comment="Expected data type")
    min_value = Column(Float, nullable=True, comment="Minimum allowed value (for numeric types)")
    max_value = Column(Float, nullable=True, comment="Maximum allowed value (for numeric types)")
    valid_options = Column(JSON, nullable=True, comment="Valid options for enum-type parameters")
    
    # Configuration metadata
    is_readonly = Column(Boolean, nullable=False, default=False, comment="Parameter cannot be modified via UI")
    requires_restart = Column(Boolean, nullable=False, default=False, comment="Change requires system restart")
    is_sensitive = Column(Boolean, nullable=False, default=False, comment="Parameter contains sensitive data")
    
    # Change tracking
    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))
    last_modified_by = Column(String(50), nullable=True, comment="User who last modified this parameter")
    
    # Default and factory values
    default_value = Column(Text, nullable=True, comment="Default/factory value")
    previous_value = Column(Text, nullable=True, comment="Previous value (for rollback)")

    def __repr__(self) -> str:
        return f"<SystemConfiguration(key='{self.key}', category='{self.category}', type='{self.data_type}')>"
    
    def get_value(self):
        """Get the configuration value with appropriate type conversion."""
        if self.data_type == "string":
            return self.value_string
        elif self.data_type == "number" or self.data_type == "float":
            return self.value_number
        elif self.data_type == "integer":
            return int(self.value_number) if self.value_number is not None else None
        elif self.data_type == "boolean":
            return self.value_boolean
        elif self.data_type == "json":
            return self.value_json
        else:
            return self.value_string
    
    def set_value(self, value, modified_by: Optional[str] = None):
        """Set the configuration value with type validation."""
        # Store previous value for rollback
        self.previous_value = str(self.get_value()) if self.get_value() is not None else None
        
        # Set new value based on data type
        if self.data_type == "string":
            self.value_string = str(value) if value is not None else None
        elif self.data_type in ["number", "float", "integer"]:
            if value is not None:
                self.value_number = float(value)
                # Validate range
                if self.min_value is not None and self.value_number < self.min_value:
                    raise ValueError(f"Value {value} is below minimum {self.min_value}")
                if self.max_value is not None and self.value_number > self.max_value:
                    raise ValueError(f"Value {value} is above maximum {self.max_value}")
        elif self.data_type == "boolean":
            self.value_boolean = bool(value) if value is not None else None
        elif self.data_type == "json":
            self.value_json = value
        
        # Update metadata
        self.updated_at = datetime.now(timezone.utc)
        self.last_modified_by = modified_by
    
    def validate_value(self, value) -> bool:
        """Validate a value against this configuration's constraints."""
        try:
            if self.data_type in ["number", "float", "integer"]:
                num_value = float(value)
                if self.min_value is not None and num_value < self.min_value:
                    return False
                if self.max_value is not None and num_value > self.max_value:
                    return False
            
            if self.valid_options and value not in self.valid_options:
                return False
            
            return True
        except (ValueError, TypeError):
            return False


class BackupStatus(str, Enum):
    """Backup operation status."""
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    COMPLETED = "completed"
    FAILED = "failed"
    CORRUPTED = "corrupted"


class Backup(Base):
    """
    System backup records.
    Tracks configuration and data backups.
    """
    __tablename__ = "backups"

    id = Column(Integer, primary_key=True, index=True)
    
    # Backup identification
    backup_name = Column(String(100), nullable=False, comment="Backup name/identifier")
    backup_type = Column(String(20), nullable=False, default="full", comment="Backup type (full, incremental, config)")
    description = Column(Text, nullable=True, comment="Backup description")
    
    # Backup metadata
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), index=True)
    created_by = Column(String(50), nullable=True, comment="User who initiated the backup")
    status = Column(SQLEnum(BackupStatus), nullable=False, default=BackupStatus.PENDING, index=True)
    
    # File information
    file_path = Column(String(500), nullable=True, comment="Path to backup file")
    file_size = Column(Integer, nullable=True, comment="Backup file size in bytes")
    checksum = Column(String(64), nullable=True, comment="MD5/SHA256 checksum for integrity")
    
    # Content information
    tables_included = Column(JSON, nullable=True, comment="List of database tables included")
    record_count = Column(Integer, nullable=True, comment="Total number of records backed up")
    
    # Compression and encryption
    is_compressed = Column(Boolean, nullable=False, default=True, comment="Backup is compressed")
    is_encrypted = Column(Boolean, nullable=False, default=False, comment="Backup is encrypted")
    compression_ratio = Column(Float, nullable=True, comment="Compression ratio achieved")
    
    # Backup operation metrics
    duration_seconds = Column(Integer, nullable=True, comment="Time taken to create backup")
    error_message = Column(Text, nullable=True, comment="Error message if backup failed")
    
    # Retention and cleanup
    expires_at = Column(DateTime(timezone=True), nullable=True, comment="When backup expires and can be deleted")
    is_archived = Column(Boolean, nullable=False, default=False, comment="Backup has been archived")
    
    # Restoration tracking
    last_restored = Column(DateTime(timezone=True), nullable=True, comment="When backup was last restored")
    restored_by = Column(String(50), nullable=True, comment="User who last restored from this backup")

    def __repr__(self) -> str:
        return f"<Backup(id={self.id}, name='{self.backup_name}', type='{self.backup_type}', status='{self.status}')>"
    
    def is_expired(self) -> bool:
        """Check if backup has expired."""
        if self.expires_at is None:
            return False
        return datetime.now(timezone.utc) > self.expires_at
    
    def is_restorable(self) -> bool:
        """Check if backup can be restored."""
        return (
            self.status == BackupStatus.COMPLETED
            and not self.is_expired()
            and self.file_path is not None
        )
    
    def calculate_size_mb(self) -> Optional[float]:
        """Get backup size in MB."""
        if self.file_size is None:
            return None
        return self.file_size / (1024 * 1024)
    
    def mark_completed(self, file_path: str, file_size: int, checksum: str) -> None:
        """Mark backup as completed with file information."""
        self.status = BackupStatus.COMPLETED
        self.file_path = file_path
        self.file_size = file_size
        self.checksum = checksum
    
    def mark_failed(self, error_message: str) -> None:
        """Mark backup as failed with error message."""
        self.status = BackupStatus.FAILED
        self.error_message = error_message