from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from weakref import WeakSet

from PySide6.QtCore import QObject, Qt, Signal
from PySide6.QtGui import QColor, QGuiApplication, QPalette, QTextCharFormat
from PySide6.QtWidgets import QApplication, QCalendarWidget, QWidget


class Theme(Enum):
    SYSTEM = "system"
    LIGHT = "light"
    DARK = "dark"
    ORANGE_ANCHOR = "#FFA500"
    ORANGE_ANCHOR_VISITED = "#B38000"


@dataclass
class ThemeConfig:
    theme: Theme = Theme.SYSTEM


class ThemeManager(QObject):
    themeChanged = Signal(Theme)

    def __init__(self, app: QApplication, cfg: ThemeConfig):
        super().__init__()
        self._app = app
        self._cfg = cfg
        self._current = None
        self._calendars: "WeakSet[QCalendarWidget]" = WeakSet()
        self._lock_overlays: "WeakSet[QWidget]" = WeakSet()

        # Follow OS if supported (Qt 6+)
        hints = QGuiApplication.styleHints()
        if hasattr(hints, "colorSchemeChanged"):
            hints.colorSchemeChanged.connect(
                lambda _: (self._cfg.theme == Theme.SYSTEM)
                and self.apply(self._cfg.theme)
            )

    def _is_system_dark(self) -> bool:
        pal = QGuiApplication.palette()
        # Heuristic: dark windows/backgrounds mean dark system theme
        return pal.color(QPalette.Window).lightness() < 128

    def _restyle_registered(self) -> None:
        for cal in list(self._calendars):
            if cal is not None:
                self._apply_calendar_theme(cal)

        for overlay in list(self._lock_overlays):
            if overlay is not None:
                self._apply_lock_overlay_theme(overlay)

    def current(self) -> Theme:
        return self._cfg.theme

    def set(self, theme: Theme):
        self._cfg.theme = theme
        self.apply(theme)

    def apply(self, theme: Theme):
        # Resolve "system" into a concrete theme
        resolved = theme
        if theme == Theme.SYSTEM:
            resolved = Theme.DARK if self._is_system_dark() else Theme.LIGHT

        if resolved == Theme.DARK:
            pal = self._dark_palette()
        else:
            pal = self._light_palette()

        # Always use Fusion so palette applies consistently cross-platform
        QApplication.setStyle("Fusion")

        self._app.setPalette(pal)
        self._current = resolved
        # Re-style any registered widgets
        self._restyle_registered()
        self.themeChanged.emit(self._current)

    def register_calendar(self, cal: QCalendarWidget) -> None:
        """Start theming calendar and keep it in sync with theme changes."""
        self._calendars.add(cal)
        self._apply_calendar_theme(cal)

    def register_lock_overlay(self, overlay: QWidget) -> None:
        """Start theming lock overlay and keep it in sync with theme changes."""
        self._lock_overlays.add(overlay)
        self._apply_lock_overlay_theme(overlay)

    # ----- Palettes -----
    def _dark_palette(self) -> QPalette:
        pal = QPalette()
        base = QColor(35, 35, 35)
        window = QColor(53, 53, 53)
        text = QColor(220, 220, 220)
        disabled = QColor(127, 127, 127)
        focus = QColor(42, 130, 218)

        # Base surfaces
        pal.setColor(QPalette.Window, window)
        pal.setColor(QPalette.Base, base)
        pal.setColor(QPalette.AlternateBase, window)

        # Text
        pal.setColor(QPalette.WindowText, text)
        pal.setColor(QPalette.ToolTipBase, window)
        pal.setColor(QPalette.ToolTipText, text)
        pal.setColor(QPalette.Text, text)
        pal.setColor(QPalette.PlaceholderText, disabled)
        pal.setColor(QPalette.ButtonText, text)

        # Buttons/frames
        pal.setColor(QPalette.Button, window)
        pal.setColor(QPalette.BrightText, QColor(255, 84, 84))

        # Links / selection
        pal.setColor(QPalette.Highlight, focus)
        pal.setColor(QPalette.HighlightedText, QColor(0, 0, 0))
        pal.setColor(QPalette.Link, QColor(Theme.ORANGE_ANCHOR.value))
        pal.setColor(QPalette.LinkVisited, QColor(Theme.ORANGE_ANCHOR_VISITED.value))

        return pal

    def _light_palette(self) -> QPalette:
        pal = QPalette()

        # Base surfaces
        pal.setColor(QPalette.Window, QColor("#ffffff"))
        pal.setColor(QPalette.Base, QColor("#ffffff"))
        pal.setColor(QPalette.AlternateBase, QColor("#f5f5f5"))

        # Text
        pal.setColor(QPalette.WindowText, QColor("#000000"))
        pal.setColor(QPalette.Text, QColor("#000000"))
        pal.setColor(QPalette.ButtonText, QColor("#000000"))

        # Buttons/frames
        pal.setColor(QPalette.Button, QColor("#f0f0f0"))
        pal.setColor(QPalette.Mid, QColor("#9e9e9e"))

        # Links / selection
        pal.setColor(QPalette.Highlight, QColor("#1a73e8"))
        pal.setColor(QPalette.HighlightedText, QColor("#ffffff"))
        pal.setColor(QPalette.Link, QColor("#1a73e8"))
        pal.setColor(QPalette.LinkVisited, QColor("#6b4ca5"))

        return pal

    def _apply_calendar_theme(self, cal: QCalendarWidget) -> None:
        """Use orange accents on the calendar in dark mode only."""
        app_pal = QApplication.instance().palette()
        is_dark = (self.current() == Theme.DARK) or (
            self.current() == Theme.SYSTEM and self._is_system_dark()
        )

        if is_dark:
            highlight_css = Theme.ORANGE_ANCHOR.value
            highlight = QColor(highlight_css)
            black = QColor(0, 0, 0)

            # Per-widget palette: selection color inside the date grid
            pal = cal.palette()
            pal.setColor(QPalette.Highlight, highlight)
            pal.setColor(QPalette.HighlightedText, black)
            cal.setPalette(pal)

            # Stylesheet: nav bar + selected-day background
            cal.setStyleSheet(self._calendar_qss(highlight_css))
        else:
            # Back to app defaults in light/system-light
            cal.setPalette(app_pal)
            cal.setStyleSheet("")

        # --- Normalise weekend colours on *all* themed calendars -------------
        # Qt's default is red for weekends; we want them to match normal text.
        weekday_color = app_pal.windowText().color()
        weekend_fmt = QTextCharFormat()
        weekend_fmt.setForeground(weekday_color)
        cal.setWeekdayTextFormat(Qt.Saturday, weekend_fmt)
        cal.setWeekdayTextFormat(Qt.Sunday, weekend_fmt)

        cal.update()

    def _calendar_qss(self, highlight_css: str) -> str:
        return f"""
        QWidget#qt_calendar_navigationbar {{ background-color: {highlight_css}; }}
        QCalendarWidget QToolButton {{ color: black; }}
        QCalendarWidget QToolButton:hover {{ background-color: rgba(255,165,0,0.20); }}
        /* Selected day color in the table view */
        QCalendarWidget QTableView:enabled {{
            selection-background-color: {highlight_css};
            selection-color: black;
        }}
        /* Keep weekday header readable */
        QCalendarWidget QTableView QHeaderView::section {{
            background: transparent;
            color: palette(windowText);
        }}
        """

    def _apply_lock_overlay_theme(self, overlay: QWidget) -> None:
        """
        Style the LockOverlay (objectName 'LockOverlay') using theme colors.
        Dark: opaque black bg, orange accent; Light: translucent scrim, palette-driven colors.
        """
        pal = QApplication.instance().palette()
        is_dark = (self.current() == Theme.DARK) or (
            self.current() == Theme.SYSTEM and self._is_system_dark()
        )

        if is_dark:
            # Use the link color as the accent
            accent = pal.color(QPalette.Link)
            r, g, b = accent.red(), accent.green(), accent.blue()
            accent_hex = accent.name()

            qss = f"""
#LockOverlay {{ background-color: rgb(0,0,0); }}
#LockOverlay QLabel#lockLabel {{ color: {accent_hex}; font-weight: 600; }}

#LockOverlay QPushButton#unlockButton {{
    color: {accent_hex};
    background-color: rgba({r},{g},{b},0.10);
    border: 1px solid {accent_hex};
    border-radius: 8px;
    padding: 8px 16px;
}}
#LockOverlay QPushButton#unlockButton:hover {{
    background-color: rgba({r},{g},{b},0.16);
    border-color: {accent_hex};
}}
#LockOverlay QPushButton#unlockButton:pressed {{
    background-color: rgba({r},{g},{b},0.24);
}}
#LockOverlay QPushButton#unlockButton:focus {{
    outline: none;
    border-color: {accent_hex};
}}
"""
        else:
            qss = """
#LockOverlay { background-color: rgba(0,0,0,120); }
#LockOverlay QLabel#lockLabel { color: palette(window-text); font-weight: 600; }

#LockOverlay QPushButton#unlockButton {
    color: palette(button-text);
    background-color: rgba(255,255,255,0.92);
    border: 1px solid rgba(0,0,0,0.25);
    border-radius: 8px;
    padding: 8px 16px;
}
#LockOverlay QPushButton#unlockButton:hover {
    background-color: rgba(255,255,255,1.0);
    border-color: rgba(0,0,0,0.35);
}
#LockOverlay QPushButton#unlockButton:pressed {
    background-color: rgba(245,245,245,1.0);
}
#LockOverlay QPushButton#unlockButton:focus {
    outline: none;
    border-color: palette(highlight);
}
"""
        overlay.setStyleSheet(qss)
        overlay.update()
