from PySide6.QtGui import QPainter, QPen, QColor, QConicalGradient, QBrush, QDesktopServices
from PySide6.QtCore import QEasingCurve, Signal, Property, Qt, QPropertyAnimation, QByteArray, QUrl
from PySide6.QtWidgets import QFrame, QGraphicsDropShadowEffect, QVBoxLayout, QWidget, QStackedLayout, QLabel
from collections.abc import Callable
import portprotonqt.themes.standart.styles as default_styles
from portprotonqt.image_utils import load_pixmap_async, round_corners
from portprotonqt.localization import _
from portprotonqt.config_utils import read_favorites, save_favorites, read_display_filter
from portprotonqt.theme_manager import ThemeManager
from portprotonqt.config_utils import read_theme_from_config
from portprotonqt.custom_widgets import ClickableLabel
import weakref
from typing import cast

class GameCard(QFrame):
    borderWidthChanged = Signal()
    gradientAngleChanged = Signal()
    # Signals for context menu actions
    editShortcutRequested = Signal(str, str, str) # name, exec_line, cover_path
    deleteGameRequested = Signal(str, str)        # name, exec_line
    addToMenuRequested = Signal(str, str)         # name, exec_line
    removeFromMenuRequested = Signal(str)         # name
    addToDesktopRequested = Signal(str, str)      # name, exec_line
    removeFromDesktopRequested = Signal(str)      # name
    addToSteamRequested = Signal(str, str, str)   # name, exec_line, cover_path
    removeFromSteamRequested = Signal(str, str)   # name, exec_line
    openGameFolderRequested = Signal(str, str)    # name, exec_line

    def __init__(self, name, description, cover_path, appid, controller_support, exec_line,
                last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, game_source,
                select_callback, theme=None, card_width=250, parent=None, context_menu_manager=None):
        super().__init__(parent)
        self.name = name
        self.description = description
        self.cover_path = cover_path
        self.appid = appid
        self.controller_support = controller_support
        self.exec_line = exec_line
        self.last_launch = last_launch
        self.formatted_playtime = formatted_playtime
        self.protondb_tier = protondb_tier
        self.anticheat_status = anticheat_status
        self.game_source = game_source
        self.last_launch_ts = last_launch_ts
        self.playtime_seconds = playtime_seconds

        self.select_callback = select_callback
        self.context_menu_manager = context_menu_manager
        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.customContextMenuRequested.connect(self._show_context_menu)
        self.theme_manager = ThemeManager()
        self.theme = theme if theme is not None else default_styles

        self.display_filter = read_display_filter()
        self.current_theme_name = read_theme_from_config()

        # Дополнительное пространство для анимации
        extra_margin = 20
        self.setFixedSize(card_width + extra_margin, int(card_width * 1.6) + extra_margin)
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.setStyleSheet(self.theme.GAME_CARD_WINDOW_STYLE)

        # Параметры анимации обводки
        self._borderWidth = 2
        self._gradientAngle = 0.0
        self._hovered = False
        self._focused = False

        # Анимации
        self.thickness_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
        self.thickness_anim.setDuration(300)
        self.gradient_anim = None
        self.pulse_anim = None

        # Флаг для отслеживания подключения слота startPulseAnimation
        self._isPulseAnimationConnected = False

        # Тень
        shadow = QGraphicsDropShadowEffect(self)
        shadow.setBlurRadius(20)
        shadow.setColor(QColor(0, 0, 0, 150))
        shadow.setOffset(0, 0)
        self.setGraphicsEffect(shadow)

        # Отступы
        layout = QVBoxLayout(self)
        layout.setContentsMargins(extra_margin // 2, extra_margin // 2, extra_margin // 2, extra_margin // 2)
        layout.setSpacing(5)

        # Контейнер обложки
        coverWidget = QWidget()
        coverWidget.setFixedSize(card_width, int(card_width * 1.2))
        coverLayout = QStackedLayout(coverWidget)
        coverLayout.setContentsMargins(0, 0, 0, 0)
        coverLayout.setStackingMode(QStackedLayout.StackingMode.StackAll)

        # Обложка
        self.coverLabel = QLabel()
        self.coverLabel.setFixedSize(card_width, int(card_width * 1.2))
        self.coverLabel.setStyleSheet(self.theme.COVER_LABEL_STYLE)
        coverLayout.addWidget(self.coverLabel)

        # создаём слабую ссылку на label
        label_ref = weakref.ref(self.coverLabel)

        def on_cover_loaded(pixmap):
            label = label_ref()
            if label is None:
                return
            label.setPixmap(round_corners(pixmap, 15))

        # асинхронная загрузка обложки (пустая строка даст placeholder внутри load_pixmap_async)
        load_pixmap_async(cover_path or "", card_width, int(card_width * 1.2), on_cover_loaded)

        # Значок избранного (звёздочка) в левом верхнем углу обложки
        self.favoriteLabel = ClickableLabel(coverWidget)
        self.favoriteLabel.setFixedSize(*self.theme.favoriteLabelSize)
        self.favoriteLabel.move(8, 8)
        self.favoriteLabel.clicked.connect(self.toggle_favorite)
        self.is_favorite = self.name in read_favorites()
        self.update_favorite_icon()
        self.favoriteLabel.raise_()

        steam_visible = (str(game_source).lower() == "steam" and self.display_filter in ("all", "favorites"))
        egs_visible = (str(game_source).lower() == "epic" and self.display_filter in ("all", "favorites"))
        portproton_visible = (str(game_source).lower() == "portproton" and self.display_filter in ("all", "favorites"))

        # ProtonDB бейдж
        tier_text = self.getProtonDBText(protondb_tier)
        if tier_text:
            icon_filename = self.getProtonDBIconFilename(protondb_tier)
            icon = self.theme_manager.get_icon(icon_filename, self.current_theme_name)
            self.protondbLabel = ClickableLabel(
                tier_text,
                icon=icon,
                parent=coverWidget,
                icon_size=16,
                icon_space=3,
            )
            self.protondbLabel.setStyleSheet(self.theme.get_protondb_badge_style(protondb_tier))
            self.protondbLabel.setFixedWidth(int(card_width * 2/3))
            protondb_visible = True
        else:
            self.protondbLabel = ClickableLabel("", parent=coverWidget, icon_size=16, icon_space=3)
            self.protondbLabel.setFixedWidth(int(card_width * 2/3))
            self.protondbLabel.setVisible(False)
            protondb_visible = False

        # Steam бейдж
        steam_icon = self.theme_manager.get_icon("steam")
        self.steamLabel = ClickableLabel(
            "Steam",
            icon=steam_icon,
            parent=coverWidget,
            icon_size=16,
            icon_space=5,
        )
        self.steamLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
        self.steamLabel.setFixedWidth(int(card_width * 2/3))
        self.steamLabel.setVisible(steam_visible)

        # Epic Games Store бейдж
        egs_icon = self.theme_manager.get_icon("steam")
        self.egsLabel = ClickableLabel(
            "Epic Games",
            icon=egs_icon,
            parent=coverWidget,
            icon_size=16,
            icon_space=5,
            change_cursor=False
        )
        self.egsLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
        self.egsLabel.setFixedWidth(int(card_width * 2/3))
        self.egsLabel.setVisible(egs_visible)

        # PortProton badge
        portproton_icon = self.theme_manager.get_icon("ppqt-tray")
        self.portprotonLabel = ClickableLabel(
            "PortProton",
            icon=portproton_icon,
            parent=coverWidget,
            icon_size=16,
            icon_space=5,
            change_cursor=False
        )
        self.portprotonLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
        self.portprotonLabel.setFixedWidth(int(card_width * 2/3))
        self.portprotonLabel.setVisible(portproton_visible)

        # WeAntiCheatYet бейдж
        anticheat_text = self.getAntiCheatText(anticheat_status)
        if anticheat_text:
            icon_filename = self.getAntiCheatIconFilename(anticheat_status)
            icon = self.theme_manager.get_icon(icon_filename, self.current_theme_name)
            self.anticheatLabel = ClickableLabel(
                anticheat_text,
                icon=icon,
                parent=coverWidget,
                icon_size=16,
                icon_space=3,
            )
            self.anticheatLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
            self.anticheatLabel.setFixedWidth(int(card_width * 2/3))
            anticheat_visible = True
        else:
            self.anticheatLabel = ClickableLabel("", parent=coverWidget, icon_size=16, icon_space=3)
            self.anticheatLabel.setFixedWidth(int(card_width * 2/3))
            self.anticheatLabel.setVisible(False)
            anticheat_visible = False

        # Расположение бейджей
        right_margin = 8
        badge_spacing = 5
        top_y = 10
        badge_y_positions = []
        badge_width = int(card_width * 2/3)
        if steam_visible:
            steam_x = card_width - badge_width - right_margin
            self.steamLabel.move(steam_x, top_y)
            badge_y_positions.append(top_y + self.steamLabel.height())
        if egs_visible:
            egs_x = card_width - badge_width - right_margin
            egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.egsLabel.move(egs_x, egs_y)
            badge_y_positions.append(egs_y + self.egsLabel.height())
        if portproton_visible:
            portproton_x = card_width - badge_width - right_margin
            portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.portprotonLabel.move(portproton_x, portproton_y)
            badge_y_positions.append(portproton_y + self.portprotonLabel.height())
        if protondb_visible:
            protondb_x = card_width - badge_width - right_margin
            protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.protondbLabel.move(protondb_x, protondb_y)
            badge_y_positions.append(protondb_y + self.protondbLabel.height())
        if anticheat_visible:
            anticheat_x = card_width - badge_width - right_margin
            anticheat_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.anticheatLabel.move(anticheat_x, anticheat_y)

        self.anticheatLabel.raise_()
        self.protondbLabel.raise_()
        self.portprotonLabel.raise_()
        self.egsLabel.raise_()
        self.steamLabel.raise_()
        self.protondbLabel.clicked.connect(self.open_protondb_report)
        self.steamLabel.clicked.connect(self.open_steam_page)
        self.anticheatLabel.clicked.connect(self.open_weanticheatyet_page)

        layout.addWidget(coverWidget)

        # Название игры
        nameLabel = QLabel(name)
        nameLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
        nameLabel.setStyleSheet(self.theme.GAME_CARD_NAME_LABEL_STYLE)
        layout.addWidget(nameLabel)

    def update_badge_visibility(self, display_filter: str):
        """Update badge visibility based on the provided display_filter."""
        self.display_filter = display_filter
        self.steam_visible = (str(self.game_source).lower() == "steam" and display_filter in ("all", "favorites"))
        self.egs_visible = (str(self.game_source).lower() == "epic" and display_filter in ("all", "favorites"))
        self.portproton_visible = (str(self.game_source).lower() == "portproton" and display_filter in ("all", "favorites"))

        self.steamLabel.setVisible(self.steam_visible)
        self.egsLabel.setVisible(self.egs_visible)
        self.portprotonLabel.setVisible(self.portproton_visible)

        # Reposition badges
        right_margin = 8
        badge_spacing = 5
        top_y = 10
        badge_y_positions = []
        badge_width = int(self.coverLabel.width() * 2/3)
        if self.steam_visible:
            steam_x = self.coverLabel.width() - badge_width - right_margin
            self.steamLabel.move(steam_x, top_y)
            badge_y_positions.append(top_y + self.steamLabel.height())
        if self.egs_visible:
            egs_x = self.coverLabel.width() - badge_width - right_margin
            egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.egsLabel.move(egs_x, egs_y)
            badge_y_positions.append(egs_y + self.egsLabel.height())
        if self.portproton_visible:
            portproton_x = self.coverLabel.width() - badge_width - right_margin
            portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.portprotonLabel.move(portproton_x, portproton_y)
            badge_y_positions.append(portproton_y + self.portprotonLabel.height())
        if self.protondbLabel.isVisible():
            protondb_x = self.coverLabel.width() - badge_width - right_margin
            protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.protondbLabel.move(protondb_x, protondb_y)
            badge_y_positions.append(protondb_y + self.protondbLabel.height())
        if self.anticheatLabel.isVisible():
            anticheat_x = self.coverLabel.width() - badge_width - right_margin
            anticheat_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            self.anticheatLabel.move(anticheat_x, anticheat_y)

            self.anticheatLabel.raise_()
            self.protondbLabel.raise_()
            self.portprotonLabel.raise_()
            self.egsLabel.raise_()
            self.steamLabel.raise_()

    def _show_context_menu(self, pos):
        """Delegate context menu display to ContextMenuManager."""
        if self.context_menu_manager:
            self.context_menu_manager.show_context_menu(self, pos)

    @staticmethod
    def getAntiCheatText(status: str) -> str:
        if not status:
            return ""
        translations = {
            "supported": _("Supported"),
            "running": _("Running"),
            "planned": _("Planned"),
            "broken":  _("Broken"),
            "denied": _("Denied")
        }
        return translations.get(status.lower(), "")

    @staticmethod
    def getAntiCheatIconFilename(status: str) -> str:
        status = status.lower()
        if status in ("supported", "running"):
            return "platinum-gold"
        elif status in ("denied", "planned", "broken"):
            return "broken"
        return ""

    @staticmethod
    def getProtonDBText(tier: str) -> str:
        if not tier:
            return ""
        translations = {
            "platinum": _("Platinum"),
            "gold": _("Gold"),
            "silver":  _("Silver"),
            "bronze": _("Bronze"),
            "borked": _("Broken"),
            "pending":  _("Pending")
        }
        return translations.get(tier.lower(), "")

    @staticmethod
    def getProtonDBIconFilename(tier: str) -> str:
        tier = tier.lower()
        if tier in ("platinum", "gold"):
            return "platinum-gold"
        elif tier in ("silver", "bronze"):
            return "silver-bronze"
        elif tier in ("borked", "pending"):
            return "broken"
        return ""

    def open_protondb_report(self):
        url = QUrl(f"https://www.protondb.com/app/{self.appid}")
        QDesktopServices.openUrl(url)

    def open_steam_page(self):
        url = QUrl(f"https://steamcommunity.com/app/{self.appid}")
        QDesktopServices.openUrl(url)

    def open_weanticheatyet_page(self):
        formatted_name = self.name.lower().replace(" ", "-")
        url = QUrl(f"https://areweanticheatyet.com/game/{formatted_name}")
        QDesktopServices.openUrl(url)

    def update_favorite_icon(self):
        if self.is_favorite:
            self.favoriteLabel.setText("★")
        else:
            self.favoriteLabel.setText("☆")
        self.favoriteLabel.setStyleSheet(self.theme.FAVORITE_LABEL_STYLE)

    def toggle_favorite(self):
        favorites = read_favorites()
        if self.is_favorite:
            if self.name in favorites:
                favorites.remove(self.name)
            self.is_favorite = False
        else:
            if self.name not in favorites:
                favorites.append(self.name)
            self.is_favorite = True
        save_favorites(favorites)
        self.update_favorite_icon()

    def getBorderWidth(self) -> int:
        return self._borderWidth

    def setBorderWidth(self, value: int):
        if self._borderWidth != value:
            self._borderWidth = value
            self.borderWidthChanged.emit()
            self.update()

    def getGradientAngle(self) -> float:
        return self._gradientAngle

    def setGradientAngle(self, value: float):
        if self._gradientAngle != value:
            self._gradientAngle = value
            self.gradientAngleChanged.emit()
            self.update()

    borderWidth = Property(int, getBorderWidth, setBorderWidth, None, "", notify=cast(Callable[[], None], borderWidthChanged))
    gradientAngle = Property(float, getGradientAngle, setGradientAngle, None, "", notify=cast(Callable[[], None], gradientAngleChanged))

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        pen = QPen()
        pen.setWidth(self._borderWidth)
        if self._hovered or self._focused:
            center = self.rect().center()
            gradient = QConicalGradient(center, self._gradientAngle)
            gradient.setColorAt(0, QColor("#00fff5"))
            gradient.setColorAt(0.33, QColor("#FF5733"))
            gradient.setColorAt(0.66, QColor("#9B59B6"))
            gradient.setColorAt(1, QColor("#00fff5"))
            pen.setBrush(QBrush(gradient))
        else:
            pen.setColor(QColor(0, 0, 0, 0))

        painter.setPen(pen)
        radius = 18
        bw = round(self._borderWidth / 2)
        rect = self.rect().adjusted(bw, bw, -bw, -bw)
        painter.drawRoundedRect(rect, radius, radius)

    def startPulseAnimation(self):
        if not (self._hovered or self._focused):
            return
        if self.pulse_anim:
            self.pulse_anim.stop()
        self.pulse_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
        self.pulse_anim.setDuration(800)
        self.pulse_anim.setLoopCount(0)
        self.pulse_anim.setKeyValueAt(0, 8)
        self.pulse_anim.setKeyValueAt(0.5, 10)
        self.pulse_anim.setKeyValueAt(1, 8)
        self.pulse_anim.start()

    def enterEvent(self, event):
        self._hovered = True
        self.thickness_anim.stop()
        if self._isPulseAnimationConnected:
            self.thickness_anim.finished.disconnect(self.startPulseAnimation)
            self._isPulseAnimationConnected = False
        self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.OutBack))
        self.thickness_anim.setStartValue(self._borderWidth)
        self.thickness_anim.setEndValue(8)
        self.thickness_anim.finished.connect(self.startPulseAnimation)
        self._isPulseAnimationConnected = True
        self.thickness_anim.start()

        if self.gradient_anim:
            self.gradient_anim.stop()
        self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
        self.gradient_anim.setDuration(3000)
        self.gradient_anim.setStartValue(360)
        self.gradient_anim.setEndValue(0)
        self.gradient_anim.setLoopCount(-1)
        self.gradient_anim.start()

        super().enterEvent(event)

    def leaveEvent(self, event):
        self._hovered = False
        if not self._focused:  # Сохраняем анимацию, если есть фокус
            if self.gradient_anim:
                self.gradient_anim.stop()
                self.gradient_anim = None
            self.thickness_anim.stop()
            if self._isPulseAnimationConnected:
                self.thickness_anim.finished.disconnect(self.startPulseAnimation)
                self._isPulseAnimationConnected = False
            if self.pulse_anim:
                self.pulse_anim.stop()
                self.pulse_anim = None
            self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
            self.thickness_anim.setStartValue(self._borderWidth)
            self.thickness_anim.setEndValue(2)
            self.thickness_anim.start()

        super().leaveEvent(event)

    def focusInEvent(self, event):
        self._focused = True
        self.thickness_anim.stop()
        if self._isPulseAnimationConnected:
            self.thickness_anim.finished.disconnect(self.startPulseAnimation)
            self._isPulseAnimationConnected = False
        self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.OutBack))
        self.thickness_anim.setStartValue(self._borderWidth)
        self.thickness_anim.setEndValue(12)
        self.thickness_anim.finished.connect(self.startPulseAnimation)
        self._isPulseAnimationConnected = True
        self.thickness_anim.start()

        if self.gradient_anim:
            self.gradient_anim.stop()
        self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
        self.gradient_anim.setDuration(3000)
        self.gradient_anim.setStartValue(360)
        self.gradient_anim.setEndValue(0)
        self.gradient_anim.setLoopCount(-1)
        self.gradient_anim.start()

        super().focusInEvent(event)

    def focusOutEvent(self, event):
        self._focused = False
        if not self._hovered:  # Сохраняем анимацию, если есть наведение
            if self.gradient_anim:
                self.gradient_anim.stop()
                self.gradient_anim = None
            self.thickness_anim.stop()
            if self._isPulseAnimationConnected:
                self.thickness_anim.finished.disconnect(self.startPulseAnimation)
                self._isPulseAnimationConnected = False
            if self.pulse_anim:
                self.pulse_anim.stop()
                self.pulse_anim = None
            self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
            self.thickness_anim.setStartValue(self._borderWidth)
            self.thickness_anim.setEndValue(2)
            self.thickness_anim.start()

        super().focusOutEvent(event)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.select_callback(
                self.name,
                self.description,
                self.cover_path,
                self.appid,
                self.controller_support,
                self.exec_line,
                self.last_launch,
                self.formatted_playtime,
                self.protondb_tier,
                self.game_source,
                self.anticheat_status
            )
        super().mousePressEvent(event)

    def keyPressEvent(self, event):
        if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
            self.select_callback(
                self.name,
                self.description,
                self.cover_path,
                self.appid,
                self.controller_support,
                self.exec_line,
                self.last_launch,
                self.formatted_playtime,
                self.protondb_tier,
                self.game_source,
                self.anticheat_status
            )
        else:
            super().keyPressEvent(event)