"""
Configuration management for MechanicsDSL
"""

import logging
from typing import Any, Dict, Tuple

from .logging import logger

# ============================================================================
# PHYSICAL CONSTANTS
# ============================================================================

# Standard gravity (m/s²)
STANDARD_GRAVITY = 9.80665
DEFAULT_GRAVITY = 9.81

# Speed of light (m/s)
SPEED_OF_LIGHT = 299792458.0

# Planck constant (J·s)
PLANCK_CONSTANT = 6.62607015e-34
HBAR = 1.054571817e-34

# ============================================================================
# NUMERICAL CONSTANTS
# ============================================================================

# Default tolerances for numerical integration
DEFAULT_RTOL = 1e-6
DEFAULT_ATOL = 1e-8

# Energy conservation tolerance (relative)
ENERGY_TOLERANCE = 0.01

# Default number of simulation points
DEFAULT_NUM_POINTS = 1000

# Default simulation time step hints
DEFAULT_MAX_STEP_FRACTION = 0.01  # Max step as fraction of time span
DEFAULT_STIFFNESS_TEST_DURATION = 0.01

# Simplification timeout (seconds)
SIMPLIFICATION_TIMEOUT = 5.0

# Maximum parser errors before aborting
MAX_PARSER_ERRORS = 10

# Maximum number of iterations for solvers
MAX_SOLVER_ITERATIONS = 1000

# Infinity approximation for numerical comparisons
NUMERICAL_INFINITY = 1e10

# Near-zero threshold for singularity detection
SINGULARITY_THRESHOLD = 1e-12

# ============================================================================
# VISUALIZATION CONSTANTS
# ============================================================================

# Animation settings
DEFAULT_TRAIL_LENGTH = 150
DEFAULT_FPS = 30
ANIMATION_INTERVAL_MS = 33  # ~30 FPS
DEFAULT_DPI = 100
MIN_DPI = 10
MAX_DPI = 1000

# Trail appearance
TRAIL_ALPHA = 0.4

# Default color scheme (professional palette)
PRIMARY_COLOR = "#E63946"  # Red-orange
SECONDARY_COLOR = "#457B9D"  # Steel blue
TERTIARY_COLOR = "#F1FAEE"  # Off-white
ACCENT_COLOR = "#A8DADC"  # Light cyan
WARNING_COLOR = "#FFB703"  # Amber
SUCCESS_COLOR = "#2A9D8F"  # Teal

# Marker sizes
DEFAULT_MARKER_SIZE = 10
LARGE_MARKER_SIZE = 100

# ============================================================================
# FILE VALIDATION CONSTANTS
# ============================================================================

# Maximum file path length
MAX_PATH_LENGTH = 4096

# Maximum file size for loading (bytes) - 100MB
MAX_FILE_SIZE = 100 * 1024 * 1024

# ============================================================================
# CACHE CONSTANTS
# ============================================================================

# Default LRU cache settings
DEFAULT_CACHE_SIZE = 128
DEFAULT_CACHE_MEMORY_MB = 100.0

# ============================================================================
# LOGGING CONSTANTS
# ============================================================================

# Log level mappings
LOG_LEVELS = {
    "DEBUG": logging.DEBUG,
    "INFO": logging.INFO,
    "WARNING": logging.WARNING,
    "ERROR": logging.ERROR,
    "CRITICAL": logging.CRITICAL,
}


