"""High DPI Support Module for Pytola Applications.

Simple and automatic high DPI scaling for Qt applications.
Just call enable_highdpi_scaling() once and everything will be scaled automatically.

Optimized Design Features:
- Singleton DPI state management
- Lazy initialization with caching
- Configurable scaling policies
- Automatic fallback handling
- Performance-optimized calculations
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from enum import Enum
from functools import cached_property
from typing import TYPE_CHECKING, ClassVar, Optional

from typing_extensions import Self

from .logger import logger

if TYPE_CHECKING:
    from PySide2.QtGui import QFont
    from PySide2.QtWidgets import QApplication

__all__ = [
    "HighDPIManager",
    "ScalingPolicy",
    "enable_highdpi_scaling",
    "get_scaling_factors",
]

# Qt availability check
try:
    from PySide2.QtGui import QFont
    from PySide2.QtWidgets import QApplication

    QT_AVAILABLE = True
except ImportError:
    QT_AVAILABLE = False
    logger.warning("PySide2 is not available, high DPI scaling is disabled.")

# Constants
STANDARD_DPI: float = 96.0
HIGH_DPI_THRESHOLD: float = 1.25
DEFAULT_FONT_SCALE: float = 1.5


class ScalingPolicy(Enum):
    """DPI scaling policy options."""

    AUTO = "auto"  # Automatic detection and scaling
    FORCE = "force"  # Force scaling regardless of system DPI
    DISABLED = "disabled"  # No scaling applied
    ENHANCED = "enhanced"  # Enhanced scaling with user preferences


@dataclass
class DPIConfiguration:
    """DPI scaling configuration settings."""

    user_font_scale: float = DEFAULT_FONT_SCALE
    scaling_policy: ScalingPolicy = ScalingPolicy.AUTO
    min_scale_factor: float = 1.0
    max_scale_factor: float = 3.0
    enable_auto_font_scaling: bool = True
    enable_widget_scaling: bool = True


class _DPIState:
    """Optimized DPI state management with singleton pattern."""

    _instance: ClassVar[Optional["_DPIState"]] = None
    _initialized: bool = False

    def __new__(cls) -> Self:
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self) -> None:
        if self._initialized:
            return

        self.config: DPIConfiguration = DPIConfiguration()
        self._scale_factor: float = 1.0
        self._is_high_dpi: bool = False
        self._screen_dpi: float = STANDARD_DPI
        self._qt_initialized: bool = False

        self._initialized = True

    @property
    def scale_factor(self) -> float:
        """Get current scale factor with lazy initialization."""
        if not self._qt_initialized:
            self._initialize_qt()
        return self._scale_factor

    @property
    def is_high_dpi(self) -> bool:
        """Check if system is high DPI."""
        if not self._qt_initialized:
            self._initialize_qt()
        return self._is_high_dpi

    @property
    def screen_dpi(self) -> float:
        """Get screen DPI value."""
        if not self._qt_initialized:
            self._initialize_qt()
        return self._screen_dpi

    def _initialize_qt(self) -> None:
        """Initialize Qt-related DPI detection."""
        if not QT_AVAILABLE or self._qt_initialized:
            self._qt_initialized = True
            return

        try:
            app = QApplication.instance()
            if not app:
                logger.debug("No Qt application instance found")
                self._qt_initialized = True
                return

            screen = app.primaryScreen()
            if screen and hasattr(screen, "logicalDotsPerInch"):
                self._screen_dpi = screen.logicalDotsPerInch()
                self._scale_factor = max(
                    self.config.min_scale_factor,
                    min(self.config.max_scale_factor, self._screen_dpi / STANDARD_DPI),
                )
                self._is_high_dpi = self._scale_factor > HIGH_DPI_THRESHOLD
            else:
                # Fallback to environment variables
                self._detect_from_environment()

        except Exception as e:
            logger.warning(f"Failed to initialize Qt DPI detection: {e}")
            self._detect_from_environment()

        self._qt_initialized = True
        logger.debug(f"DPI initialized - Scale: {self._scale_factor:.2f}, HighDPI: {self._is_high_dpi}")

    def _detect_from_environment(self) -> None:
        """Detect DPI from environment variables as fallback."""
        try:
            # Check common environment variables
            dpi_env = os.environ.get("QT_FONT_DPI") or os.environ.get("GDK_DPI_SCALE")
            if dpi_env:
                env_dpi = float(dpi_env)
                self._screen_dpi = env_dpi
                self._scale_factor = env_dpi / STANDARD_DPI
                self._is_high_dpi = self._scale_factor > HIGH_DPI_THRESHOLD
            else:
                # Default to standard DPI
                self._scale_factor = 1.0
                self._is_high_dpi = False

        except (ValueError, TypeError):
            self._scale_factor = 1.0
            self._is_high_dpi = False

    def update_configuration(self, config: DPIConfiguration) -> None:
        """Update DPI configuration and recalculate values."""
        self.config = config
        self._qt_initialized = False  # Force reinitialization
        self._initialize_qt()

    @cached_property
    def qt_app(self) -> Optional[QApplication]:
        """Get Qt application instance."""
        return QApplication.instance() if QT_AVAILABLE else None

    def scale_value(self, value: float, min_value: float = 1.0) -> float:
        """Scale a value based on current DPI settings."""
        if self.config.scaling_policy == ScalingPolicy.DISABLED:
            return max(min_value, value)

        base_scale = self.scale_factor
        if self.config.scaling_policy == ScalingPolicy.FORCE:
            base_scale = max(base_scale, HIGH_DPI_THRESHOLD)
        elif self.config.scaling_policy == ScalingPolicy.ENHANCED:
            base_scale = base_scale * self.config.user_font_scale

        return max(min_value, value * base_scale)


class UIScalingFactors:
    """Predefined UI scaling factors with automatic DPI adjustment.

    Optimized for performance with caching and lazy evaluation.
    """

    # Base values - Consistent base sizes for all systems
    BASE_SMALL_FONT: ClassVar[int] = 12
    BASE_NORMAL_FONT: ClassVar[int] = 14
    BASE_LARGE_FONT: ClassVar[int] = 16
    BASE_TITLE_FONT: ClassVar[int] = 18
    BASE_HEADING_FONT: ClassVar[int] = 24

    # Spacing constants
    BASE_TIGHT_SPACING: ClassVar[int] = 4
    BASE_NORMAL_SPACING: ClassVar[int] = 8
    BASE_WIDE_SPACING: ClassVar[int] = 12
    BASE_EXTRA_WIDE_SPACING: ClassVar[int] = 16

    # Dimension constants
    BASE_BUTTON_HEIGHT: ClassVar[int] = 36
    BASE_INPUT_HEIGHT: ClassVar[int] = 32
    BASE_MIN_CONTROL_HEIGHT: ClassVar[int] = 24
    BASE_SCROLLBAR_WIDTH: ClassVar[int] = 12

    # Border and padding constants
    BASE_BORDER_WIDTH: ClassVar[int] = 1
    BASE_BORDER_RADIUS: ClassVar[int] = 4
    BASE_PADDING_SMALL: ClassVar[int] = 4
    BASE_PADDING_NORMAL: ClassVar[int] = 8
    BASE_PADDING_LARGE: ClassVar[int] = 12

    def __init__(self, dpi_state: _DPIState) -> None:
        self.dpi_state = dpi_state
        self._cache: dict = {}
        self._apply_scaling()

    def _apply_scaling(self) -> None:
        """Apply DPI scaling with optimized calculations."""
        # Use cached scaling calculations
        scale_func = self.dpi_state.scale_value

        # Font sizes
        self.SMALL_FONT = int(scale_func(self.BASE_SMALL_FONT, 8))
        self.NORMAL_FONT = int(scale_func(self.BASE_NORMAL_FONT, 9))
        self.LARGE_FONT = int(scale_func(self.BASE_LARGE_FONT, 10))
        self.TITLE_FONT = int(scale_func(self.BASE_TITLE_FONT, 12))
        self.HEADING_FONT = int(scale_func(self.BASE_HEADING_FONT, 14))

        # Apply user font scale if enabled
        if self.dpi_state.config.enable_auto_font_scaling:
            font_scale = self.dpi_state.config.user_font_scale
            self.SMALL_FONT = int(self.SMALL_FONT * font_scale)
            self.NORMAL_FONT = int(self.NORMAL_FONT * font_scale)
            self.LARGE_FONT = int(self.LARGE_FONT * font_scale)
            self.TITLE_FONT = int(self.TITLE_FONT * font_scale)
            self.HEADING_FONT = int(self.HEADING_FONT * font_scale)

        # Spacing
        self.TIGHT_SPACING = int(scale_func(self.BASE_TIGHT_SPACING, 2))
        self.NORMAL_SPACING = int(scale_func(self.BASE_NORMAL_SPACING, 4))
        self.WIDE_SPACING = int(scale_func(self.BASE_WIDE_SPACING, 6))
        self.EXTRA_WIDE_SPACING = int(scale_func(self.BASE_EXTRA_WIDE_SPACING, 8))

        # Dimensions
        self.BUTTON_HEIGHT = int(scale_func(self.BASE_BUTTON_HEIGHT, 24))
        self.INPUT_HEIGHT = int(scale_func(self.BASE_INPUT_HEIGHT, 20))
        self.MIN_CONTROL_HEIGHT = int(scale_func(self.BASE_MIN_CONTROL_HEIGHT, 16))
        self.SCROLLBAR_WIDTH = int(scale_func(self.BASE_SCROLLBAR_WIDTH, 8))

        # Border and padding
        self.BORDER_WIDTH = int(scale_func(self.BASE_BORDER_WIDTH, 1))
        self.BORDER_RADIUS = int(scale_func(self.BASE_BORDER_RADIUS, 2))
        self.PADDING_SMALL = int(scale_func(self.BASE_PADDING_SMALL, 2))
        self.PADDING_NORMAL = int(scale_func(self.BASE_PADDING_NORMAL, 4))
        self.PADDING_LARGE = int(scale_func(self.BASE_PADDING_LARGE, 6))

    def get_font(self, size: Optional[int] = None) -> Optional[QFont]:
        """Get scaled QFont instance."""
        if not QT_AVAILABLE or not self.dpi_state.qt_app:
            return None

        try:
            font = QFont()
            font_size = size or self.NORMAL_FONT
            font.setPixelSize(font_size)
            return font
        except Exception as e:
            logger.warning(f"Failed to create scaled font: {e}")
            return None

    def apply_to_application(self) -> bool:
        """Apply scaling to the entire Qt application."""
        if not QT_AVAILABLE or not self.dpi_state.qt_app:
            return False

        try:
            # Apply default font
            default_font = self.get_font()
            if default_font:
                self.dpi_state.qt_app.setFont(default_font)

            # Store scaling factors for later use
            self.dpi_state.qt_app.setProperty("_high_dpi_factors", self)
            return True

        except Exception as e:
            logger.warning(f"Failed to apply DPI scaling to application: {e}")
            return False

    @classmethod
    def get_instance(cls) -> "UIScalingFactors":
        """Get singleton instance of scaling factors."""
        dpi_state = _DPIState()
        return cls(dpi_state)


class HighDPIManager:
    """High-level DPI management interface."""

    def __init__(self) -> None:
        self.dpi_state = _DPIState()
        self.scaling_factors = UIScalingFactors(self.dpi_state)

    def configure(
        self,
        user_font_scale: float = DEFAULT_FONT_SCALE,
        scaling_policy: ScalingPolicy = ScalingPolicy.AUTO,
        enable_auto_font_scaling: bool = True,
        enable_widget_scaling: bool = True,
    ) -> None:
        """Configure DPI scaling settings."""
        config = DPIConfiguration(
            user_font_scale=user_font_scale,
            scaling_policy=scaling_policy,
            enable_auto_font_scaling=enable_auto_font_scaling,
            enable_widget_scaling=enable_widget_scaling,
        )
        self.dpi_state.update_configuration(config)
        self.scaling_factors = UIScalingFactors(self.dpi_state)

    def apply_scaling(self, auto_apply: bool = True) -> UIScalingFactors:
        """Apply DPI scaling and return scaling factors."""
        if auto_apply:
            self.scaling_factors.apply_to_application()
        return self.scaling_factors

    @property
    def is_high_dpi_system(self) -> bool:
        """Check if current system is high DPI."""
        return self.dpi_state.is_high_dpi

    @property
    def scale_factor(self) -> float:
        """Get current scale factor."""
        return self.dpi_state.scale_factor


# Global manager instance
_high_dpi_manager: Optional[HighDPIManager] = None


def _get_manager() -> HighDPIManager:
    """Get global DPI manager instance."""
    global _high_dpi_manager
    if _high_dpi_manager is None:
        _high_dpi_manager = HighDPIManager()
    return _high_dpi_manager


def enable_highdpi_scaling(
    user_font_scale: float = DEFAULT_FONT_SCALE,
    *,
    scaling_policy: ScalingPolicy = ScalingPolicy.AUTO,
    auto_apply: bool = True,
    enable_auto_font_scaling: bool = True,
    enable_widget_scaling: bool = True,
) -> UIScalingFactors:
    """Apply automatic high DPI scaling with optimized design.

    Args:
        user_font_scale: Additional font scaling factor for user preferences
        scaling_policy: DPI scaling policy to use
        auto_apply: Whether to automatically apply scaling to application
        enable_auto_font_scaling: Enable automatic font scaling
        enable_widget_scaling: Enable widget dimension scaling

    Returns
    -------
        UIScalingFactors: Class with appropriately scaled UI constants
    """
    manager = _get_manager()
    manager.configure(
        user_font_scale=user_font_scale,
        scaling_policy=scaling_policy,
        enable_auto_font_scaling=enable_auto_font_scaling,
        enable_widget_scaling=enable_widget_scaling,
    )
    return manager.apply_scaling(auto_apply)


def get_scaling_factors() -> UIScalingFactors:
    """Get current scaling factors instance."""
    return _get_manager().scaling_factors


def is_high_dpi_system() -> bool:
    """Check if current system is high DPI."""
    return _get_manager().is_high_dpi_system


def get_scale_factor() -> float:
    """Get current DPI scale factor."""
    return _get_manager().scale_factor