class Config:
    """
    Global configuration for MechanicsDSL with validation.

    All configuration values are validated on assignment to ensure
    they are within reasonable bounds and of correct types.
    """

    def __init__(self) -> None:
        """Initialize configuration with default values."""
        self._enable_profiling: bool = False
        self._enable_debug_logging: bool = False
        self._simplification_timeout: float = SIMPLIFICATION_TIMEOUT
        self._max_parser_errors: int = MAX_PARSER_ERRORS
        self._default_rtol: float = DEFAULT_RTOL
        self._default_atol: float = DEFAULT_ATOL
        self._trail_length: int = DEFAULT_TRAIL_LENGTH
        self._animation_fps: int = DEFAULT_FPS
        self._save_intermediate_results: bool = False
        self._cache_symbolic_results: bool = True
        # v6.0 Advanced features
        self._enable_performance_monitoring: bool = True
        self._cache_max_size: int = 256
        self._cache_max_memory_mb: float = 200.0
        self._enable_adaptive_solver: bool = True
        self._enable_parallel_processing: bool = False
        self._max_workers: int = 4
        self._enable_memory_monitoring: bool = True
        self._gc_threshold: Tuple[int, int, int] = (700, 10, 10)
        self._enable_type_checking: bool = True
        self._error_recovery_enabled: bool = True
        self._max_retry_attempts: int = 3

    @property
    def enable_profiling(self) -> bool:
        """Whether to enable performance profiling."""
        return self._enable_profiling

    @enable_profiling.setter
    def enable_profiling(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"enable_profiling must be bool, got {type(value).__name__}")
        self._enable_profiling = value

    @property
    def enable_debug_logging(self) -> bool:
        """Whether to enable debug-level logging."""
        return self._enable_debug_logging

    @enable_debug_logging.setter
    def enable_debug_logging(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"enable_debug_logging must be bool, got {type(value).__name__}")
        self._enable_debug_logging = value

    @property
    def simplification_timeout(self) -> float:
        """Timeout for symbolic simplification operations in seconds."""
        return self._simplification_timeout

    @simplification_timeout.setter
    def simplification_timeout(self, value: float) -> None:
        if not isinstance(value, (int, float)):
            raise TypeError(f"simplification_timeout must be numeric, got {type(value).__name__}")
        if value < 0:
            raise ValueError(f"simplification_timeout must be non-negative, got {value}")
        if value > 3600:
            raise ValueError(f"simplification_timeout too large (>{3600}s), got {value}")
        self._simplification_timeout = float(value)

    @property
    def max_parser_errors(self) -> int:
        """Maximum parser errors before giving up."""
        return self._max_parser_errors

    @max_parser_errors.setter
    def max_parser_errors(self, value: int) -> None:
        if not isinstance(value, int):
            raise TypeError(f"max_parser_errors must be int, got {type(value).__name__}")
        if value < 1:
            raise ValueError(f"max_parser_errors must be at least 1, got {value}")
        if value > 1000:
            raise ValueError(f"max_parser_errors too large (>{1000}), got {value}")
        self._max_parser_errors = value

    @property
    def default_rtol(self) -> float:
        """Default relative tolerance for numerical integration."""
        return self._default_rtol

    @default_rtol.setter
    def default_rtol(self, value: float) -> None:
        if not isinstance(value, (int, float)):
            raise TypeError(f"default_rtol must be numeric, got {type(value).__name__}")
        if value <= 0 or value >= 1:
            raise ValueError(f"default_rtol must be in (0, 1), got {value}")
        self._default_rtol = float(value)

    @property
    def default_atol(self) -> float:
        """Default absolute tolerance for numerical integration."""
        return self._default_atol

    @default_atol.setter
    def default_atol(self, value: float) -> None:
        if not isinstance(value, (int, float)):
            raise TypeError(f"default_atol must be numeric, got {type(value).__name__}")
        if value <= 0:
            raise ValueError(f"default_atol must be positive, got {value}")
        self._default_atol = float(value)

    @property
    def trail_length(self) -> int:
        """Maximum length of animation trails."""
        return self._trail_length

    @trail_length.setter
    def trail_length(self, value: int) -> None:
        if not isinstance(value, int):
            raise TypeError(f"trail_length must be int, got {type(value).__name__}")
        if value < 0:
            raise ValueError(f"trail_length must be non-negative, got {value}")
        if value > 100000:
            raise ValueError(f"trail_length too large (>{100000}), got {value}")
        self._trail_length = value

    @property
    def animation_fps(self) -> int:
        """Animation frames per second."""
        return self._animation_fps

    @animation_fps.setter
    def animation_fps(self, value: int) -> None:
        if not isinstance(value, int):
            raise TypeError(f"animation_fps must be int, got {type(value).__name__}")
        if value < 1:
            raise ValueError(f"animation_fps must be at least 1, got {value}")
        if value > 120:
            raise ValueError(f"animation_fps too large (>{120}), got {value}")
        self._animation_fps = value

    @property
    def save_intermediate_results(self) -> bool:
        """Whether to save intermediate computation results."""
        return self._save_intermediate_results

    @save_intermediate_results.setter
    def save_intermediate_results(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"save_intermediate_results must be bool, got {type(value).__name__}")
        self._save_intermediate_results = value

    @property
    def cache_symbolic_results(self) -> bool:
        """Whether to cache symbolic computation results."""
        return self._cache_symbolic_results

    @cache_symbolic_results.setter
    def cache_symbolic_results(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"cache_symbolic_results must be bool, got {type(value).__name__}")
        self._cache_symbolic_results = value

    @property
    def enable_performance_monitoring(self) -> bool:
        """Whether to enable performance monitoring."""
        return self._enable_performance_monitoring

    @enable_performance_monitoring.setter
    def enable_performance_monitoring(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(
                f"enable_performance_monitoring must be bool, got {type(value).__name__}"
            )
        self._enable_performance_monitoring = value

    @property
    def enable_memory_monitoring(self) -> bool:
        """Whether to enable additional memory monitoring."""
        return self._enable_memory_monitoring

    @enable_memory_monitoring.setter
    def enable_memory_monitoring(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"enable_memory_monitoring must be bool, got {type(value).__name__}")
        self._enable_memory_monitoring = value

    @property
    def cache_max_size(self) -> int:
        """Maximum cache size."""
        return self._cache_max_size

    @cache_max_size.setter
    def cache_max_size(self, value: int) -> None:
        if not isinstance(value, int):
            raise TypeError(f"cache_max_size must be int, got {type(value).__name__}")
        if value < 1:
            raise ValueError(f"cache_max_size must be at least 1, got {value}")
        if value > 10000:
            raise ValueError(f"cache_max_size too large (>{10000}), got {value}")
        self._cache_max_size = value

    @property
    def cache_max_memory_mb(self) -> float:
        """Maximum cache memory in MB."""
        return self._cache_max_memory_mb

    @cache_max_memory_mb.setter
    def cache_max_memory_mb(self, value: float) -> None:
        if not isinstance(value, (int, float)):
            raise TypeError(f"cache_max_memory_mb must be numeric, got {type(value).__name__}")
        if value <= 0:
            raise ValueError(f"cache_max_memory_mb must be positive, got {value}")
        if value > 10000:
            raise ValueError(f"cache_max_memory_mb too large (>{10000} MB), got {value}")
        self._cache_max_memory_mb = float(value)

    @property
    def enable_adaptive_solver(self) -> bool:
        """Whether to enable adaptive solver selection."""
        return self._enable_adaptive_solver

    @enable_adaptive_solver.setter
    def enable_adaptive_solver(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"enable_adaptive_solver must be bool, got {type(value).__name__}")
        self._enable_adaptive_solver = value

    @property
    def error_recovery_enabled(self) -> bool:
        """Whether error recovery is enabled."""
        return self._error_recovery_enabled

    @error_recovery_enabled.setter
    def error_recovery_enabled(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError(f"error_recovery_enabled must be bool, got {type(value).__name__}")
        self._error_recovery_enabled = value

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert configuration to dictionary.

        Returns:
            Dictionary containing all configuration values
        """
        return {
            # Core settings
            "enable_profiling": self._enable_profiling,
            "enable_debug_logging": self._enable_debug_logging,
            "simplification_timeout": self._simplification_timeout,
            "max_parser_errors": self._max_parser_errors,
            "default_rtol": self._default_rtol,
            "default_atol": self._default_atol,
            "trail_length": self._trail_length,
            "animation_fps": self._animation_fps,
            "save_intermediate_results": self._save_intermediate_results,
            "cache_symbolic_results": self._cache_symbolic_results,
            # v6.0 Advanced features
            "enable_performance_monitoring": self._enable_performance_monitoring,
            "cache_max_size": self._cache_max_size,
            "cache_max_memory_mb": self._cache_max_memory_mb,
            "enable_adaptive_solver": self._enable_adaptive_solver,
            "enable_parallel_processing": self._enable_parallel_processing,
            "max_workers": self._max_workers,
            "enable_memory_monitoring": self._enable_memory_monitoring,
            "gc_threshold": self._gc_threshold,
            "enable_type_checking": self._enable_type_checking,
            "error_recovery_enabled": self._error_recovery_enabled,
            "max_retry_attempts": self._max_retry_attempts,
        }

    def from_dict(self, data: Dict[str, Any]) -> None:
        """
        Load configuration from dictionary with validation.

        Uses property setters to ensure all values are validated.

        Args:
            data: Dictionary containing configuration values

        Raises:
            TypeError: If data is not a dictionary
            ValueError: If any value is invalid
        """
        if not isinstance(data, dict):
            raise TypeError(f"data must be dict, got {type(data).__name__}")

        # Map property names (some internal names differ from property names)
        property_mapping = {
            "gc_threshold": "_gc_threshold",
            "max_workers": "_max_workers",
            "enable_parallel_processing": "_enable_parallel_processing",
            "enable_type_checking": "_enable_type_checking",
            "max_retry_attempts": "_max_retry_attempts",
        }

        for key, value in data.items():
            # Try to use property setter first (provides validation)
            if hasattr(self.__class__, key) and isinstance(
                getattr(self.__class__, key, None), property
            ):
                try:
                    setattr(self, key, value)
                except (TypeError, ValueError) as e:
                    logger.warning(f"Invalid value for config key '{key}': {e}")
            # Fallback to direct attribute setting for internal-only fields
            elif key in property_mapping:
                internal_name = property_mapping[key]
                setattr(self, internal_name, value)
            elif key.startswith("_") and hasattr(self, key):
                setattr(self, key, value)
            else:
                logger.warning(f"Unknown configuration key: {key}")


# Global config instance
config = Config()
