"""
PyQt6 Multi-threaded Video Player with QWebEngineView Overlay System
Replaces the previous PyQt5 implementation with enhanced functionality
"""

import sys
import time
import logging
import json
import threading
import signal
import os
from pathlib import Path
from typing import Optional, Dict, Any, List

# Disable Qt WebEngine sandbox to allow localhost fetches - MUST be set before Qt imports
os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = '1'
print("Qt WebEngine sandbox disabled for localhost access")

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QPushButton, QSlider, QFrame, QStackedWidget
)
from PyQt6.QtCore import (
    Qt, QTimer, QThread, pyqtSignal, QUrl, QRect, QPropertyAnimation,
    QEasingCurve, QSequentialAnimationGroup, QObject, QMutex, QMutexLocker,
    QThreadPool, QRunnable, pyqtSlot
)
from PyQt6.QtGui import (
    QFont, QPainter, QPen, QBrush, QColor, QPixmap, QMovie, 
    QLinearGradient, QFontMetrics, QAction
)
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
from PyQt6.QtMultimediaWidgets import QVideoWidget
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import QWebEngineProfile
from PyQt6.QtWebChannel import QWebChannel

from ..core.thread_manager import ThreadedComponent
from ..core.message_bus import MessageBus, Message, MessageType, MessageBuilder
from ..config.settings import QtConfig
from .overlay_url_handler import OverlayUrlSchemeHandler

logger = logging.getLogger(__name__)


class OverlayWebChannel(QObject):
    """QObject for WebChannel communication with overlay HTML/JS"""

    # Signals to send data to JavaScript
    dataUpdated = pyqtSignal(dict)
    positionChanged = pyqtSignal(float, float)  # position, duration in seconds
    videoInfoChanged = pyqtSignal(dict)

    # Signal to receive console messages from JavaScript
    consoleMessage = pyqtSignal(str, str, int, str)  # level, message, line, source
    
    def __init__(self, db_manager=None, message_bus=None):
        super().__init__()
        self.mutex = QMutex()
        self.overlay_data = {}
        self.db_manager = db_manager
        self.message_bus = message_bus
        self.timer_state = {"running": False, "remaining_seconds": 0}

        # Subscribe to timer updates if message bus is available
        if self.message_bus:
            self.message_bus.subscribe("qt_player", MessageType.CUSTOM, self._handle_timer_update)

        logger.info("OverlayWebChannel initialized")
    
    @pyqtSlot(str)
    def updateTitle(self, title: str):
        """Update main title from JavaScript"""
        # Clean data before emitting to prevent null emissions
        if title is None or title == "":
            logger.debug("Skipping null/empty title update")
            return
            
        with QMutexLocker(self.mutex):
            self.overlay_data['title'] = title
            cleaned_data = self._clean_data({'title': title})
            if cleaned_data:
                self.dataUpdated.emit(cleaned_data)
                logger.debug(f"Title updated: {title}")
    
    @pyqtSlot(str)
    def updateSubtitle(self, subtitle: str):
        """Update subtitle from JavaScript"""
        # Clean data before emitting to prevent null emissions
        if subtitle is None or subtitle == "":
            logger.debug("Skipping null/empty subtitle update")
            return
            
        with QMutexLocker(self.mutex):
            self.overlay_data['subtitle'] = subtitle
            cleaned_data = self._clean_data({'subtitle': subtitle})
            if cleaned_data:
                self.dataUpdated.emit(cleaned_data)
                logger.debug(f"Subtitle updated: {subtitle}")
    
    @pyqtSlot(bool)
    def toggleStats(self, show: bool):
        """Toggle stats panel visibility"""
        # Clean data before emitting to prevent null emissions
        if show is None:
            logger.debug("Skipping null show stats value")
            return
            
        with QMutexLocker(self.mutex):
            self.overlay_data['showStats'] = show
            cleaned_data = self._clean_data({'showStats': show})
            if cleaned_data:
                self.dataUpdated.emit(cleaned_data)
                logger.debug(f"Stats panel toggled: {show}")
    
    def send_data_update(self, data: Dict[str, Any]):
        """Send data update to JavaScript (thread-safe)"""
        # Validate data before sending to prevent null emissions
        if not data:
            logger.warning("send_data_update called with null/empty data, skipping")
            return

        # Check if this data contains results information
        has_results_data = any(key in data for key in ['outcome', 'result', 'match', 'match_id', 'fixture_id'])

        # Clean data to remove null/undefined values before sending to JavaScript
        cleaned_data = self._clean_data(data)
        if not cleaned_data:
            return

        with QMutexLocker(self.mutex):
            self.overlay_data.update(cleaned_data)

            # Add additional validation just before emit
            if cleaned_data and isinstance(cleaned_data, dict) and any(v is not None for v in cleaned_data.values()):
                self.dataUpdated.emit(cleaned_data)
    
    def _clean_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Clean data by removing null/undefined values before sending to JavaScript"""
        cleaned_data = {}

        for key, value in data.items():
            # Skip null, undefined, or empty string values
            if value is None or value == "":
                logger.debug(f"OverlayWebChannel: Skipping null/empty property '{key}'")
                continue

            # Keep dicts and lists as is (Qt WebChannel can handle them)
            if isinstance(value, (dict, list)):
                cleaned_data[key] = value
            # Convert non-null values to appropriate types
            elif isinstance(value, str):
                cleaned_data[key] = value
            elif isinstance(value, bool):
                cleaned_data[key] = value
            elif isinstance(value, (int, float)):
                cleaned_data[key] = str(value)  # Convert numbers to strings for overlay
            else:
                # For other types, convert to string
                cleaned_data[key] = str(value)

        return cleaned_data
    
    def send_position_update(self, position: float, duration: float):
        """Send playback position update to JavaScript (thread-safe)"""
        # Validate position and duration before emitting
        if position is None or duration is None:
            logger.debug("Skipping null position/duration update")
            return
        self.positionChanged.emit(position, duration)
    
    def send_video_info(self, info: Dict[str, Any]):
        """Send video information to JavaScript (thread-safe)"""
        # Validate info before emitting
        if not info:
            logger.debug("Skipping null/empty video info update")
            return
        cleaned_info = self._clean_data(info)
        if cleaned_info:
            self.videoInfoChanged.emit(cleaned_info)
        else:
            logger.debug("Video info contained only null/undefined values, skipping")

    @pyqtSlot(str)
    def log(self, message: str):
        """Receive console.log messages from JavaScript"""
        logger.info(f"[JS CONSOLE.LOG] {message}")
        print(f"[JS CONSOLE.LOG] {message}")

    @pyqtSlot(result=str)
    def getCurrentData(self) -> str:
        """Provide current overlay data to JavaScript via WebChannel"""
        try:
            # Return current overlay data
            current_data = dict(self.overlay_data)
            json_result = json.dumps(current_data)
            return json_result
        except Exception as e:
            logger.error(f"Failed to get current data: {e}")
            import traceback
            logger.error(f"Full traceback: {traceback.format_exc()}")
            return json.dumps({})

    @pyqtSlot(result=str)
    def getFixtureData(self) -> str:
        """Provide fixture data to JavaScript via WebChannel"""
        try:
            # Get fixture data from the games thread via message bus
            fixture_data = self._get_fixture_data_from_games_thread()
            if fixture_data:
                logger.debug(f"Providing fixture data to JavaScript: {len(fixture_data)} matches")
                return json.dumps(fixture_data)
            else:
                logger.debug("No fixture data available")
                return json.dumps([])
        except Exception as e:
            logger.error(f"Failed to get fixture data: {e}")
            return json.dumps([])

    def _handle_timer_update(self, message: Message):
        """Handle timer update messages"""
        try:
            logger.debug(f"OverlayWebChannel received message: {message.type} from {message.sender}")
            logger.debug(f"Message data: {message.data}")
            if message.data.get("timer_update"):
                timer_update = message.data["timer_update"]
                with QMutexLocker(self.mutex):
                    self.timer_state = timer_update
                logger.debug(f"Timer state updated: {timer_update}")
            else:
                logger.debug("Message does not contain timer_update")
        except Exception as e:
            logger.error(f"Failed to handle timer update: {e}")

    @pyqtSlot(result=str)
    def getTimerState(self) -> str:
        """Provide current cached timer state to JavaScript via WebChannel"""
        try:
            with QMutexLocker(self.mutex):
                timer_state = self.timer_state.copy()
            logger.debug(f"Providing cached timer state to JavaScript: {timer_state}")
            return json.dumps(timer_state)
        except Exception as e:
            logger.error(f"Failed to get timer state: {e}")
            return json.dumps({"running": False, "remaining_seconds": 0})

    @pyqtSlot(int, result=str)
    def getWinningBets(self, match_id: int) -> str:
        """Provide winning bets data for a match to JavaScript via WebChannel"""
        try:
            logger.info(f"QtWebChannel: Getting winning bets for match {match_id}")

            # Get winning bets from database
            winning_bets = self._get_winning_bets_from_database(match_id)

            logger.debug(f"QtWebChannel: Found {len(winning_bets)} winning bets for match {match_id}")
            return json.dumps(winning_bets)

        except Exception as e:
            logger.error(f"QtWebChannel: Failed to get winning bets for match {match_id}: {e}")
            return json.dumps([])

    @pyqtSlot(int, result=str)
    def getWinningOutcomes(self, match_id: int) -> str:
        """Provide winning outcomes data for a match to JavaScript via WebChannel"""
        try:
            logger.info(f"QtWebChannel: Getting winning outcomes for match {match_id}")
            logger.debug(f"QtWebChannel: db_manager available: {self.db_manager is not None}")

            # Get winning outcomes from database
            winning_outcomes = self._get_winning_outcomes_from_database(match_id)

            logger.debug(f"QtWebChannel: Found {len(winning_outcomes)} winning outcomes for match {match_id}")
            return json.dumps(winning_outcomes)

        except Exception as e:
            logger.error(f"QtWebChannel: Failed to get winning outcomes for match {match_id}: {e}")
            import traceback
            logger.error(f"QtWebChannel: Full traceback: {traceback.format_exc()}")
            return json.dumps([])

    @pyqtSlot(result=str)
    def getLicenseText(self) -> str:
        """Provide license text to JavaScript via WebChannel"""
        try:
            logger.info("QtWebChannel: Getting license text from database")

            if not self.db_manager:
                logger.error("QtWebChannel: Database manager not available")
                return json.dumps({"license_text": "License text not available - database not connected"})

            # Get license text from database
            license_text = self._get_license_text_from_database()

            logger.debug(f"QtWebChannel: Retrieved license text from database")
            return json.dumps({"license_text": license_text})

        except Exception as e:
            logger.error(f"QtWebChannel: Failed to get license text: {e}")
            return json.dumps({"license_text": "License text not available - error occurred"})

    @pyqtSlot(result=str)
    def getCompletedMatches(self) -> str:
        """Provide last 3 completed matches to JavaScript via WebChannel"""
        try:
            logger.info("QtWebChannel: Getting completed matches from database")

            if not self.db_manager:
                logger.error("QtWebChannel: Database manager not available")
                return json.dumps([])

            # Get completed matches from database
            completed_matches = self._get_completed_matches_from_database()

            logger.debug(f"QtWebChannel: Retrieved {len(completed_matches)} completed matches from database")
            return json.dumps(completed_matches)

        except Exception as e:
            logger.error(f"QtWebChannel: Failed to get completed matches: {e}")
            return json.dumps([])

    def _get_license_text_from_database(self) -> str:
        """Get license text from database configuration"""
        try:
            if not self.db_manager:
                logger.error("Database manager not available for getting license text")
                return "License text not available - database not connected"

            # Get license text from configuration
            license_text = self.db_manager.get_config_value('license_text', 'License text not configured')

            logger.debug(f"Retrieved license text from database: {license_text}")
            return license_text

        except Exception as e:
            logger.error(f"Failed to get license text from database: {e}")
            return "License text not available - database error"

    def _get_completed_matches_from_database(self) -> Optional[List[Dict[str, Any]]]:
        """Get last 3 completed matches from database"""
        try:
            from ..database.models import MatchModel
            from datetime import datetime

            # Use the database manager passed to this channel
            if not self.db_manager:
                logger.error("Database manager not initialized")
                return []

            session = self.db_manager.get_session()

            try:
                # Get last 3 completed matches (status = 'done' and result is not null)
                # Ordered by end_time descending (most recent first)
                completed_matches = session.query(MatchModel).filter(
                    MatchModel.status == 'done',
                    MatchModel.result.isnot(None),
                    MatchModel.end_time.isnot(None)
                ).order_by(MatchModel.end_time.desc()).limit(3).all()

                if not completed_matches:
                    logger.debug("No completed matches found")
                    return []

                matches_data = []
                for match in completed_matches:
                    match_data = {
                        'id': match.id,
                        'match_number': match.match_number,
                        'fighter1_township': match.fighter1_township,
                        'fighter2_township': match.fighter2_township,
                        'venue_kampala_township': match.venue_kampala_township,
                        'result': match.result,
                        'under_over_result': match.under_over_result,
                        'end_time': match.end_time.isoformat() if match.end_time else None
                    }
                    matches_data.append(match_data)

                logger.debug(f"Retrieved {len(matches_data)} completed matches from database")
                return matches_data

            finally:
                session.close()

        except Exception as e:
            logger.error(f"Failed to get completed matches from database: {e}")
            return []

    def _get_fixture_data_from_games_thread(self) -> Optional[List[Dict[str, Any]]]:
        """Get fixture data from the games thread"""
        try:
            # Import here to avoid circular imports
            from ..core.message_bus import MessageBus, Message, MessageType

            # Get the message bus from the parent player
            if hasattr(self, 'parent') and self.parent():
                player_window = self.parent()
                if hasattr(player_window, '_message_bus') and player_window._message_bus:
                    message_bus = player_window._message_bus

                    # Send a request for fixture data to games_thread
                    request_message = Message(
                        type=MessageType.CUSTOM,
                        sender="qt_player",
                        recipient="games_thread",
                        data={
                            "request": "get_active_fixture_matches",
                            "limit": 5
                        }
                    )

                    # For synchronous response, we need to implement a callback mechanism
                    # For now, return mock data or get from database directly
                    return self._get_fixture_data_from_database()

            # Fallback: get data directly from database
            return self._get_fixture_data_from_database()

        except Exception as e:
            logger.error(f"Failed to get fixture data from games thread: {e}")
            return None

    def _get_fixture_data_from_database(self) -> Optional[List[Dict[str, Any]]]:
        """Get fixture data directly from database"""
        try:
            from ..database.models import MatchModel, MatchOutcomeModel
            from datetime import datetime

            # Use the database manager passed to this channel
            if not self.db_manager:
                logger.error("Database manager not initialized")
                return None

            session = self.db_manager.get_session()

            try:
                # Get today's date in UTC (consistent with database storage)
                today = datetime.utcnow().date()

                # Get active matches for today (non-terminal states)
                active_matches = session.query(MatchModel).filter(
                    MatchModel.start_time.isnot(None),
                    MatchModel.start_time >= datetime.combine(today, datetime.min.time()),
                    MatchModel.start_time < datetime.combine(today, datetime.max.time()),
                    MatchModel.status.notin_(['done', 'end', 'cancelled', 'failed', 'paused']),
                    MatchModel.active_status == True
                ).order_by(MatchModel.start_time.asc()).limit(5).all()

                if not active_matches:
                    logger.debug("No active matches found")
                    return []

                fixture_data = []
                for match in active_matches:
                    # Get outcomes for this match
                    outcomes = session.query(MatchOutcomeModel).filter(
                        MatchOutcomeModel.match_id == match.id
                    ).all()

                    outcome_dict = {}
                    for outcome in outcomes:
                        outcome_dict[outcome.column_name] = outcome.float_value

                    match_data = {
                        'id': match.id,
                        'match_number': match.match_number,
                        'fighter1_township': match.fighter1_township,
                        'fighter2_township': match.fighter2_township,
                        'venue_kampala_township': match.venue_kampala_township,
                        'start_time': match.start_time.isoformat() if match.start_time else None,
                        'outcomes': outcome_dict
                    }
                    fixture_data.append(match_data)

                logger.debug(f"Retrieved {len(fixture_data)} matches from database")
                return fixture_data

            finally:
                session.close()

        except Exception as e:
            logger.error(f"Failed to get fixture data from database: {e}")
            return None

    def _get_winning_bets_from_database(self, match_id: int) -> List[Dict[str, Any]]:
        """Get winning bets for a match from database"""
        try:
            from ..database.models import BetDetailModel, MatchModel
            from datetime import datetime

            # Use the database manager passed to this channel
            if not self.db_manager:
                logger.error("Database manager not initialized")
                return []

            session = self.db_manager.get_session()

            try:
                # Get winning bets for this match
                winning_bets = session.query(BetDetailModel).filter(
                    BetDetailModel.match_id == match_id,
                    BetDetailModel.result == 'win'
                ).all()

                # Convert to dictionary format for JavaScript
                bets_data = []
                for bet in winning_bets:
                    bet_data = {
                        'id': bet.id,
                        'outcome': bet.outcome,
                        'amount': float(bet.amount),
                        'win_amount': float(bet.win_amount) if bet.win_amount else 0.0,
                        'result': bet.result
                    }
                    bets_data.append(bet_data)

                logger.debug(f"Retrieved {len(bets_data)} winning bets for match {match_id}")
                return bets_data

            finally:
                session.close()

        except Exception as e:
            logger.error(f"Failed to get winning bets from database: {e}")
            return []

    def _get_winning_outcomes_from_database(self, match_id: int) -> List[Dict[str, Any]]:
        """Get winning outcomes aggregated by outcome type for a match from database, including UNDER/OVER when they win"""
        try:
            from ..database.models import BetDetailModel, MatchModel, MatchOutcomeModel, ExtractionStatsModel, ExtractionAssociationModel
            from sqlalchemy import func

            logger.debug(f"QtWebChannel: _get_winning_outcomes_from_database called for match {match_id}")

            # Use the database manager passed to this channel
            if not self.db_manager:
                logger.error("QtWebChannel: Database manager not initialized")
                return []

            logger.debug("QtWebChannel: Getting database session")
            session = self.db_manager.get_session()

            try:
                logger.debug(f"QtWebChannel: Executing query for match {match_id}")

                # First, check if match has result set (from extraction)
                match = session.query(MatchModel).filter(MatchModel.id == match_id).first()
                if match and match.result:
                    logger.debug(f"QtWebChannel: Match {match_id} has result set: {match.result}")

                    # Get winning outcomes from associations
                    winning_outcomes_query = session.query(ExtractionAssociationModel.outcome_name).filter(
                        ExtractionAssociationModel.extraction_result == match.result
                    ).all()

                    winning_outcome_names = [outcome.outcome_name for outcome in winning_outcomes_query]
                    logger.debug(f"QtWebChannel: Found {len(winning_outcome_names)} winning outcomes from associations for result {match.result}: {winning_outcome_names}")

                    # Include under_over_result if available
                    if match.under_over_result:
                        winning_outcome_names.append(match.under_over_result)
                        logger.debug(f"QtWebChannel: Added under_over_result: {match.under_over_result}")

                    # Calculate amounts from pending bets
                    outcomes_data = []
                    for outcome_name in set(winning_outcome_names):
                        total_amount = session.query(func.sum(BetDetailModel.amount)).filter(
                            BetDetailModel.match_id == match_id,
                            BetDetailModel.outcome == outcome_name,
                            BetDetailModel.result == 'pending'
                        ).scalar() or 0.0

                        outcome_data = {
                            'outcome': outcome_name,
                            'amount': float(total_amount)
                        }
                        outcomes_data.append(outcome_data)
                        logger.debug(f"QtWebChannel: Calculated amount for {outcome_name}: {total_amount}")

                    logger.debug(f"QtWebChannel: Retrieved {len(outcomes_data)} winning outcomes from match result: {outcomes_data}")
                    return outcomes_data

                # Fallback: try to get winning outcomes from updated bet results
                all_winning_outcomes_query = session.query(
                    BetDetailModel.outcome,
                    func.sum(BetDetailModel.win_amount).label('total_amount')
                ).join(MatchModel).filter(
                    BetDetailModel.match_id == match_id,
                    BetDetailModel.result == 'win',
                    MatchModel.active_status == True
                ).group_by(BetDetailModel.outcome).all()

                logger.debug(f"QtWebChannel: Query returned {len(all_winning_outcomes_query)} winning outcomes from bet results")

                if all_winning_outcomes_query:
                    # Convert to dictionary format for JavaScript
                    outcomes_data = []
                    for outcome_name, total_amount in all_winning_outcomes_query:
                        outcome_data = {
                            'outcome': outcome_name,
                            'amount': float(total_amount) if total_amount else 0.0
                        }
                        outcomes_data.append(outcome_data)

                    logger.debug(f"QtWebChannel: Retrieved {len(outcomes_data)} winning outcomes from bet results: {outcomes_data}")
                    return outcomes_data

                # Fallback: No winning bets found, try to get from extraction results
                logger.debug("QtWebChannel: No winning bets found, trying extraction results fallback")

                # Get extraction stats for this match
                extraction_stats = session.query(ExtractionStatsModel).filter(
                    ExtractionStatsModel.match_id == match_id
                ).first()

                if not extraction_stats or not extraction_stats.actual_result:
                    logger.debug("QtWebChannel: No extraction stats found for match")
                    return []

                extraction_result = extraction_stats.actual_result
                logger.debug(f"QtWebChannel: Found extraction result: {extraction_result}")

                # Get winning outcomes from extraction associations
                winning_outcomes_query = session.query(ExtractionAssociationModel.outcome_name).filter(
                    ExtractionAssociationModel.extraction_result == extraction_result
                ).all()

                winning_outcome_names = [outcome.outcome_name for outcome in winning_outcomes_query]
                logger.debug(f"QtWebChannel: Found {len(winning_outcome_names)} winning outcomes from associations: {winning_outcome_names}")

                # Get the match to check for under_over_result
                match = session.query(MatchModel).filter(MatchModel.id == match_id).first()
                under_over_result = match.under_over_result if match else None
                logger.debug(f"QtWebChannel: Under/over result from match: {under_over_result}")

                # Combine winning outcomes
                all_winning_outcomes = set(winning_outcome_names)
                if under_over_result:
                    all_winning_outcomes.add(under_over_result)

                logger.debug(f"QtWebChannel: Combined winning outcomes: {all_winning_outcomes}")

                # Calculate amounts for each winning outcome from pending bets
                outcomes_data = []
                for outcome_name in all_winning_outcomes:
                    # Get total amount of pending bets for this outcome
                    total_amount = session.query(func.sum(BetDetailModel.amount)).filter(
                        BetDetailModel.match_id == match_id,
                        BetDetailModel.outcome == outcome_name,
                        BetDetailModel.result == 'pending'
                    ).scalar() or 0.0

                    outcome_data = {
                        'outcome': outcome_name,
                        'amount': float(total_amount)
                    }
                    outcomes_data.append(outcome_data)
                    logger.debug(f"QtWebChannel: Calculated amount for {outcome_name}: {total_amount}")

                logger.debug(f"QtWebChannel: Retrieved {len(outcomes_data)} winning outcomes from extraction fallback: {outcomes_data}")
                return outcomes_data

            finally:
                session.close()
                logger.debug("QtWebChannel: Database session closed")

        except Exception as e:
            logger.error(f"QtWebChannel: Failed to get winning outcomes from database: {e}")
            import traceback
            logger.error(f"QtWebChannel: Full traceback: {traceback.format_exc()}")
            return []





class VideoProcessingWorker(QRunnable):
    """Background worker for video processing tasks"""
    
    def __init__(self, task_type: str, data: Dict[str, Any], callback=None):
        super().__init__()
        self.task_type = task_type
        self.data = data
        self.callback = callback
        self.setAutoDelete(True)
    
    def run(self):
        """Execute the video processing task"""
        try:
            logger.debug(f"Processing video task: {self.task_type}")
            
            if self.task_type == "metadata_extraction":
                result = self._extract_metadata()
            elif self.task_type == "thumbnail_generation":
                result = self._generate_thumbnail()
            elif self.task_type == "overlay_rendering":
                result = self._render_overlay()
            else:
                result = {"error": f"Unknown task type: {self.task_type}"}
            
            if self.callback:
                self.callback(result)
                
        except Exception as e:
            logger.error(f"Video processing worker error: {e}")
            if self.callback:
                self.callback({"error": str(e)})
    
    def _extract_metadata(self) -> Dict[str, Any]:
        """Extract video metadata"""
        # Placeholder for metadata extraction
        return {
            "resolution": "1920x1080",
            "bitrate": "5.2 Mbps",
            "codec": "H.264",
            "fps": "30.0"
        }
    
    def _generate_thumbnail(self) -> Dict[str, Any]:
        """Generate video thumbnail"""
        # Placeholder for thumbnail generation
        return {"thumbnail_path": "/tmp/thumbnail.jpg"}
    
    def _render_overlay(self) -> Dict[str, Any]:
        """Render overlay elements"""
        # Placeholder for overlay rendering
        return {"rendered": True}


class OverlayWebView(QWebEngineView):
    """Custom QWebEngineView for video overlays with transparent background"""

    def __init__(self, parent=None, debug_overlay=False, web_server_url=None, db_manager=None):
        super().__init__(parent)
        logger.info(f"DEBUG: OverlayWebView.__init__ called with debug_overlay={debug_overlay}")
        self.debug_overlay = debug_overlay
        self.web_server_url = web_server_url or "http://127.0.0.1:5001"
        self.db_manager = db_manager
        self.web_channel = None
        self.overlay_channel = None
        self.current_template = "default.html"

        # Built-in templates directory (bundled with app)
        self.builtin_templates_dir = Path(__file__).parent / "templates"

        # Persistent uploaded templates directory (user data)
        self.uploaded_templates_dir = self._get_persistent_templates_dir()
        self.uploaded_templates_dir.mkdir(parents=True, exist_ok=True)

        # Primary templates directory for backwards compatibility
        self.templates_dir = self.builtin_templates_dir

        self.setup_web_view()
        self._setup_custom_scheme()
        logger.info(f"OverlayWebView initialized - builtin: {self.builtin_templates_dir}, uploaded: {self.uploaded_templates_dir}")

    def setup_web_view(self):
        """Setup web view with proper transparency for overlay"""
        logger.info("OverlayWebView.setup_web_view() - Starting setup")

        # Enhanced GPU detection and logging for VirtualBox compatibility
        import os
        is_mesa = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or \
                  os.environ.get('MESA_GL_VERSION_OVERRIDE') is not None

        # Check if running in VirtualBox
        is_virtualbox = False
        try:
            with open('/sys/devices/virtual/dmi/id/sys_vendor', 'r') as f:
                vendor = f.read().strip().lower()
                if 'virtualbox' in vendor or 'innotek' in vendor:
                    is_virtualbox = True
                    logger.warning("VIRTUALBOX ENVIRONMENT DETECTED - Overlay compatibility issues likely")
        except:
            # Check for VirtualBox guest additions
            try:
                import subprocess
                result = subprocess.run(['lsmod'], capture_output=True, text=True, timeout=2)
                if 'vboxvideo' in result.stdout or 'vboxguest' in result.stdout:
                    is_virtualbox = True
                    logger.warning("VIRTUALBOX GUEST MODULES DETECTED - Overlay compatibility issues likely")
            except:
                pass

        if is_virtualbox:
            logger.warning("=== VIRTUALBOX OVERLAY DIAGNOSTICS ===")
            logger.warning("VirtualBox detected - common overlay issues:")
            logger.warning("1. GPU acceleration disabled by default")
            logger.warning("2. Limited OpenGL support")
            logger.warning("3. Qt WebEngine may fallback to software rendering")
            logger.warning("4. Transparency effects may not work")
            logger.warning("5. WebChannel communication may be unreliable")

        # Log GPU-related environment variables
        gpu_env_vars = [
            'LIBGL_ALWAYS_SOFTWARE',
            'MESA_GL_VERSION_OVERRIDE',
            'QTWEBENGINE_CHROMIUM_FLAGS',
            'QT_QPA_PLATFORM',
            'DISPLAY',
            'XDG_SESSION_TYPE',
            'QTWEBENGINE_DISABLE_SANDBOX',
            'QTWEBENGINE_REMOTE_DEBUGGING',
            'QTWEBENGINE_DISABLE_GPU',
            'QT_OPENGL'
        ]

        logger.info("=== GPU Environment Check ===")
        for var in gpu_env_vars:
            value = os.environ.get(var)
            logger.info(f"{var}: {value if value else '<not set>'}")

        # Check for NVIDIA GPU and processes
        try:
            import subprocess
            # Check GPU info
            result = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total,memory.used,driver_version', '--format=csv,noheader,nounits'],
                                  capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                gpu_info = result.stdout.strip().split(',')
                logger.info(f"NVIDIA GPU detected: {gpu_info[0]}, Memory: {gpu_info[1]}/{gpu_info[2]}MB, Driver: {gpu_info[3]}")
            else:
                logger.warning("nvidia-smi not available or failed - likely VirtualBox or no NVIDIA GPU")

            # Check GPU processes specifically
            proc_result = subprocess.run(['nvidia-smi', 'pmon'], capture_output=True, text=True, timeout=5)
            if proc_result.returncode == 0:
                lines = proc_result.stdout.strip().split('\n')
                gpu_processes = [line for line in lines if line.strip() and not line.startswith('#')]
                if gpu_processes:
                    logger.info(f"Active GPU processes: {len(gpu_processes)} found")
                    for proc in gpu_processes[:3]:  # Log first 3 processes
                        logger.info(f"GPU Process: {proc}")
                else:
                    logger.warning("NO GPU PROCESSES FOUND - This indicates GPU is not being utilized (common in VirtualBox)")
            else:
                logger.warning("Could not check GPU processes - likely VirtualBox environment")

        except Exception as e:
            logger.warning(f"Could not check NVIDIA GPU: {e} - likely VirtualBox or no GPU")

        # Check OpenGL
        try:
            result = subprocess.run(['glxinfo', '|', 'grep', '"OpenGL renderer"'], shell=True,
                                  capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                logger.info(f"OpenGL renderer: {result.stdout.strip()}")
            else:
                logger.warning("Could not get OpenGL renderer info - VirtualBox may not support OpenGL properly")
        except Exception as e:
            logger.warning(f"Could not check OpenGL: {e} - VirtualBox OpenGL issues likely")

        # Check Vulkan (Qt WebEngine can use Vulkan on some systems)
        try:
            result = subprocess.run(['vulkaninfo', '--summary'], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                logger.info("Vulkan is available")
            else:
                logger.info("Vulkan not available or not configured - VirtualBox typically lacks Vulkan support")
        except Exception as e:
            logger.debug(f"Vulkan check failed: {e}")

        logger.info(f"Mesa software rendering detection: {is_mesa}")
        if is_mesa:
            logger.warning("MESA SOFTWARE RENDERING DETECTED - GPU acceleration may be disabled (VirtualBox default)")
        elif is_virtualbox:
            logger.warning("VirtualBox detected but Mesa not forced - Qt WebEngine may still fallback to software rendering")

        # Check Qt WebEngine version and capabilities
        try:
            from PyQt6.QtWebEngineCore import QWebEngineProfile
            logger.info("Qt WebEngine is available")
        except ImportError as e:
            logger.error(f"Qt WebEngine not available: {e} - overlays will not work")

        # Set transparent background on the web page
        page = self.page()

        if is_mesa:
            # Mesa-specific transparency settings
            logger.info("Applying Mesa software rendering transparency fixes")
            page.setBackgroundColor(QColor(0, 0, 0, 1))  # Semi-transparent for Mesa
            # Use CSS-based transparency for Mesa
            page.runJavaScript("""
                document.body.style.backgroundColor = 'rgba(0, 0, 0, 0.01)';
                document.documentElement.style.backgroundColor = 'rgba(0, 0, 0, 0.01)';
            """)
        else:
            # Standard hardware transparency
            logger.info("Applying standard hardware transparency")
            page.setBackgroundColor(QColor(0, 0, 0, 0))  # Fully transparent

        # Widget should be visible but allow transparency
        if is_mesa:
            self.setStyleSheet("""
                QWebEngineView {
                    border: none;
                    background: rgba(0, 0, 0, 0.01);
                }
            """)
        else:
            self.setStyleSheet("""
                QWebEngineView {
                    border: none;
                    background: transparent;
                }
            """)

        logger.info(f"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured")

        # Setup WebChannel
        logger.info("Setting up WebChannel for overlay communication")
        self.web_channel = QWebChannel()
        # Get message bus from parent window
        message_bus = None
        if hasattr(self.parent(), '_message_bus'):
            message_bus = self.parent()._message_bus
            logger.info("Message bus found for WebChannel")
        else:
            logger.warning("No message bus available for WebChannel - overlay communication may be limited")

        self.overlay_channel = OverlayWebChannel(db_manager=self.db_manager, message_bus=message_bus)
        self.web_channel.registerObject("overlay", self.overlay_channel)
        page.setWebChannel(self.web_channel)
        logger.info("WebChannel setup completed - overlay object registered as 'overlay'")

        # Monitor GPU processes after WebEngine initialization
        self._monitor_gpu_processes()

        # Console override moved to _enable_debug_console to ensure it runs after page load

        # Note: Console message capturing via WebChannel is now handled by JavaScript overrides
        # The javaScriptConsoleMessage signal connection was removed as it was failing

        # Load default template
        logger.info("DEBUG: About to load default template in OverlayWebView.setup_web_view")
        self.load_template(self.current_template)

        # Show debug console if debug mode is enabled
        if self.debug_overlay:
            # Connect to load finished signal to enable debug console
            logger.info("DEBUG: Connecting loadFinished signal to _enable_debug_console")
            self.page().loadFinished.connect(self._enable_debug_console)

        # Add overlay visibility monitoring
        logger.info("DEBUG: Setting up overlay visibility monitoring")
        QTimer.singleShot(1000, self._check_overlay_visibility)
        QTimer.singleShot(5000, self._check_overlay_visibility)
        QTimer.singleShot(10000, self._check_overlay_visibility)

        # Check Qt WebEngine GPU acceleration status
        QTimer.singleShot(2000, self._check_qt_webengine_gpu_status)

        logger.info("OverlayWebView setup completed successfully")

    def _enable_debug_console(self, ok=None):
        """Enable debug console and ensure JavaScript overrides are active"""
        try:
            if ok is not None and not ok:
                logger.warning("Page failed to load, cannot enable debug console")
                return

            logger.info("DEBUG: _enable_debug_console called")
            logger.info("Enabling debug console for JavaScript logging")

            # Enable Qt WebEngine developer tools
            page = self.page()
            if page:
                # Try to connect to Qt WebEngine console messages (may not be available in all Qt versions)
                try:
                    # Check if javaScriptConsoleMessage is available and is a signal
                    if hasattr(page, 'javaScriptConsoleMessage') and hasattr(page.javaScriptConsoleMessage, 'connect'):
                        page.javaScriptConsoleMessage.connect(self._on_javaScript_console_message)
                        logger.info("Connected to Qt WebEngine console messages")
                    else:
                        logger.debug("Qt WebEngine console messages not available or not a signal in this Qt version")
                except Exception as e:
                    logger.debug(f"Could not connect to Qt WebEngine console messages (this is normal in some Qt configurations): {e}")

                # Override console.log in JavaScript to send messages to Python
                page.runJavaScript("""
                    (function() {
                        if (typeof window.qtConsoleOverride === 'undefined') {
                            window.qtConsoleOverride = true;

                            var originalLog = console.log;
                            var originalError = console.error;
                            var originalWarn = console.warn;
                            var originalInfo = console.info;

                            // Override console methods - WebChannel should be ready
                            console.log = function(...args) {
                                var message = args.map(String).join(' ');
                                if (window.overlay && window.overlay.log) {
                                    window.overlay.log('[LOG] ' + message);
                                }
                                originalLog.apply(console, args);
                            };

                            console.error = function(...args) {
                                var message = args.map(String).join(' ');
                                if (window.overlay && window.overlay.log) {
                                    window.overlay.log('[ERROR] ' + message);
                                }
                                originalError.apply(console, args);
                            };

                            console.warn = function(...args) {
                                var message = args.map(String).join(' ');
                                if (window.overlay && window.overlay.log) {
                                    window.overlay.log('[WARN] ' + message);
                                }
                                originalWarn.apply(console, args);
                            };

                            console.info = function(...args) {
                                var message = args.map(String).join(' ');
                                if (window.overlay && window.overlay.log) {
                                    window.overlay.log('[INFO] ' + message);
                                }
                                originalInfo.apply(console, args);
                            };

                            console.debug = function(...args) {
                                var message = args.map(String).join(' ');
                                if (window.overlay && window.overlay.log) {
                                    window.overlay.log('[DEBUG] ' + message);
                                }
                                originalLog.apply(console, args);
                            };

                            // Test the override
                            console.log('JavaScript console override active - Qt WebEngine debug console enabled');
                        }
                    })();
                """)

                logger.info("Debug console enabled - JavaScript console override injected")

        except Exception as e:
            logger.error(f"Failed to enable debug console: {e}")

    def _on_javaScript_console_message(self, level, message, line, source):
        """Handle Qt WebEngine console messages"""
        try:
            logger.info(f"DEBUG: JavaScript console message received: {level} - {message}")
            level_name = {
                0: 'INFO',
                1: 'WARNING',
                2: 'ERROR'
            }.get(level, 'UNKNOWN')

            logger.info(f"[QtWebEngine {level_name}] {source}:{line} - {message}")

            # Also send to our WebChannel if available
            if hasattr(self, 'overlay_channel') and self.overlay_channel:
                self.overlay_channel.log(f"[QtWebEngine {level_name}] {source}:{line} - {message}")

        except Exception as e:
            logger.error(f"Failed to handle JavaScript console message: {e}")

    def _setup_custom_scheme(self):
        """Setup custom URL scheme handler for overlay resources"""
        try:
            # Register the custom scheme before installing the handler
            from PyQt6.QtWebEngineCore import QWebEngineUrlScheme
            overlay_scheme = QWebEngineUrlScheme(b"overlay")
            overlay_scheme.setSyntax(QWebEngineUrlScheme.Syntax.HostAndPort)
            overlay_scheme.setDefaultPort(0)
            overlay_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
                                   QWebEngineUrlScheme.Flag.LocalScheme |
                                   QWebEngineUrlScheme.Flag.LocalAccessAllowed)
            QWebEngineUrlScheme.registerScheme(overlay_scheme)

            logger.info("Custom overlay:// URL scheme registered successfully")

            # Get the page's profile
            profile = self.page().profile()

            # Create and install URL scheme handler
            self.scheme_handler = OverlayUrlSchemeHandler(self)
            profile.installUrlSchemeHandler(b"overlay", self.scheme_handler)

            logger.info("Custom overlay:// URL scheme handler installed successfully")

        except Exception as e:
            logger.error(f"Failed to setup custom URL scheme: {e}")
    
    def load_template(self, template_name: str):
        """Load a specific template file, prioritizing uploaded templates"""
        try:
            logger.info(f"=== LOADING OVERLAY TEMPLATE: {template_name} ===")

            # Check if running in VirtualBox for additional diagnostics
            is_virtualbox = False
            try:
                with open('/sys/devices/virtual/dmi/id/sys_vendor', 'r') as f:
                    vendor = f.read().strip().lower()
                    if 'virtualbox' in vendor or 'innotek' in vendor:
                        is_virtualbox = True
            except:
                pass

            if is_virtualbox:
                logger.warning("VIRTUALBOX: Loading overlay template - potential compatibility issues")

            if self.debug_overlay:
                logger.debug(f"GREEN SCREEN DEBUG: Starting template load - {template_name}")
                logger.debug(f"GREEN SCREEN DEBUG: Current page URL before load: {self.url().toString()}")
                logger.debug(f"GREEN SCREEN DEBUG: WebEngine view visible: {self.isVisible()}")

            # CRITICAL FIX: Store visibility state before template load
            was_visible = self.isVisible()
            logger.info(f"Overlay visibility before template load: {was_visible}")

            # If no template name provided, use default
            if not template_name:
                template_name = "default.html"

            # Ensure .html extension
            if not template_name.endswith('.html'):
                template_name += '.html'

            logger.info(f"Template name resolved to: {template_name}")

            # First try uploaded templates directory (user uploads take priority)
            template_path = self.uploaded_templates_dir / template_name
            template_source = "uploaded"
            logger.info(f"Checking uploaded templates: {template_path} (exists: {template_path.exists()})")

            # If not found in uploaded, try built-in templates
            if not template_path.exists():
                template_path = self.builtin_templates_dir / template_name
                template_source = "builtin"
                logger.info(f"Checking builtin templates: {template_path} (exists: {template_path.exists()})")

            # If still not found, fallback to default.html in built-in templates
            if not template_path.exists():
                default_template_path = self.builtin_templates_dir / "default.html"
                logger.info(f"Template not found, trying fallback: {default_template_path} (exists: {default_template_path.exists()})")
                if default_template_path.exists():
                    template_path = default_template_path
                    template_name = "default.html"
                    template_source = "builtin"
                    logger.warning(f"Template {template_name} not found, using default.html")
                else:
                    logger.error(f"No template found: {template_name}, and default.html missing")
                    # Load fallback minimal overlay
                    self._load_fallback_overlay()
                    return

            if template_path and template_path.exists():
                logger.info(f"TEMPLATE FOUND: {template_path} (source: {template_source})")

                # Check template file permissions and size
                try:
                    stat_info = template_path.stat()
                    logger.info(f"Template file size: {stat_info.st_size} bytes")
                    logger.info(f"Template file permissions: {oct(stat_info.st_mode)}")
                    logger.info(f"Template file readable: {template_path.stat().st_mode & 0o400 != 0}")
                except Exception as stat_error:
                    logger.warning(f"Could not check template file stats: {stat_error}")

                if self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: About to load template file: {template_path}")
                    logger.debug(f"GREEN SCREEN DEBUG: Template source: {template_source}")

                    # Check WebEngine state before load
                    page = self.page()
                    if page:
                        logger.debug(f"GREEN SCREEN DEBUG: Page background color before load: {page.backgroundColor()}")

                # CRITICAL FIX: Add safety check for PyInstaller bundle template access
                import sys
                if getattr(sys, 'frozen', False) and template_source == "builtin":
                    # Running in PyInstaller bundle - verify template path is accessible
                    try:
                        # Test if we can read the template file
                        with open(template_path, 'r', encoding='utf-8') as f:
                            content = f.read()
                            logger.info(f"TEMPLATE CONTENT LENGTH: {len(content)} characters")
                            if len(content) == 0:
                                logger.warning(f"Template file {template_path} is empty, using fallback")
                                self._load_fallback_overlay()
                                return
                    except Exception as file_error:
                        logger.error(f"Cannot read template file {template_path} in bundle: {file_error}")
                        logger.warning("Using fallback overlay due to template access issue")
                        self._load_fallback_overlay()
                        return

                # Check WebEngine page state before loading
                page = self.page()
                if page:
                    logger.info("WebEngine page state before template load:")
                    logger.info(f"  - Page URL: {page.url().toString()}")
                    logger.info(f"  - Page title: {page.title()}")
                    logger.info(f"  - Page background color: {page.backgroundColor()}")
                    logger.info(f"  - WebChannel available: {page.webChannel() is not None}")
                else:
                    logger.warning("No WebEngine page available before template load")

                logger.info(f"LOADING TEMPLATE INTO WEBENGINE: {template_path}")
                load_start_time = time.time()
                self.load(QUrl.fromLocalFile(str(template_path)))
                load_end_time = time.time()
                logger.info(f"Template load initiated in {load_end_time - load_start_time:.3f} seconds")

                self.current_template = template_name

                # CRITICAL FIX: Force visibility recovery after template load
                if was_visible and not self.isVisible():
                    logger.warning("Overlay lost visibility after template load, recovering")
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN FIX: Recovering overlay visibility after template load")
                    self.show()
                    self.raise_()

                # CRITICAL FIX: Schedule additional visibility check after page load
                from PyQt6.QtCore import QTimer
                QTimer.singleShot(100, lambda: self._ensure_overlay_visibility_post_load(was_visible))

                # If fixtures or match template was loaded, the template handles its own data fetching via WebChannel
                if template_name == "fixtures.html" or template_name == "fixtures" or template_name == "match.html" or template_name == "match":
                    template_type = "fixtures" if ("fixtures" in template_name) else "match"
                    logger.info(f"{template_type.title()} template loaded - template handles its own data fetching via WebChannel")
                    # Send webServerBaseUrl to the template for WebChannel setup
                    logger.info(f"Sending webServerBaseUrl to {template_type} template: {self.web_server_url}")
                    data_to_send = {'webServerBaseUrl': self.web_server_url}
                    if self.debug_overlay:
                        data_to_send['debugMode'] = True
                        logger.info(f"Debug mode enabled for {template_type} template")
                    self.update_overlay_data(data_to_send)

                # Ensure console override is active after template load
                if self.debug_overlay:
                    self._enable_debug_console()
                QTimer.singleShot(200, lambda: self._ensure_console_override())

                if self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}")
                logger.info(f"=== TEMPLATE LOAD COMPLETED: {template_path} (source: {template_source}) ===")

                # Schedule a check for template load success
                QTimer.singleShot(1000, lambda: self._check_template_load_success(template_name))

            else:
                logger.error(f"No template found: {template_name}")
                # Load fallback minimal overlay
                self._load_fallback_overlay()

        except Exception as e:
            if self.debug_overlay:
                logger.debug(f"GREEN SCREEN DEBUG: Template load failed: {e}")
            logger.error(f"Failed to load template {template_name}: {e}")
            self._load_fallback_overlay()
    
    def _ensure_overlay_visibility_post_load(self, should_be_visible: bool):
        """Ensure overlay maintains proper visibility after template load"""
        try:
            if should_be_visible and not self.isVisible():
                logger.debug(f"GREEN SCREEN FIX: Overlay lost visibility after template load, forcing recovery")
                self.show()
                self.raise_()
                logger.debug(f"GREEN SCREEN FIX: Overlay visibility recovered")
            
            # Check parent overlay window visibility as well
            parent = self.parent()
            if parent and should_be_visible and not parent.isVisible():
                logger.debug(f"GREEN SCREEN FIX: Parent overlay window lost visibility, forcing recovery")
                parent.show()
                parent.raise_()
                logger.debug(f"GREEN SCREEN FIX: Parent overlay window visibility recovered")
                
            # CRITICAL FIX: Protect video rendering context during template operations
            self._protect_video_context_during_template_load()
                
        except Exception as e:
            logger.error(f"GREEN SCREEN FIX: Failed to ensure overlay visibility: {e}")
    
    def _protect_video_context_during_template_load(self):
        """Protect video rendering context during template loading operations"""
        try:
            if hasattr(self, 'parent') and self.parent():
                parent_window = self.parent()
                if hasattr(parent_window, 'media_player') and parent_window.media_player:
                    logger.debug(f"GREEN SCREEN FIX: Protecting video context during template load")
                    media_player = parent_window.media_player
                    
                    # Store current video output
                    current_output = media_player.videoOutput()
                    if current_output:
                        logger.debug(f"GREEN SCREEN FIX: Video output preserved during template load")
                    
                    # Force repaint of video widget
                    if hasattr(parent_window, 'video_widget') and parent_window.video_widget:
                        video_widget = parent_window.video_widget.get_video_widget()
                        if video_widget:
                            video_widget.repaint()
                            video_widget.update()
                            logger.debug(f"GREEN SCREEN FIX: Video widget repainted during template load")
        except Exception as e:
            logger.debug(f"GREEN SCREEN FIX: Failed to protect video context: {e}")
    
    def reload_current_template(self):
        """Reload the current template"""
        if self.debug_overlay:
            logger.debug(f"GREEN SCREEN DEBUG: Reloading current template - {self.current_template}")
            logger.debug(f"GREEN SCREEN DEBUG: WebEngine state before reload - visible: {self.isVisible()}")
        self.load_template(self.current_template)
        if self.debug_overlay:
            logger.debug(f"GREEN SCREEN DEBUG: Current template reload initiated")
    
    def get_available_templates(self) -> List[str]:
        """Get list of available template files from both directories"""
        try:
            templates = set()
            
            # Get built-in templates
            if self.builtin_templates_dir.exists():
                builtin_templates = [f.name for f in self.builtin_templates_dir.glob("*.html")]
                templates.update(builtin_templates)
            
            # Get uploaded templates (these override built-in ones with same name)
            if self.uploaded_templates_dir.exists():
                uploaded_templates = [f.name for f in self.uploaded_templates_dir.glob("*.html")]
                templates.update(uploaded_templates)
            
            template_list = sorted(list(templates))
            if not template_list:
                template_list = ["default.html"]
            
            return template_list
            
        except Exception as e:
            logger.error(f"Failed to get available templates: {e}")
            return ["default.html"]
    
    def _get_persistent_templates_dir(self) -> Path:
        """Get persistent templates directory for user uploads"""
        try:
            import platform
            import os
            
            system = platform.system()
            
            if system == "Windows":
                # Use AppData/Roaming on Windows
                app_data = os.getenv('APPDATA', os.path.expanduser('~'))
                templates_dir = Path(app_data) / "MbetterClient" / "templates"
            elif system == "Darwin":  # macOS
                # Use ~/Library/Application Support on macOS
                templates_dir = Path.home() / "Library" / "Application Support" / "MbetterClient" / "templates"
            else:  # Linux and other Unix-like systems
                # Use ~/.config on Linux
                config_home = os.getenv('XDG_CONFIG_HOME', str(Path.home() / ".config"))
                templates_dir = Path(config_home) / "MbetterClient" / "templates"
            
            logger.debug(f"Persistent templates directory: {templates_dir}")
            return templates_dir
                
        except Exception as e:
            logger.error(f"Failed to determine persistent templates directory: {e}")
            # Fallback to local directory
            return Path.cwd() / "user_templates"
    
    def _get_default_template_path(self) -> Optional[Path]:
        """Get default template path from templates directory"""
        try:
            # Always use default.html from built-in templates directory
            default_template_path = self.builtin_templates_dir / "default.html"
            logger.debug(f"Default template path: {default_template_path}")
            return default_template_path
                
        except Exception as e:
            logger.error(f"Failed to determine default template path: {e}")
            return None
    
    def _load_fallback_overlay(self):
        """Load minimal fallback overlay if main overlay file not found"""
        try:
            fallback_html = """
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <style>
                    body {
                        margin: 0;
                        padding: 20px;
                        background: transparent;
                        color: white;
                        font-family: Arial, sans-serif;
                    }
                    .overlay-content {
                        position: absolute;
                        top: 20px;
                        left: 20px;
                        background: rgba(0,0,0,0.7);
                        padding: 10px;
                        border-radius: 5px;
                    }
                </style>
            </head>
            <body>
                <div class="overlay-content">
                    <h2>MbetterClient PyQt6 Player</h2>
                    <p>Overlay System Active</p>
                </div>
            </body>
            </html>
            """
            
            self.setHtml(fallback_html)
            logger.info("Loaded fallback overlay HTML")
            
        except Exception as e:
            logger.error(f"Failed to load fallback overlay: {e}")
    
    def get_overlay_channel(self) -> OverlayWebChannel:
        """Get the overlay communication channel"""
        return self.overlay_channel
    
    def update_overlay_data(self, data: Dict[str, Any]):
        """Update overlay with new data"""
        # Validate data before sending to prevent null emissions
        if not data:
            logger.warning("update_overlay_data called with null/empty data, skipping")
            return
            
        if self.overlay_channel:
            self.overlay_channel.send_data_update(data)
    
    def update_position(self, position: float, duration: float):
        """Update playback position"""
        if self.overlay_channel:
            self.overlay_channel.send_position_update(position, duration)
    
    def update_video_info(self, info: Dict[str, Any]):
        """Update video information"""
        if self.overlay_channel:
            self.overlay_channel.send_video_info(info)

    def _ensure_console_override(self):
        """Ensure console override is active in the current page"""
        try:
            if hasattr(self, 'page') and self.page():
                self.page().runJavaScript("""
                    if (typeof window.overlay !== 'undefined' && window.overlay && window.overlay.log) {
                        if (!console.originalLog) {
                            console.originalLog = console.log;
                            console.originalError = console.error;
                            console.originalWarn = console.warn;
                            console.originalInfo = console.info;

                            console.log = function(...args) {
                                var message = args.map(String).join(' ');
                                window.overlay.log('[LOG] ' + message);
                                console.originalLog.apply(console, args);
                            };

                            console.error = function(...args) {
                                var message = args.map(String).join(' ');
                                window.overlay.log('[ERROR] ' + message);
                                console.originalError.apply(console, args);
                            };

                            console.warn = function(...args) {
                                var message = args.map(String).join(' ');
                                window.overlay.log('[WARN] ' + message);
                                console.originalWarn.apply(console, args);
                            };

                            console.info = function(...args) {
                                var message = args.map(String).join(' ');
                                window.overlay.log('[INFO] ' + message);
                                console.originalInfo.apply(console, args);
                            };
                        }
                    }
                """)
                logger.debug("Console override ensured after template load")
        except Exception as e:
            logger.debug(f"Failed to ensure console override: {e}")

    def _monitor_gpu_processes(self):
        """Async GPU process monitoring for usability checking"""
        try:
            logger.debug("Starting async GPU process monitoring")

            # Use QTimer for non-blocking GPU monitoring
            from PyQt6.QtCore import QTimer

            def check_gpu_async():
                """Non-blocking GPU process check"""
                try:
                    import subprocess
                    import os

                    # Check for GPU processes that might interfere with video playback
                    gpu_processes = []

                    # Check for known GPU-intensive processes
                    processes_to_check = ['chrome', 'chromium', 'firefox', 'opera', 'safari']

                    for proc_name in processes_to_check:
                        try:
                            # Use pgrep or tasklist to check for running processes
                            if os.name == 'nt':  # Windows
                                result = subprocess.run(['tasklist', '/FI', f'IMAGENAME eq {proc_name}.exe'],
                                                      capture_output=True, text=True, timeout=2)
                                if proc_name.lower() in result.stdout.lower():
                                    gpu_processes.append(proc_name)
                            else:  # Unix-like systems
                                result = subprocess.run(['pgrep', '-f', proc_name],
                                                      capture_output=True, text=True, timeout=2)
                                if result.returncode == 0:
                                    gpu_processes.append(proc_name)
                        except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
                            # Commands may not be available, continue
                            pass

                    if gpu_processes:
                        logger.info(f"GPU-intensive processes detected: {', '.join(gpu_processes)}")
                        logger.warning("Multiple GPU processes may impact video playback performance")
                    else:
                        logger.debug("No conflicting GPU processes detected")

                    # Check GPU memory usage if nvidia-smi is available
                    try:
                        nvidia_result = subprocess.run(['nvidia-smi', '--query-gpu=memory.used,memory.total',
                                                      '--format=csv,noheader,nounits'],
                                                     capture_output=True, text=True, timeout=3)
                        if nvidia_result.returncode == 0:
                            lines = nvidia_result.stdout.strip().split('\n')
                            for i, line in enumerate(lines):
                                if ',' in line:
                                    used, total = line.split(',')
                                    used = int(used.strip())
                                    total = int(total.strip())
                                    usage_percent = (used / total) * 100 if total > 0 else 0
                                    logger.debug(f"GPU {i} memory usage: {used}MB/{total}MB ({usage_percent:.1f}%)")
                                    if usage_percent > 90:
                                        logger.warning(f"GPU {i} memory usage is very high: {usage_percent:.1f}%")
                    except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
                        # nvidia-smi not available or failed
                        pass

                except Exception as e:
                    logger.debug(f"GPU monitoring check failed: {e}")

            # Schedule the first check after a delay, then periodically
            QTimer.singleShot(2000, check_gpu_async)  # First check after 2 seconds

            # Set up periodic monitoring every 30 seconds
            self._gpu_monitor_timer = QTimer()
            self._gpu_monitor_timer.timeout.connect(check_gpu_async)
            self._gpu_monitor_timer.start(30000)  # 30 seconds

            logger.debug("GPU process monitoring scheduled")

        except Exception as e:
            logger.error(f"Failed to start GPU process monitoring: {e}")

    def _check_template_load_success(self, expected_template: str):
        """Check if template loaded successfully"""
        try:
            logger.info(f"=== TEMPLATE LOAD SUCCESS CHECK: {expected_template} ===")

            page = self.page()
            if page:
                current_url = page.url().toString()
                logger.info(f"Current page URL: {current_url}")
                logger.info(f"Expected template: {expected_template}")

                # Check if URL contains the expected template
                if expected_template in current_url or expected_template.replace('.html', '') in current_url:
                    logger.info("✓ Template URL matches expected template")
                else:
                    logger.warning(f"✗ Template URL mismatch - expected {expected_template}, got {current_url}")

                # Check page title
                title = page.title()
                logger.info(f"Page title: '{title}'")

                # Run JavaScript to check page content
                page.runJavaScript("""
                    console.log('TEMPLATE CHECK: Page content validation');
                    console.log('Document readyState:', document.readyState);
                    console.log('Body exists:', !!document.body);
                    if (document.body) {
                        console.log('Body children count:', document.body.children.length);
                        console.log('Body innerHTML length:', document.body.innerHTML.length);
                        console.log('Body background:', window.getComputedStyle(document.body).background);
                        console.log('Body display:', window.getComputedStyle(document.body).display);
                        console.log('Body visibility:', window.getComputedStyle(document.body).visibility);

                        // Check for critical overlay elements
                        const criticalElements = ['titleMain', 'titleSubtitle', 'tickerText'];
                        criticalElements.forEach(id => {
                            const el = document.getElementById(id);
                            console.log(`Element ${id}: ${el ? 'EXISTS' : 'MISSING'}`);
                            if (el) {
                                console.log(`  - display: ${window.getComputedStyle(el).display}`);
                                console.log(`  - visibility: ${window.getComputedStyle(el).visibility}`);
                                console.log(`  - textContent: '${el.textContent}'`);
                            }
                        });
                    }
                    console.log('TEMPLATE CHECK: Validation complete');
                """)

                # Check WebChannel status
                web_channel = page.webChannel()
                logger.info(f"WebChannel available: {web_channel is not None}")
                if web_channel:
                    logger.info("WebChannel is properly configured")
                else:
                    logger.warning("WebChannel not available - overlay communication will fail")

            else:
                logger.error("No WebEngine page available for template check")

            logger.info(f"=== END TEMPLATE LOAD SUCCESS CHECK ===")

        except Exception as e:
            logger.error(f"Failed to check template load success: {e}")

    def _check_overlay_visibility(self):
        """Check overlay window visibility and positioning"""
        try:
            logger.info("=== OVERLAY VISIBILITY CHECK ===")

            # Check if running in VirtualBox
            is_virtualbox = False
            try:
                with open('/sys/devices/virtual/dmi/id/sys_vendor', 'r') as f:
                    vendor = f.read().strip().lower()
                    if 'virtualbox' in vendor or 'innotek' in vendor:
                        is_virtualbox = True
                        logger.warning("VIRTUALBOX: Running overlay visibility check")
            except:
                pass

            logger.info(f"Overlay window exists: {self.parent() is not None}")
            if self.parent():
                parent = self.parent()
                logger.info(f"Parent window geometry: {parent.geometry()}")
                logger.info(f"Parent window visible: {parent.isVisible()}")
                logger.info(f"Parent window on top: {parent.windowFlags() & Qt.WindowType.WindowStaysOnTopHint}")

            logger.info(f"Overlay WebView visible: {self.isVisible()}")
            logger.info(f"Overlay WebView geometry: {self.geometry()}")
            logger.info(f"Overlay WebView size: {self.size()}")
            logger.info(f"Overlay WebView pos: {self.pos()}")

            # Check if overlay is properly positioned over parent
            if self.parent():
                parent_geo = self.parent().geometry()
                overlay_geo = self.geometry()
                if parent_geo == overlay_geo:
                    logger.info("✓ Overlay properly positioned over parent window")
                else:
                    logger.warning(f"✗ Overlay positioning mismatch - parent: {parent_geo}, overlay: {overlay_geo}")

            # Check WebEngine page status
            page = self.page()
            if page:
                logger.info(f"WebEngine page URL: {page.url().toString()}")
                logger.info(f"WebEngine page title: {page.title()}")

                # Check if page has content
                page.runJavaScript("""
                    console.log('OVERLAY VISIBILITY: Checking page content');
                    console.log('Document readyState:', document.readyState);
                    console.log('Body exists:', !!document.body);
                    if (document.body) {
                        console.log('Body innerHTML length:', document.body.innerHTML.length);
                        console.log('Body background:', window.getComputedStyle(document.body).background);
                        console.log('Body opacity:', window.getComputedStyle(document.body).opacity);
                        console.log('Body display:', window.getComputedStyle(document.body).display);
                        console.log('Body visibility:', window.getComputedStyle(document.body).visibility);

                        // Check for VirtualBox-specific issues
                        console.log('Window devicePixelRatio:', window.devicePixelRatio);
                        console.log('Canvas support:', !!document.createElement('canvas').getContext);
                        console.log('WebGL support check:');
                        try {
                            const canvas = document.createElement('canvas');
                            const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
                            console.log('  WebGL available:', !!gl);
                            if (gl) {
                                console.log('  WebGL renderer:', gl.getParameter(gl.RENDERER));
                                console.log('  WebGL vendor:', gl.getParameter(gl.VENDOR));
                            }
                        } catch (e) {
                            console.log('  WebGL check failed:', e.message);
                        }
                    }
                    console.log('OVERLAY VISIBILITY: Page check complete');
                """)

            # Check GPU processes specifically for Qt WebEngine
            try:
                import subprocess
                result = subprocess.run(['nvidia-smi', 'pmon'], capture_output=True, text=True, timeout=5)
                if result.returncode == 0:
                    lines = result.stdout.strip().split('\n')
                    qt_processes = [line for line in lines if 'QtWebEngine' in line or 'python' in line.lower()]
                    if qt_processes:
                        logger.info(f"Qt WebEngine GPU processes found: {len(qt_processes)}")
                        for proc in qt_processes:
                            logger.info(f"GPU Process: {proc}")
                    else:
                        logger.warning("NO Qt WebEngine GPU processes found - overlay may not be using GPU acceleration")
                        if is_virtualbox:
                            logger.warning("VIRTUALBOX: This is expected - VirtualBox typically disables GPU acceleration")
                else:
                    logger.warning("Could not check GPU processes for Qt WebEngine")
                    if is_virtualbox:
                        logger.warning("VIRTUALBOX: GPU process check failed - common in VirtualBox environment")
            except Exception as e:
                logger.debug(f"GPU process check failed: {e}")
                if is_virtualbox:
                    logger.warning("VIRTUALBOX: GPU monitoring not available - this is normal")

            # Check for VirtualBox-specific display issues
            if is_virtualbox:
                logger.info("VIRTUALBOX: Checking for display compatibility issues")
                try:
                    import os
                    display = os.environ.get('DISPLAY', 'not set')
                    logger.info(f"VIRTUALBOX: DISPLAY environment: {display}")

                    # Check if X11 forwarding is working
                    if display.startswith(':'):
                        logger.info("VIRTUALBOX: Local X11 display detected")
                    elif display.startswith('localhost:') or display.startswith('127.0.0.1:'):
                        logger.warning("VIRTUALBOX: X11 forwarding detected - may cause overlay issues")
                    else:
                        logger.warning("VIRTUALBOX: Unusual DISPLAY setting - may indicate display issues")

                except Exception as e:
                    logger.debug(f"VIRTUALBOX: Display check failed: {e}")

            logger.info("=== END OVERLAY VISIBILITY CHECK ===")

        except Exception as e:
            logger.error(f"Failed to check overlay visibility: {e}")

    def _check_qt_webengine_gpu_status(self):
        """Check Qt WebEngine GPU acceleration status"""
        try:
            logger.info("=== QT WEBENGINE GPU ACCELERATION CHECK ===")

            # Check Qt WebEngine version and GPU flags
            from PyQt6.QtWebEngineCore import QWebEngineProfile
            profile = self.page().profile()
            logger.info(f"Qt WebEngine profile: {profile.storageName()}")

            # Check if GPU acceleration is enabled via JavaScript
            page = self.page()
            if page:
                page.runJavaScript("""
                    console.log('QT WEBENGINE GPU CHECK: Starting GPU acceleration test');

                    // Check WebGL support
                    const canvas = document.createElement('canvas');
                    const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
                    if (gl) {
                        console.log('QT WEBENGINE GPU: WebGL is supported');
                        const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
                        if (debugInfo) {
                            const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
                            const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
                            console.log('QT WEBENGINE GPU: Renderer - ' + renderer);
                            console.log('QT WEBENGINE GPU: Vendor - ' + vendor);
                        } else {
                            console.log('QT WEBENGINE GPU: Debug renderer info not available');
                        }
                    } else {
                        console.log('QT WEBENGINE GPU: WebGL is NOT supported - GPU acceleration disabled');
                    }

                    // Check for software rendering indicators
                    const isSoftware = navigator.userAgent.includes('Mesa') ||
                                     document.body.style.backgroundColor.includes('rgba(0,0,0,0.01)');
                    console.log('QT WEBENGINE GPU: Software rendering detected: ' + isSoftware);

                    console.log('QT WEBENGINE GPU CHECK: Complete');
                """)

            # Check environment variables that affect GPU acceleration
            import os
            gpu_env_vars = [
                'QTWEBENGINE_CHROMIUM_FLAGS',
                'QT_QPA_PLATFORM',
                'LIBGL_ALWAYS_SOFTWARE',
                'MESA_GL_VERSION_OVERRIDE',
                'QTWEBENGINE_DISABLE_GPU'
            ]

            logger.info("Qt WebEngine GPU environment variables:")
            for var in gpu_env_vars:
                value = os.environ.get(var)
                logger.info(f"  {var}: {value if value else '<not set>'}")

            # Check if GPU blacklist is active
            try:
                # Try to run a simple GPU test
                page.runJavaScript("""
                    // Test GPU compositing
                    const testDiv = document.createElement('div');
                    testDiv.style.width = '100px';
                    testDiv.style.height = '100px';
                    testDiv.style.background = 'linear-gradient(45deg, red, blue)';
                    testDiv.style.position = 'absolute';
                    testDiv.style.top = '-200px'; // Off-screen
                    testDiv.id = 'gpu-test-element';
                    document.body.appendChild(testDiv);

                    // Check if transform3d is hardware accelerated
                    testDiv.style.transform = 'translate3d(0, 0, 0)';
                    const styles = window.getComputedStyle(testDiv);
                    const transform = styles.transform;
                    console.log('QT WEBENGINE GPU: 3D transform support: ' + (transform.includes('matrix') || transform.includes('translate3d')));

                    // Clean up
                    setTimeout(() => {
                        if (document.getElementById('gpu-test-element')) {
                            document.body.removeChild(testDiv);
                        }
                    }, 1000);
                """)
            except Exception as e:
                logger.debug(f"GPU compositing test failed: {e}")

            logger.info("=== END QT WEBENGINE GPU ACCELERATION CHECK ===")

        except Exception as e:
            logger.error(f"Failed to check Qt WebEngine GPU status: {e}")

    # Removed _on_javaScript_console_message method as console capturing now uses WebChannel








class VideoWidget(QWidget):
    """Video widget for Qt multimedia player"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setup_ui()
        logger.info("VideoWidget initialized")
    
    def setup_ui(self):
        """Setup video player - ONLY video widget, overlay handled by PlayerWindow"""
        logger.debug("VideoWidget.setup_ui() - Setting up video-only widget")
        
        # Use a simple layout for the video widget only
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        
        # ONLY the video widget - no overlays here
        self.video_widget = QVideoWidget(self)
        self.video_widget.setStyleSheet("QVideoWidget { background-color: black; }")
        layout.addWidget(self.video_widget)
        
        # No overlay_view created here - handled by PlayerWindow as separate window
        self.overlay_view = None
        
        logger.debug("VideoWidget setup completed - video only, overlay handled separately")
    
    def get_video_widget(self) -> QVideoWidget:
        """Get the video widget for media player"""
        return self.video_widget
    
    def get_overlay_view(self):
        """Get the overlay view - now handled by PlayerWindow as separate window"""
        return None  # Overlay is now a separate top-level window


class PlayerControlsWidget(QWidget):
    """Enhanced video player controls with thread-safe operations"""
    
    play_pause_clicked = pyqtSignal()
    stop_clicked = pyqtSignal()
    seek_requested = pyqtSignal(int)
    volume_changed = pyqtSignal(int)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.mutex = QMutex()
        self.setup_ui()
    
    def setup_ui(self):
        """Setup enhanced control UI"""
        layout = QHBoxLayout(self)
        layout.setContentsMargins(10, 5, 10, 5)
        
        # Play/Pause button
        self.play_pause_btn = QPushButton("⏸️")
        self.play_pause_btn.setFixedSize(40, 30)
        self.play_pause_btn.clicked.connect(self.play_pause_clicked.emit)
        
        # Stop button
        self.stop_btn = QPushButton("⏹️")
        self.stop_btn.setFixedSize(40, 30)
        self.stop_btn.clicked.connect(self.stop_clicked.emit)
        
        # Position slider
        self.position_slider = QSlider(Qt.Orientation.Horizontal)
        self.position_slider.setMinimum(0)
        self.position_slider.setMaximum(100)
        self.position_slider.sliderPressed.connect(self._on_slider_pressed)
        self.position_slider.sliderReleased.connect(self._on_slider_released)
        
        # Volume slider
        self.volume_slider = QSlider(Qt.Orientation.Horizontal)
        self.volume_slider.setMinimum(0)
        self.volume_slider.setMaximum(100)
        self.volume_slider.setValue(100)
        self.volume_slider.setMaximumWidth(100)
        self.volume_slider.valueChanged.connect(self.volume_changed.emit)
        
        # Time labels
        self.current_time_label = QLabel("00:00")
        self.duration_label = QLabel("00:00")
        
        # Volume label
        volume_label = QLabel("🔊")
        
        # Layout
        layout.addWidget(self.play_pause_btn)
        layout.addWidget(self.stop_btn)
        layout.addWidget(self.current_time_label)
        layout.addWidget(self.position_slider, 1)  # Stretch
        layout.addWidget(self.duration_label)
        layout.addWidget(volume_label)
        layout.addWidget(self.volume_slider)
        
        # Enhanced styling
        self.setStyleSheet("""
            QWidget {
                background-color: rgba(0, 0, 0, 200);
                color: white;
            }
            QPushButton {
                border: 1px solid #555;
                border-radius: 5px;
                background-color: #333;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #555;
            }
            QPushButton:pressed {
                background-color: #777;
            }
            QSlider::groove:horizontal {
                border: 1px solid #555;
                height: 6px;
                background: #222;
                border-radius: 3px;
            }
            QSlider::handle:horizontal {
                background: #fff;
                border: 2px solid #555;
                width: 14px;
                margin: -5px 0;
                border-radius: 7px;
            }
            QSlider::handle:horizontal:hover {
                background: #ddd;
            }
        """)
        
        self.slider_pressed = False
    
    def _on_slider_pressed(self):
        with QMutexLocker(self.mutex):
            self.slider_pressed = True
    
    def _on_slider_released(self):
        with QMutexLocker(self.mutex):
            self.slider_pressed = False
            self.seek_requested.emit(self.position_slider.value())
    
    def update_position(self, position: int, duration: int):
        """Update position display (thread-safe)"""
        with QMutexLocker(self.mutex):
            if not self.slider_pressed and duration > 0:
                self.position_slider.setValue(int(position * 100 / duration))
            
            self.current_time_label.setText(self._format_time(position))
            self.duration_label.setText(self._format_time(duration))
    
    def update_play_pause_button(self, is_playing: bool):
        """Update play/pause button state (thread-safe)"""
        with QMutexLocker(self.mutex):
            self.play_pause_btn.setText("⏸️" if is_playing else "▶️")
    
    def _format_time(self, ms: int) -> str:
        """Format time in milliseconds to MM:SS"""
        seconds = ms // 1000
        minutes = seconds // 60
        seconds = seconds % 60
        return f"{minutes:02d}:{seconds:02d}"


class PlayerWindow(QMainWindow):
    """Enhanced main player window with thread-safe operations"""
    
    # Signals for thread communication
    position_changed = pyqtSignal(int, int)
    video_loaded = pyqtSignal(str)
    
    def __init__(self, settings: QtConfig, message_bus: MessageBus = None, debug_overlay: bool = False, qt_player=None):
        super().__init__()
        self.settings = settings
        self.debug_overlay = debug_overlay
        self.mutex = QMutex()
        self.thread_pool = QThreadPool()
        self.thread_pool.setMaxThreadCount(4)
        self._message_bus = message_bus  # Store message bus reference for shutdown messages
        self.qt_player = qt_player  # Reference to parent QtVideoPlayer for message sending
        
        self.setup_ui()
        self.setup_media_player()
        self.setup_timers()
        
        logger.info("PlayerWindow initialized")
    
    def _get_web_server_base_url(self) -> str:
        """Get the web server base URL for API requests"""
        try:
            # Try to get the URL from the parent QtVideoPlayer component
            if hasattr(self, '_message_bus') and self._message_bus:
                # Look for the qt_player component in the message bus
                qt_player_queue = self._message_bus._queues.get('qt_player')
                if qt_player_queue and hasattr(qt_player_queue, 'component'):
                    qt_player = qt_player_queue.component
                    if hasattr(qt_player, 'web_dashboard_url') and qt_player.web_dashboard_url:
                        logger.debug(f"Web server base URL from QtVideoPlayer: {qt_player.web_dashboard_url}")
                        return qt_player.web_dashboard_url

            # Fallback to defaults if not available
            base_url = "http://127.0.0.1:5001"
            logger.debug(f"Web server base URL using fallback: {base_url}")
            return base_url

        except Exception as e:
            logger.error(f"Failed to determine web server base URL: {e}")
            # Return default fallback
            return "http://127.0.0.1:5001"

    def _get_database_manager(self):
        """Get database manager from message bus"""
        try:
            if hasattr(self, '_message_bus') and self._message_bus:
                # Try to get db_manager from web_dashboard component
                try:
                    web_dashboard_queue = self._message_bus._queues.get('web_dashboard')
                    if web_dashboard_queue and hasattr(web_dashboard_queue, 'component'):
                        component = web_dashboard_queue.component
                        if hasattr(component, 'db_manager'):
                            logger.debug("PlayerWindow: Got db_manager from web_dashboard component")
                            return component.db_manager
                except Exception as e:
                    logger.debug(f"PlayerWindow: Could not get db_manager from message bus: {e}")

            # Fallback: create database manager directly
            from ..config.settings import get_user_data_dir
            from ..database.manager import DatabaseManager
            db_path = get_user_data_dir() / "mbetterclient.db"
            logger.debug(f"PlayerWindow: Creating database manager directly: {db_path}")
            db_manager = DatabaseManager(str(db_path))
            if db_manager.initialize():
                return db_manager
            else:
                logger.warning("PlayerWindow: Failed to initialize database manager")
                return None

        except Exception as e:
            logger.error(f"PlayerWindow: Failed to get database manager: {e}")
            return None
    
    def setup_ui(self):
        """Setup enhanced window UI"""
        self.setWindowTitle("MbetterClient - PyQt6 Video Player")
        
        # CLEAN INTERFACE: Remove all toolbars, menus, and status bars
        self.setMenuBar(None)  # Remove menu bar completely
        self.setStatusBar(None)  # Remove status bar
        self.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)  # Disable context menus
        
        # RADICAL APPROACH: Remove ALL transparency attributes and force complete opacity
        self.setAutoFillBackground(True)  # Critical: Fill background automatically
        
        # Use a simple black background color palette approach
        from PyQt6.QtGui import QPalette
        palette = self.palette()
        palette.setColor(QPalette.ColorRole.Window, QColor(0, 0, 0))  # Black background
        palette.setColor(QPalette.ColorRole.WindowText, QColor(255, 255, 255))  # White text
        palette.setColor(QPalette.ColorRole.Base, QColor(0, 0, 0))  # Black base
        self.setPalette(palette)
        
        # Simple black stylesheet
        self.setStyleSheet("QMainWindow { background-color: black; }")
        
        # Central widget - completely black and opaque
        central_widget = QWidget()
        central_widget.setAutoFillBackground(True)
        central_widget.setPalette(palette)
        central_widget.setStyleSheet("background-color: black;")
        self.setCentralWidget(central_widget)
        
        # Layout
        layout = QVBoxLayout(central_widget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # SIMPLE VIDEO WIDGET ONLY - overlay as separate top-level window
        logger.info("PlayerWindow: Using QWebEngineView overlay")

        # Create simple video widget without any overlay (VideoWidget no longer handles overlays)
        self.video_widget = VideoWidget(parent=central_widget)
        layout.addWidget(self.video_widget, 1)
        
        # Check if single window overlay mode is enabled
        if self.settings.overlay_single_window:
            # SINGLE WINDOW MODE: Overlay stacks on top of player widget using Qt widget transparency
            logger.info("=== CREATING SINGLE WINDOW OVERLAY ===")
            logger.info("Single window overlay mode enabled - overlay will stack on top of player widget")

            # Create overlay widget directly in central layout (no separate window)
            debug_overlay = getattr(self, 'debug_overlay', False)
            web_server_url = self._get_web_server_base_url()
            db_manager = self._get_database_manager()
            self.window_overlay = OverlayWebView(central_widget, debug_overlay=debug_overlay, web_server_url=web_server_url, db_manager=db_manager)
            logger.debug("PlayerWindow: Created QWebEngineView overlay as widget in central layout")

            # Configure widget-level transparency for single window mode
            import os
            is_mesa = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or \
                      os.environ.get('MESA_GL_VERSION_OVERRIDE') is not None

            if is_mesa:
                # Mesa compatibility - use composition-based transparency
                self.window_overlay.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
                self.window_overlay.setStyleSheet("background: rgba(0, 0, 0, 0.01);")
                logger.info("Mesa single window overlay configured with composition transparency")
            else:
                # Standard hardware transparency
                self.window_overlay.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
                self.window_overlay.setStyleSheet("background: transparent;")
                logger.info("Hardware single window overlay configured with native transparency")

            # Add overlay widget to central layout on top of video widget
            layout.addWidget(self.window_overlay, 1)  # Give it stretch to fill available space

            # No separate overlay window needed
            self.overlay_window = None
            logger.info("SINGLE WINDOW OVERLAY SYSTEM ENABLED: Overlay stacked on top of player widget")

        else:
            # MULTI-WINDOW MODE: Create overlay as SEPARATE TOP-LEVEL WINDOW (original behavior)
            logger.info("=== CREATING MULTI-WINDOW OVERLAY ===")
            self.overlay_window = QWidget()
            self.overlay_window.setWindowFlags(
                Qt.WindowType.FramelessWindowHint |
                Qt.WindowType.WindowStaysOnTopHint |
                Qt.WindowType.Tool
            )
            logger.info("Overlay window created with flags: Frameless, StaysOnTop, Tool")

            # Mesa-specific transparency handling
            import os
            is_mesa = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or \
                      os.environ.get('MESA_GL_VERSION_OVERRIDE') is not None

            if is_mesa:
                # Mesa compatibility - use composition-based transparency
                self.overlay_window.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
                self.overlay_window.setStyleSheet("background: rgba(0, 0, 0, 0.01);")
                logger.info("Mesa overlay window configured with composition transparency (WA_TranslucentBackground=False)")
            else:
                # Standard hardware transparency
                self.overlay_window.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
                self.overlay_window.setStyleSheet("background: transparent;")
                logger.info("Hardware overlay window configured with native transparency (WA_TranslucentBackground=True)")

            # Create overlay using QWebEngineView
            debug_overlay = getattr(self, 'debug_overlay', False)
            web_server_url = self._get_web_server_base_url()
            # Get database manager from message bus
            db_manager = self._get_database_manager()
            self.window_overlay = OverlayWebView(self.overlay_window, debug_overlay=debug_overlay, web_server_url=web_server_url, db_manager=db_manager)
            logger.debug("PlayerWindow: Created QWebEngineView overlay as separate window")

            # Layout for overlay window
            overlay_layout = QVBoxLayout(self.overlay_window)
            overlay_layout.setContentsMargins(0, 0, 0, 0)
            overlay_layout.addWidget(self.window_overlay)

            logger.info("MULTI-WINDOW OVERLAY SYSTEM ENABLED: Separate overlay window created")
        
        # Controls removed per user request - clean overlay-only interface
        self.controls = None

        # Window settings - FORCE window to display properly
        if self.settings.fullscreen:
            self.showFullScreen()
        else:
            self.resize(self.settings.window_width, self.settings.window_height)
            
        # CRITICAL: Force window display and painting
        self.show()
        self.raise_()  # Bring to front
        self.activateWindow()  # Give focus
        self.repaint()  # Force repaint
        
        # Force central widget to repaint too
        if self.centralWidget():
            self.centralWidget().repaint()
        
        if self.settings.always_on_top:
            self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
            self.show()  # Reshow after flag change
        
        # Enable overlay window positioning - threading issues resolved
        logger.info("ENABLING overlay window with proper main thread positioning")
        QTimer.singleShot(100, self._sync_overlay_position)
        
        # Setup menu - REMOVED for clean interface
        # self.setup_menu()
    
    def _sync_overlay_position(self):
        """Synchronize overlay window position with main player window"""
        try:
            # Only sync position if we have a separate overlay window (multi-window mode)
            if hasattr(self, 'overlay_window') and self.overlay_window and not self.settings.overlay_single_window:
                # Check video state during overlay positioning
                if hasattr(self, 'media_player'):
                    video_state = self.media_player.playbackState()
                    logger.debug(f"GREEN SCREEN DEBUG: Video state during overlay sync: {video_state}")

                # Get main window geometry
                main_geo = self.geometry()
                logger.debug(f"Main window geometry: {main_geo}")
                logger.debug(f"GREEN SCREEN DEBUG: Syncing overlay position - main geometry: {main_geo}")

                # CRITICAL FIX: Store overlay visibility state before sync
                overlay_was_visible = self.overlay_window.isVisible()

                # Check overlay window state before positioning
                logger.debug(f"GREEN SCREEN DEBUG: Overlay window before sync - visible: {overlay_was_visible}")
                logger.debug(f"GREEN SCREEN DEBUG: Overlay window before sync - geometry: {self.overlay_window.geometry()}")

                # Position overlay window exactly over main window
                self.overlay_window.setGeometry(main_geo)
                self.overlay_window.show()
                self.overlay_window.raise_()

                # CRITICAL FIX: Ensure WebEngine overlay within window maintains visibility
                if hasattr(self, 'window_overlay') and hasattr(self.window_overlay, 'isVisible'):
                    webengine_visible = self.window_overlay.isVisible()
                    if not webengine_visible:
                        logger.error(f"GREEN SCREEN FIX: WebEngine overlay lost visibility during sync, recovering")
                        self.window_overlay.show()
                        self.window_overlay.raise_()
                        logger.error(f"GREEN SCREEN FIX: WebEngine overlay visibility recovered during sync")

                if self.debug_overlay:
                    logger.debug(f"Overlay window positioned at: {self.overlay_window.geometry()}")
                    logger.debug(f"GREEN SCREEN DEBUG: Overlay window after sync - geometry: {self.overlay_window.geometry()}")

            elif self.settings.overlay_single_window:
                # Single window mode - overlay is already part of the central widget, no positioning needed
                logger.debug("Single window overlay mode - no position sync needed")
                # Ensure overlay widget visibility in single window mode
                if hasattr(self, 'window_overlay') and hasattr(self.window_overlay, 'isVisible'):
                    if not self.window_overlay.isVisible():
                        logger.debug("Ensuring single window overlay visibility")
                        self.window_overlay.show()

        except Exception as e:
            if self.debug_overlay:
                logger.debug(f"GREEN SCREEN DEBUG: Overlay sync failed: {e}")
            logger.error(f"Failed to sync overlay position: {e}")
    
    def resizeEvent(self, event):
        """Handle window resize events - sync overlay position"""
        super().resizeEvent(event)
        # Only sync overlay position in multi-window mode
        if hasattr(self, 'overlay_window') and self.overlay_window and not self.settings.overlay_single_window:
            # Delay sync to allow window to finish resizing
            QTimer.singleShot(10, self._sync_overlay_position)

    def moveEvent(self, event):
        """Handle window move events - sync overlay position"""
        super().moveEvent(event)
        # Only sync overlay position in multi-window mode
        if hasattr(self, 'overlay_window') and self.overlay_window and not self.settings.overlay_single_window:
            # Delay sync to allow window to finish moving
            QTimer.singleShot(10, self._sync_overlay_position)
    
    def setup_menu(self):
        """Setup application menu - DISABLED for clean interface"""
        # Menu and toolbar removed for minimal video player interface
        # All functionality still available via keyboard shortcuts:
        # - F11: Toggle fullscreen
        # - S: Toggle stats
        # - Space: Play/pause
        # - M: Mute/unmute
        # - Escape: Exit
        pass
    
    def setup_media_player(self):
        """Setup PyQt6 media player with audio output"""
        self.media_player = QMediaPlayer()
        self.audio_output = QAudioOutput()
        
        self.media_player.setAudioOutput(self.audio_output)
        
        # DEBUG: Check video widget setup
        logger.info(f"Setting up media player video output:")
        logger.info(f"self.video_widget: {self.video_widget}")
        logger.info(f"has get_video_widget: {hasattr(self.video_widget, 'get_video_widget')}")
        
        # For VideoWidget without internal overlay, get the base video widget directly
        if hasattr(self.video_widget, 'get_video_widget'):
            video_output_widget = self.video_widget.get_video_widget()
            logger.info(f"Using get_video_widget(): {video_output_widget}")
            logger.info(f"Video widget visible: {video_output_widget.isVisible()}")
            logger.info(f"Video widget size: {video_output_widget.size()}")
            self.media_player.setVideoOutput(video_output_widget)
        else:
            # Simple VideoWidget case
            video_output_widget = self.video_widget.video_widget
            logger.info(f"Using video_widget directly: {video_output_widget}")
            logger.info(f"Video widget visible: {video_output_widget.isVisible()}")
            logger.info(f"Video widget size: {video_output_widget.size()}")
            self.media_player.setVideoOutput(video_output_widget)
        
        logger.info(f"Media player video output set to: {self.media_player.videoOutput()}")
        
        # Connect signals
        self.media_player.playbackStateChanged.connect(self.on_state_changed)
        self.media_player.positionChanged.connect(self.on_position_changed)
        self.media_player.durationChanged.connect(self.on_duration_changed)
        self.media_player.errorOccurred.connect(self.on_media_error)
        self.media_player.mediaStatusChanged.connect(self.on_media_status_changed)
        
        # Loop control state
        self.loop_enabled = False
        self.infinite_loop = False
        self.loop_count = 0
        self.current_loop_iteration = 0
        self.current_file_path = None
        logger.info("RESULT DEBUG: Initialized current_file_path = None")

        # Match video tracking
        self.is_playing_match_video = False
        self.current_match_id = None
        self.current_match_video_filename = None
        self.current_fixture_id = None

        # Template rotation state
        self.template_sequence = []
        self.rotating_time = '00:15'
        self.current_template_index = 0
        self.template_rotation_timer = None

        # Set volume
        self.audio_output.setVolume(self.settings.volume)
        self.audio_output.setMuted(self.settings.mute)
    
    def setup_timers(self):
        """Setup various timers"""
        # Overlay update timer
        self.overlay_timer = QTimer()
        self.overlay_timer.timeout.connect(self.update_overlay_periodically)
        self.overlay_timer.start(1000)  # Update every second

        # Template rotation timer (initially disabled)
        self.template_rotation_timer = QTimer()
        self.template_rotation_timer.timeout.connect(self._rotate_template)
        self.template_rotation_timer.setSingleShot(False)  # Repeat timer

        # Mouse tracking for window interactions (no controls to show/hide)
        self.setMouseTracking(True)

    def _start_template_rotation(self):
        """Start the template rotation timer"""
        try:
            if not self.template_sequence or len(self.template_sequence) <= 1:
                logger.debug("Template rotation not needed - insufficient templates")
                return

            # Parse rotating time (format: MM:SS)
            try:
                minutes, seconds = map(int, self.rotating_time.split(':'))
                rotation_interval_ms = (minutes * 60 + seconds) * 1000  # Convert to milliseconds
            except (ValueError, AttributeError):
                logger.warning(f"Invalid rotating_time format '{self.rotating_time}', using default 15 seconds")
                rotation_interval_ms = 15 * 1000  # Default 15 seconds

            logger.info(f"Starting template rotation every {rotation_interval_ms}ms ({self.rotating_time})")

            # Stop any existing timer
            if self.template_rotation_timer and self.template_rotation_timer.isActive():
                self.template_rotation_timer.stop()

            # Start the rotation timer
            self.template_rotation_timer.start(rotation_interval_ms)
            self.current_template_index = 0  # Reset to first template

        except Exception as e:
            logger.error(f"Failed to start template rotation: {e}")

    def _rotate_template(self):
        """Rotate to the next template in the sequence"""
        try:
            if not self.template_sequence or len(self.template_sequence) == 0:
                logger.debug("No template sequence available for rotation")
                return

            # Move to next template
            self.current_template_index = (self.current_template_index + 1) % len(self.template_sequence)
            next_template = self.template_sequence[self.current_template_index]['name']

            logger.info(f"Rotating to template: {next_template} (index {self.current_template_index})")

            # Load the new template
            if hasattr(self, 'window_overlay') and isinstance(self.window_overlay, OverlayWebView):
                self.window_overlay.load_template(next_template)
                logger.info(f"Template rotated to: {next_template}")
            else:
                logger.warning("No WebEngine overlay available for template rotation")

        except Exception as e:
            logger.error(f"Failed to rotate template: {e}")

    
    def open_file_dialog(self):
        """Open file dialog to select video"""
        from PyQt6.QtWidgets import QFileDialog
        
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Open Video File",
            "",
            "Video Files (*.mp4 *.avi *.mkv *.mov *.wmv *.flv *.webm *.m4v);;All Files (*)"
        )
        
        if file_path:
            self.play_video(file_path)
    
    def play_video(self, file_path: str, template_data: Dict[str, Any] = None, template_name: str = None, loop_data: Dict[str, Any] = None):
        """Play video file with optional overlay data, template, and loop control"""
        try:
            logger.info(f"PlayerWindow.play_video() called with: {file_path}")
            logger.info(f"Loop data: {loop_data}")
            logger.info(f"Media player state before play: {self.media_player.playbackState()}")
            logger.info(f"Media player error state: {self.media_player.error()}")
            logger.info(f"Media player has video output: {self.media_player.videoOutput() is not None}")
            logger.info(f"Video widget visible: {self.video_widget.get_video_widget().isVisible()}")
            logger.info(f"Video widget size: {self.video_widget.get_video_widget().size()}")
            logger.info(f"Window visible: {self.isVisible()}")
            logger.info(f"Window size: {self.size()}")
            logger.info(f"Overlay window visible: {hasattr(self, 'overlay_window') and self.overlay_window and self.overlay_window.isVisible() if not self.settings.overlay_single_window else 'N/A (single window mode)'}")
            logger.info(f"Overlay window size: {hasattr(self, 'overlay_window') and self.overlay_window and self.overlay_window.size() if not self.settings.overlay_single_window else 'N/A (single window mode)'}")

            # Process loop control parameters
            if loop_data:
                self.loop_enabled = loop_data.get('loop_mode', False) or loop_data.get('infinite_loop', False) or loop_data.get('continuous_playback', False)
                self.infinite_loop = loop_data.get('infinite_loop', False) or loop_data.get('continuous_playback', False)
                self.loop_count = loop_data.get('loop_count', 0)

                # Handle template rotation for intro videos
                self.template_sequence = loop_data.get('template_sequence', [])
                self.rotating_time = loop_data.get('rotating_time', '00:15')
                self.current_template_index = 0

                if self.template_sequence:
                    logger.info(f"TEMPLATE ROTATION ENABLED: {len(self.template_sequence)} templates, rotating every {self.rotating_time}")

                if self.infinite_loop or self.loop_count == -1:
                    self.infinite_loop = True
                    logger.info("INFINITE LOOP MODE ENABLED")
                elif self.loop_count > 0:
                    logger.info(f"FINITE LOOP MODE ENABLED - {self.loop_count} iterations")

                self.current_loop_iteration = 0
            else:
                # No loop data - disable looping and template rotation
                self.loop_enabled = False
                self.infinite_loop = False
                self.loop_count = 0
                self.current_loop_iteration = 0
                self.template_sequence = []
                self.rotating_time = '00:15'
                self.current_template_index = 0
            
            with QMutexLocker(self.mutex):
                # Handle both absolute and relative file paths
                path_obj = Path(file_path)
                logger.info(f"Original path object: {path_obj}")
                
                if not path_obj.is_absolute():
                    # For relative paths, resolve them relative to the application directory
                    import os
                    app_dir = Path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
                    absolute_path = app_dir / file_path
                    logger.info(f"Resolved relative path '{file_path}' to '{absolute_path}'")
                else:
                    absolute_path = path_obj
                    logger.info(f"Using absolute path: {absolute_path}")
                
                # Verify file exists before trying to play
                logger.info(f"Checking if file exists: {absolute_path}")
                if not absolute_path.exists():
                    logger.error(f"Video file not found: {absolute_path}")
                    # Show error in overlay
                    if hasattr(self, 'window_overlay'):
                        overlay_view = self.window_overlay
                        error_data = {
                            'title': 'File Not Found',
                            'subtitle': f'Cannot find: {file_path}',
                            'ticker': 'Please check the file path and try again.'
                        }
                        # Update overlay safely - handles both native and WebEngine
                        self._update_overlay_safe(overlay_view, error_data)
                    return
                
                logger.info(f"File exists! Size: {absolute_path.stat().st_size} bytes")
                
                url = QUrl.fromLocalFile(str(absolute_path))
                logger.info(f"Created QUrl: {url.toString()}")
                logger.info(f"QUrl is valid: {url.isValid()}")
                logger.info(f"QUrl scheme: {url.scheme()}")
                logger.info(f"QUrl path: {url.path()}")

                # Store current file path for loop functionality
                logger.info(f"RESULT DEBUG: Setting current_file_path = '{str(absolute_path)}'")
                self.current_file_path = str(absolute_path)

                logger.info(f"Media player current state: {self.media_player.playbackState()}")
                logger.info(f"Media player current source before set: {self.media_player.source()}")
                self.media_player.setSource(url)
                logger.info(f"Media player source set to: {url.toString()}")
                logger.info(f"Media player source after set: {self.media_player.source()}")
                logger.info(f"Media player error after setSource: {self.media_player.error()}")
                
                # CRITICAL FIX: Protect video rendering context during template operations
                logger.debug(f"GREEN SCREEN FIX: Protecting video rendering context during template load")
                
                # Store video widget state before template operations
                video_widget = None
                if hasattr(self.video_widget, 'get_video_widget'):
                    video_widget = self.video_widget.get_video_widget()
                
                if video_widget:
                    logger.debug(f"GREEN SCREEN FIX: Video widget state before template load - visible: {video_widget.isVisible()}")
                
                # DELAYED TEMPLATE LOADING: Load template AFTER video starts to avoid timing conflicts
                # Template loading during video initialization can cause crashes in PyInstaller bundles
                def load_template_after_video_start():
                    """Load template after video has started playing to avoid timing issues"""
                    try:
                        if hasattr(self, 'window_overlay') and isinstance(self.window_overlay, OverlayWebView):
                            if template_name:
                                logger.debug(f"GREEN SCREEN FIX: Loading template after video start: {template_name}")
                                # Load template after video starts to avoid conflicts
                                self.window_overlay.load_template(template_name)
                                logger.info(f"Loaded template '{template_name}' for video playback")
                            else:
                                logger.debug(f"GREEN SCREEN FIX: Reloading template after video start")
                                # Reload template after video starts to avoid conflicts
                                self.window_overlay.reload_current_template()
                                logger.info("Reloaded current overlay template for video playback")
                    except Exception as template_error:
                        logger.error(f"Failed to load template after video start: {template_error}")
                        # Continue without template - video should still play

                # Schedule template loading to happen after video has started (0.3 seconds delay)
                QTimer.singleShot(300, load_template_after_video_start)
                
                # CRITICAL FIX: Force video widget refresh after template operations
                if video_widget:
                    logger.debug(f"GREEN SCREEN FIX: Forcing video widget refresh after template load")
                    video_widget.repaint()
                    video_widget.update()
                    logger.debug(f"GREEN SCREEN FIX: Video widget state after template load - visible: {video_widget.isVisible()}")
                
                # Update overlay with video info using safe method
                overlay_data = template_data or {}
                overlay_data.update({
                    'title': f'Playing: {Path(file_path).name}',
                    'subtitle': 'MbetterClient PyQt6 Player',
                    'webServerBaseUrl': self._get_web_server_base_url()
                })
                
                if hasattr(self, 'window_overlay'):
                    overlay_view = self.window_overlay
                    # Update overlay safely - handles both native and WebEngine
                    QTimer.singleShot(300, lambda: self._update_overlay_safe(overlay_view, overlay_data))
                
                # Start template rotation timer if enabled
                if self.template_sequence and len(self.template_sequence) > 1:
                    self._start_template_rotation()

                if self.settings.auto_play:
                    logger.info("Auto-play enabled, calling media_player.play()")
                    self.media_player.play()
                    logger.info(f"Media player state after play(): {self.media_player.playbackState()}")
                    logger.info(f"Media player error after play(): {self.media_player.error()}")
                else:
                    logger.info("Auto-play disabled, not calling play()")

                # Ensure proper window focus for video playback
                app = QApplication.instance()
                if app:
                    logger.info("Applying window focus for video playback")
                    self.show()
                    self.raise_()
                    self.activateWindow()
                    app.processEvents()
                    app.setActiveWindow(self)
                    logger.debug(f"Window focus applied for video playback - activeWindow: {app.activeWindow()}")
                    logger.debug(f"Window visible after focus: {self.isVisible()}")
                    logger.debug(f"Video widget visible after focus: {self.video_widget.get_video_widget().isVisible()}")
                else:
                    logger.warning("No QApplication instance found for window focus")

                # Start background metadata extraction
                worker = VideoProcessingWorker(
                    "metadata_extraction",
                    {"file_path": file_path},
                    self.on_metadata_extracted
                )
                self.thread_pool.start(worker)

                logger.info(f"Playing video: {file_path}")
                self.video_loaded.emit(file_path)
                
        except Exception as e:
            logger.error(f"Failed to play video: {e}")
    
    def on_metadata_extracted(self, metadata: Dict[str, Any]):
        """Handle extracted metadata"""
        if 'error' not in metadata and hasattr(self, 'window_overlay'):
            self.window_overlay.update_video_info(metadata)
            logger.debug(f"Video metadata: {metadata}")
    
    def toggle_play_pause(self):
        """Toggle play/pause (thread-safe)"""
        with QMutexLocker(self.mutex):
            if self.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
                self.media_player.pause()
            else:
                self.media_player.play()
    
    def stop_playback(self):
        """Stop playback (thread-safe)"""
        with QMutexLocker(self.mutex):
            self.media_player.stop()

            # Stop template rotation timer
            if self.template_rotation_timer and self.template_rotation_timer.isActive():
                self.template_rotation_timer.stop()
                logger.debug("Template rotation timer stopped")
    
    def seek_to_position(self, percentage: int):
        """Seek to position (percentage) (thread-safe)"""
        with QMutexLocker(self.mutex):
            duration = self.media_player.duration()
            if duration > 0:
                position = int(duration * percentage / 100)
                self.media_player.setPosition(position)
    
    def set_volume(self, volume: int):
        """Set volume (0-100) (thread-safe)"""
        with QMutexLocker(self.mutex):
            self.audio_output.setVolume(volume / 100.0)
    
    def toggle_fullscreen(self):
        """Toggle fullscreen mode"""
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()
    
    def toggle_stats(self):
        """Toggle stats panel"""
        overlay_view = self.window_overlay
        # Toggle stats via WebChannel
        if hasattr(overlay_view, 'overlay_channel') and overlay_view.overlay_channel:
            current_stats = getattr(self, '_stats_visible', False)
            overlay_view.overlay_channel.toggleStats(not current_stats)
            self._stats_visible = not current_stats
    
    def on_state_changed(self, state):
        """Handle playback state changes (thread-safe)"""
        is_playing = state == QMediaPlayer.PlaybackState.PlayingState
        # Controls removed - no button updates needed
        logger.info(f"Playback state changed: {'playing' if is_playing else 'paused'}")
        logger.info(f"Media player state: {state}")
        logger.info(f"Media player error: {self.media_player.error()}")
        logger.info(f"Media player source: {self.media_player.source()}")
        logger.info(f"Video widget visible: {self.video_widget.get_video_widget().isVisible()}")
        logger.info(f"Window visible: {self.isVisible()}")
    
    def on_position_changed(self, position):
        """Handle position changes (thread-safe)"""
        duration = self.media_player.duration()
        # Controls removed - no slider updates needed
        
        # Update overlay with position info using safe method
        if duration > 0 and hasattr(self, 'window_overlay'):
            overlay_view = self.window_overlay
            # Update position for both native and WebEngine overlays
            try:
                overlay_view.update_position(
                    position / 1000.0,  # Convert to seconds
                    duration / 1000.0   # Convert to seconds
                )
            except Exception as e:
                logger.debug(f"Failed to update overlay position: {e}")
        
        # Emit signal for other components
        self.position_changed.emit(position, duration)
    
    def on_duration_changed(self, duration):
        """Handle duration changes"""
        # Controls removed - no updates needed
        logger.debug(f"Duration changed: {duration}ms")
    
    def on_media_error(self, error):
        """Handle media errors"""
        logger.error(f"Media player error: {error}")
        
        # Show error in overlay using safe update
        if hasattr(self, 'window_overlay'):
            overlay_view = self.window_overlay
            error_data = {
                'title': 'Playback Error',
                'subtitle': f'Error: {error.name if hasattr(error, "name") else str(error)}',
                'ticker': 'Please check the video file and try again.'
            }
            # Update overlay safely - handles both native and WebEngine
            self._update_overlay_safe(overlay_view, error_data)
    
    def on_media_status_changed(self, status):
        """Handle media status changes and loop control"""
        logger.info(f"LOOP DEBUG: Media status changed to: {status} ({status.name if hasattr(status, 'name') else 'unknown'})")
        logger.info(f"Media player source: {self.media_player.source()}")
        logger.info(f"Media player error: {self.media_player.error()}")
        logger.info(f"Video widget visible: {self.video_widget.get_video_widget().isVisible()}")
        logger.info(f"Window visible: {self.isVisible()}")
        logger.debug(f"LOOP DEBUG: Media status changed to: {status} ({status.name if hasattr(status, 'name') else 'unknown'})")

        if status == QMediaPlayer.MediaStatus.LoadedMedia:
            # Media loaded successfully - use safe update
            if hasattr(self, 'window_overlay'):
                overlay_view = self.window_overlay
                status_data = {'subtitle': 'Media loaded successfully'}
                # Update overlay safely - handles both native and WebEngine
                self._update_overlay_safe(overlay_view, status_data)

        elif status == QMediaPlayer.MediaStatus.EndOfMedia:
            # Handle end of media for loop functionality and match video completion
            logger.debug(f"LOOP DEBUG: END OF MEDIA DETECTED!")
            logger.debug(f"LOOP DEBUG: Loop enabled: {self.loop_enabled}")
            logger.debug(f"LOOP DEBUG: Infinite loop: {self.infinite_loop}")
            logger.debug(f"LOOP DEBUG: Current iteration: {self.current_loop_iteration}")
            logger.debug(f"LOOP DEBUG: Loop count: {self.loop_count}")
            logger.debug(f"MATCH DEBUG: Is playing match video: {self.is_playing_match_video}")

            # Check if this is the end of a match video
            if self.is_playing_match_video:
                logger.info(f"MATCH DEBUG: Match video ended - sending PLAY_VIDEO_MATCH_DONE")
                if self.qt_player:
                    self.qt_player._send_match_video_done_message(
                        match_id=self.current_match_id,
                        video_filename=self.current_match_video_filename,
                        fixture_id=self.current_fixture_id
                    )
                # Reset match video tracking
                self.is_playing_match_video = False
                self.current_match_id = None
                self.current_match_video_filename = None
                self.current_fixture_id = None
                return

            # Check if this is the end of a result video (any result video)
            current_filename = Path(self.current_file_path).name if self.current_file_path else ""

            # Dynamically get result video filenames from database (excludes OVER/UNDER)
            result_videos = self.qt_player._get_result_video_filenames() if hasattr(self.qt_player, '_get_result_video_filenames') else ["WIN1.mp4", "WIN2.mp4", "KO1.mp4", "KO2.mp4", "DRAW.mp4", "RET1.mp4", "RET2.mp4"]
            logger.info(f"RESULT DEBUG: Dynamically loaded result_videos = {result_videos}")

            # DEBUG: Add comprehensive logging for result video end detection
            logger.info(f"RESULT DEBUG: Checking for result video end detection")
            logger.info(f"RESULT DEBUG: current_file_path = {self.current_file_path}")
            logger.info(f"RESULT DEBUG: current_filename = {current_filename}")

            # Check if current filename is a result video
            is_result_video = current_filename in result_videos
            logger.info(f"RESULT DEBUG: current_filename in result_videos = {is_result_video}")

            if is_result_video:
                logger.info(f"RESULT DEBUG: Result video {current_filename} detected - extracting information and scheduling PLAY_VIDEO_RESULTS_DONE in 5 seconds")

                # Extract result from filename
                result = current_filename.replace('.mp4', '')

                # Extract match_id from path like /tmp/match_29_.../
                import re
                match = re.search(r'/match_(\d+)_', self.current_file_path)
                match_id = int(match.group(1)) if match else None

                # Try to get fixture_id from database using match_id
                fixture_id = None
                if match_id:
                    try:
                        from ..database.models import MatchModel
                        db_manager = self.qt_player._get_database_manager() if hasattr(self, 'qt_player') else None
                        if db_manager:
                            session = db_manager.get_session()
                            try:
                                match_obj = session.query(MatchModel).filter_by(id=match_id).first()
                                if match_obj:
                                    fixture_id = match_obj.fixture_id
                            finally:
                                session.close()
                    except Exception as e:
                        logger.debug(f"Failed to get fixture_id from database: {e}")

                # Create result_video_info
                result_video_info = {
                    'path': self.current_file_path,
                    'fixture_id': fixture_id,
                    'match_id': match_id,
                    'result': result,
                    'duration': 10.0,  # Default duration
                    'is_result_video': True
                }

                # Set tracking attributes for the done message
                self.current_result_video_info = result_video_info
                self.current_result_video_filename = current_filename

                logger.info(f"RESULT DEBUG: Extracted result video info: {result_video_info}")

                # Schedule sending PLAY_VIDEO_RESULTS_DONE message after 5 seconds (non-blocking)
                # This allows the results overlay template to show results before sending the done message
                QTimer.singleShot(5000, self.qt_player._send_result_video_done_after_delay)
                return
            else:
                logger.info(f"RESULT DEBUG: Not a result video - continuing with normal flow")

            # Handle loop functionality for intro videos
            if self.loop_enabled:
                logger.debug(f"LOOP DEBUG: Processing loop restart logic...")
                if self.infinite_loop:
                    # Infinite loop - restart immediately
                    self.current_loop_iteration += 1
                    logger.debug(f"LOOP DEBUG: INFINITE LOOP - Starting iteration {self.current_loop_iteration}")
                    self._restart_video()
                elif self.loop_count > 0 and self.current_loop_iteration < self.loop_count:
                    # Finite loop - check if we should continue
                    self.current_loop_iteration += 1
                    logger.debug(f"LOOP DEBUG: FINITE LOOP - Starting iteration {self.current_loop_iteration}/{self.loop_count}")
                    self._restart_video()
                else:
                    logger.debug(f"LOOP DEBUG: Loop completed after {self.current_loop_iteration} iterations - stopping playback")
                    self.loop_enabled = False
            else:
                logger.debug(f"LOOP DEBUG: No loop enabled - playback finished")
    
    def _restart_video(self):
        """Restart video playback for looping"""
        try:
            logger.debug(f"LOOP DEBUG: _restart_video called")
            logger.debug(f"LOOP DEBUG: Current file path: {self.current_file_path}")
            logger.debug(f"LOOP DEBUG: Media player state: {self.media_player.playbackState()}")
            
            if self.current_file_path:
                logger.debug(f"LOOP DEBUG: Restarting video for loop: {self.current_file_path}")
                url = QUrl.fromLocalFile(self.current_file_path)
                logger.debug(f"LOOP DEBUG: Created QUrl: {url.toString()}")
                
                self.media_player.setSource(url)
                logger.debug(f"LOOP DEBUG: Set media player source")
                
                self.media_player.play()
                logger.debug(f"LOOP DEBUG: Called media player play()")
                logger.debug(f"LOOP DEBUG: Media player state after play(): {self.media_player.playbackState()}")
                
                # Update overlay to show loop status
                if hasattr(self, 'window_overlay'):
                    overlay_view = self.window_overlay
                    loop_info = f"Loop {self.current_loop_iteration}"
                    if self.infinite_loop:
                        loop_info += " (∞)"
                    else:
                        loop_info += f"/{self.loop_count}"
                    
                    status_data = {'subtitle': f'Playing - {loop_info}'}
                    self._update_overlay_safe(overlay_view, status_data)
                    logger.debug(f"LOOP DEBUG: Updated overlay with loop info: {loop_info}")
            else:
                logger.error("LOOP DEBUG: No current file path available for restart")
        except Exception as e:
            logger.debug(f"LOOP DEBUG: Failed to restart video for loop: {e}")
    
    def update_overlay_periodically(self):
        """Periodic overlay updates with WebEngine safety checks"""
        try:
            current_time = time.strftime("%H:%M:%S")
            if hasattr(self, 'window_overlay'):
                overlay_view = self.window_overlay
                # Only update if we have valid overlay data
                time_data = {
                    'currentTime': current_time,
                    'webServerBaseUrl': self._get_web_server_base_url()
                }
                if time_data and current_time:  # Ensure we have valid data
                    self._update_overlay_safe(overlay_view, time_data)

        except Exception as e:
            logger.error(f"Periodic overlay update failed: {e}")





    
    def mouseMoveEvent(self, event):
        """Show controls on mouse movement"""
        super().mouseMoveEvent(event)
        self.show_controls()
        
        # Controls removed - no timer handling needed
        pass
    
    def keyPressEvent(self, event):
        """Handle key presses"""
        if event.key() == Qt.Key.Key_Space:
            self.toggle_play_pause()
        elif event.key() == Qt.Key.Key_Escape:
            if self.isFullScreen():
                self.showNormal()
            else:
                self.close()
        elif event.key() == Qt.Key.Key_F11:
            self.toggle_fullscreen()
        elif event.key() == Qt.Key.Key_M:
            current_muted = self.audio_output.isMuted()
            self.audio_output.setMuted(not current_muted)
        elif event.key() == Qt.Key.Key_S:
            self.toggle_stats()
        
        super().keyPressEvent(event)
    
    def show_controls(self):
        """Show controls - REMOVED per user request"""
        # Controls removed - clean overlay-only interface
        self.setCursor(Qt.CursorShape.ArrowCursor)
    
    def hide_controls(self):
        """Hide controls - REMOVED per user request"""
        # Controls removed - clean overlay-only interface
        if self.isFullScreen():
            self.setCursor(Qt.CursorShape.BlankCursor)
    
    def start_controls_timer(self):
        """Start timer to hide controls - REMOVED per user request"""
        # Controls removed - no timer needed
        pass
    
    def closeEvent(self, event):
        """Handle window close (thread-safe)"""
        with QMutexLocker(self.mutex):
            self.media_player.stop()
            self.thread_pool.waitForDone(3000)  # Wait up to 3 seconds for threads

            # Stop template rotation timer
            if self.template_rotation_timer and self.template_rotation_timer.isActive():
                self.template_rotation_timer.stop()
                logger.debug("Template rotation timer stopped on window close")

            # Close overlay window (only in multi-window mode)
            if hasattr(self, 'overlay_window') and self.overlay_window and not self.settings.overlay_single_window:
                self.overlay_window.close()
                logger.debug("Overlay window closed")
            elif self.settings.overlay_single_window:
                logger.debug("Single window overlay mode - no separate overlay window to close")

        logger.info("Player window closing - Qt will handle application exit")
        event.accept()
    
    def _is_webengine_ready(self, overlay_view):
        """Check if WebEngine overlay is fully ready for updates"""
        try:
            # First check if this is actually a WebEngine overlay
            if not isinstance(overlay_view, OverlayWebView):
                return False
                
            # Check if overlay_view is a QWebEngineView
            if not hasattr(overlay_view, 'page'):
                return False
                
            page = overlay_view.page()
            if not page:
                return False
            
            # Check if WebChannel exists
            if not page.webChannel():
                return False
                
            # Check if URL is loaded (not about:blank)
            current_url = overlay_view.url().toString()
            if current_url == 'about:blank' or not current_url:
                return False
            
            # Check if the overlay_channel object exists and is ready
            if not (hasattr(overlay_view, 'overlay_channel') and overlay_view.overlay_channel):
                return False
                
            return True
            
        except Exception as e:
            logger.debug(f"WebEngine readiness check failed: {e}")
            return False
    
    def _is_native_overlay(self, overlay_view):
        """Check if this is a native overlay"""
        return False  # Native overlay removed
    
    def _clean_overlay_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Clean overlay data by removing null/undefined values"""
        cleaned_data = {}
        
        # Define valid overlay properties and their expected types
        valid_properties = {
            'title': str,
            'subtitle': str,
            'ticker': str,
            'currentTime': str,
            'showStats': bool,
            'customCSS': str
        }
        
        for key, value in data.items():
            # Skip null, undefined, or empty string values
            if value is None or value == "":
                logger.debug(f"Skipping null/empty property '{key}' in overlay data")
                continue
            
            # Validate property type if it's a known property
            if key in valid_properties:
                expected_type = valid_properties[key]
                if not isinstance(value, expected_type):
                    logger.debug(f"Converting property '{key}' from {type(value)} to {expected_type}")
                    try:
                        # Convert to expected type
                        if expected_type == str:
                            cleaned_data[key] = str(value)
                        elif expected_type == bool:
                            cleaned_data[key] = bool(value)
                        else:
                            cleaned_data[key] = value
                    except (ValueError, TypeError):
                        logger.warning(f"Failed to convert property '{key}' to {expected_type}, skipping")
                        continue
                else:
                    cleaned_data[key] = value
            else:
                # Unknown property, pass through if not null
                cleaned_data[key] = value
        
        data_keys = list(cleaned_data.keys()) if isinstance(cleaned_data, dict) else []
        logger.debug(f"Cleaned overlay data: {len(cleaned_data)} items with keys: {data_keys}")
        return cleaned_data
    
    def _update_overlay_safe(self, overlay_view, data):
        """Update overlay data safely, handling both native and WebEngine overlays"""
        try:
            # Check video state during overlay update
            if hasattr(self, 'media_player') and self.debug_overlay:
                video_state = self.media_player.playbackState()
                logger.debug(f"GREEN SCREEN DEBUG: Video state during overlay update: {video_state}")
                logger.debug(f"GREEN SCREEN DEBUG: Video position during overlay update: {self.media_player.position()}")

            # Clean data before sending to prevent null property issues
            cleaned_data = self._clean_overlay_data(data)
            if not cleaned_data:
                logger.debug("No valid data to send to overlay after cleaning")
                return False

            if self.debug_overlay:
                data_keys = list(cleaned_data.keys()) if isinstance(cleaned_data, dict) else []
                logger.debug(f"GREEN SCREEN DEBUG: About to update overlay with {len(cleaned_data)} data items: {data_keys}")
            
            if self._is_native_overlay(overlay_view):
                # Native overlay - always ready, update immediately
                if self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Updating native overlay")
                overlay_view.update_overlay_data(cleaned_data)
                if self.debug_overlay:
                    logger.debug("Native overlay updated successfully")
                    logger.debug(f"GREEN SCREEN DEBUG: Native overlay update completed")
                return True
            elif isinstance(overlay_view, OverlayWebView):
                # WebEngine overlay - check readiness first
                if self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Checking WebEngine overlay readiness")
                    logger.debug(f"GREEN SCREEN DEBUG: WebEngine URL: {overlay_view.url().toString()}")
                    logger.debug(f"GREEN SCREEN DEBUG: WebEngine visible: {overlay_view.isVisible()}")

                # CRITICAL FIX: Ensure WebEngine overlay visibility before update
                if not overlay_view.isVisible():
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN FIX: WebEngine overlay not visible, forcing visibility recovery")
                    overlay_view.show()
                    overlay_view.raise_()
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN FIX: WebEngine overlay visibility forced during update")

                    # Also ensure parent overlay window is visible (only in multi-window mode)
                    if hasattr(self, 'overlay_window') and self.overlay_window and not self.settings.overlay_single_window and not self.overlay_window.isVisible():
                        if self.debug_overlay:
                            logger.debug(f"GREEN SCREEN FIX: Parent overlay window not visible, forcing visibility")
                        self.overlay_window.show()
                        self.overlay_window.raise_()
                        if self.debug_overlay:
                            logger.debug(f"GREEN SCREEN FIX: Parent overlay window visibility forced")

                if self._is_webengine_ready(overlay_view):
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN DEBUG: WebEngine ready, updating overlay")
                    overlay_view.update_overlay_data(cleaned_data)
                    if self.debug_overlay:
                        logger.debug("WebEngine overlay updated successfully")
                        logger.debug(f"GREEN SCREEN DEBUG: WebEngine overlay update completed")
                    return True
                else:
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN DEBUG: WebEngine not ready, skipping update")
                        logger.debug("WebEngine overlay not ready, skipping update")
                    return False
            else:
                logger.warning(f"Unknown overlay type: {type(overlay_view)}")
                if self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Unknown overlay type: {type(overlay_view)}")
                return False
        except Exception as e:
            if self.debug_overlay:
                logger.debug(f"GREEN SCREEN DEBUG: Overlay update failed: {e}")
            logger.error(f"Failed to update overlay safely: {e}")
            return False
    
    
    def _protect_video_context_after_template_load(self):
        """Protect video rendering context after template loading operations"""
        try:
            logger.debug(f"GREEN SCREEN FIX: Protecting video context after template load")
            
            # Force repaint of video widget
            if hasattr(self, 'video_widget') and hasattr(self.video_widget, 'get_video_widget'):
                video_widget = self.video_widget.get_video_widget()
                if video_widget:
                    video_widget.repaint()
                    video_widget.update()
                    logger.debug(f"GREEN SCREEN FIX: Video widget repainted after template load")
            
            # Force media player to refresh video output
            if hasattr(self, 'media_player'):
                logger.debug(f"GREEN SCREEN FIX: Refreshing media player video output after template load")
                current_output = self.media_player.videoOutput()
                if current_output:
                    # Force video output refresh by briefly setting to None and back
                    self.media_player.setVideoOutput(None)
                    self.media_player.setVideoOutput(current_output)
                    logger.debug(f"GREEN SCREEN FIX: Media player video output refreshed after template load")
        except Exception as e:
            logger.debug(f"GREEN SCREEN FIX: Failed to protect video context after template load: {e}")
    
    def _send_safe_overlay_update(self, overlay_view, data):
        """Send overlay update with additional safety checks"""
        return self._update_overlay_safe(overlay_view, data)


class QtVideoPlayer(QObject):
    """PyQt6 video player component with message bus integration (replaces PyQt5 version)"""

    # Signal for cross-thread video playback
    play_video_signal = pyqtSignal(str, dict)

    def __init__(self, message_bus: MessageBus, settings: QtConfig, debug_player: bool = False, debug_overlay: bool = False):
        super().__init__()
        logger.info(f"DEBUG: QtVideoPlayer.__init__ called with debug_overlay={debug_overlay}")
        self.name = "qt_player"
        self.message_bus = message_bus
        self.settings = settings
        self.debug_player = debug_player
        self.debug_overlay = debug_overlay
        self.app: Optional[QApplication] = None
        self.window: Optional[PlayerWindow] = None
        self.mutex = QMutex()

        # Progress update throttling (2 per second = 500ms minimum interval)
        self.last_progress_update_time = 0.0
        self.progress_update_interval = 0.5  # 500ms

        # Set web dashboard URL for API calls
        self.web_dashboard_url = "http://localhost:5000"  # Default web dashboard URL

        # Register message queue
        logger.info(f"Registering QtVideoPlayer component with message bus - name: '{self.name}'")
        self.message_queue = self.message_bus.register_component(self.name)
        logger.info(f"QtVideoPlayer component registered successfully - queue: {self.message_queue}")

        # Message processing timer (runs on Qt main thread)
        self.message_timer: Optional[QTimer] = None

        logger.info("QtVideoPlayer (PyQt6) initialized")
    



    
    def initialize(self) -> bool:
        """Initialize PyQt6 application and components"""
        try:
            logger.info("DEBUG: QtVideoPlayer.initialize called")
            with QMutexLocker(self.mutex):
                # Linux-specific system configuration
                self._configure_linux_system()
                
                # Create QApplication if needed
                existing_app = QApplication.instance()
                if existing_app:
                    logger.debug("Using existing QApplication instance")
                    self.app = existing_app
                else:
                    logger.info("Creating new QApplication")
                self.app = QApplication(sys.argv)
                self.app.setApplicationName("MbetterClient PyQt6")
                self.app.setApplicationVersion("2.0.0")
                self.app.setQuitOnLastWindowClosed(True)
                
                # CRITICAL: Check what attribute was removed here that might affect video
                # Was there originally: self.app.setAttribute(Qt.ApplicationAttribute.WA_OpaquePaintEvent, True)?
                logger.info("Skipping any application attributes that might interfere with video rendering")
                
                # Setup signal handling for proper shutdown
                self._setup_signal_handlers()
                
                # Linux-specific application settings
                self._configure_linux_app_settings()
                
                # Create player window with message bus reference and debug settings
                self.window = PlayerWindow(self.settings, self.message_bus, debug_overlay=self.debug_overlay, qt_player=self)
                
                # CRITICAL: Connect signal to slot for cross-thread video playback
                self.play_video_signal.connect(self.window.play_video, Qt.ConnectionType.QueuedConnection)
                logger.info("Connected play_video_signal to PlayerWindow.play_video with QueuedConnection")
                
                # Apply window focus for proper OpenGL context
                logger.debug("Applying window focus for OpenGL context")
                self.window.setWindowOpacity(1.0)
                self.window.setAutoFillBackground(False)  # Let Qt handle background
                
                # CRITICAL: Force window to become active and gain focus for OpenGL context
                self.window.show()
                self.window.raise_()
                self.window.activateWindow()
                self.app.processEvents()  # Process pending events
                
                # Force application to recognize this as active window
                self.app.setActiveWindow(self.window)
                self.app.processEvents()
                
                # Verify window focus was applied
                logger.debug(f"Window focus applied - activeWindow: {self.app.activeWindow()}")
                
                # Schedule additional window activation to ensure proper focus
                def ensure_focus():
                    if not self.app.activeWindow():
                        logger.debug("Applying additional window focus")
                        self.window.activateWindow()
                        self.window.setFocus()
                        self.app.setActiveWindow(self.window)
                        self.app.processEvents()
                        logger.debug(f"Additional focus applied - activeWindow: {self.app.activeWindow()}")
                
                QTimer.singleShot(100, ensure_focus)
                
                # Connect window signals
                self.window.position_changed.connect(self._on_position_changed)
                self.window.video_loaded.connect(self._on_video_loaded)
                
                # Subscribe to messages
                logger.info(f"Subscribing QtPlayer to message bus events...")
                self.message_bus.subscribe(self.name, MessageType.VIDEO_PLAY, self._handle_video_play)
                self.message_bus.subscribe(self.name, MessageType.VIDEO_PAUSE, self._handle_video_pause)
                self.message_bus.subscribe(self.name, MessageType.VIDEO_STOP, self._handle_video_stop)
                self.message_bus.subscribe(self.name, MessageType.VIDEO_SEEK, self._handle_video_seek)
                self.message_bus.subscribe(self.name, MessageType.VIDEO_VOLUME, self._handle_video_volume)
                self.message_bus.subscribe(self.name, MessageType.VIDEO_FULLSCREEN, self._handle_video_fullscreen)
                self.message_bus.subscribe(self.name, MessageType.TEMPLATE_CHANGE, self._handle_template_change)
                self.message_bus.subscribe(self.name, MessageType.OVERLAY_UPDATE, self._handle_overlay_update)
                self.message_bus.subscribe(self.name, MessageType.STATUS_REQUEST, self._handle_status_request)
                self.message_bus.subscribe(self.name, MessageType.START_INTRO, self._handle_start_intro)
                self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_MATCH, self._handle_play_video_match)
                self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_RESULT, self._handle_play_video_result)
                self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status)
                self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status)
                # Subscribe to web dashboard ready messages to get correct server URL
                self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_web_dashboard_ready)
                logger.info("QtPlayer subscriptions completed successfully")
                
                # Delay loading default overlay to allow JavaScript initialization
                QTimer.singleShot(300, self._load_default_overlay)  # Wait 0.3 seconds

                # Request initial game status to check if we should play intro
                QTimer.singleShot(3000, self._request_initial_game_status)  # Wait 3 seconds for everything to be ready
                
                logger.info("QtVideoPlayer (PyQt6) initialized successfully")
                return True
                
        except Exception as e:
            logger.error(f"QtVideoPlayer initialization failed: {e}")
            return False
    
    def _force_window_display(self):
        """Force window to display properly - aggressive approach"""
        try:
            if self.window:
                logger.debug("Forcing window display and repaint")
                self.window.show()
                self.window.raise_()
                self.window.activateWindow()
                self.window.repaint()
                
                # Force video widget to be visible and painted
                video_widget = self.window.video_widget
                if video_widget:
                    video_widget.show()
                    video_widget.repaint()
                    
                    # Force base video widget to be visible
                    base_video = video_widget.get_video_widget()
                    if base_video:
                        base_video.show()
                        base_video.repaint()
                
                logger.debug("Window display forced")
        except Exception as e:
            logger.error(f"Failed to force window display: {e}")
    
    def _configure_linux_system(self):
        """Configure Linux-specific system settings"""
        import platform
        import os

        if platform.system() != 'Linux':
            return

        # Linux environment variables are properly configured for Qt xcb platform support

        try:
            # TEMPORARILY DISABLED - ALL environment variables that might interfere with video
            linux_env_vars = {
                # 'QT_QPA_PLATFORM': 'xcb',
                # 'QT_AUTO_SCREEN_SCALE_FACTOR': '1',
                # 'QT_SCALE_FACTOR': '1',
                # 'QT_LOGGING_RULES': 'qt.qpa.xcb.info=false;qt.qpa.xcb.xcberror.warning=false',
                # 'QTWEBENGINE_CHROMIUM_FLAGS': '--no-sandbox --disable-gpu-sandbox',
                # 'QTWEBENGINE_DISABLE_SANDBOX': '1',
                # COMMENTED OUT: These OpenGL settings prevent video display
                # 'QT_OPENGL': 'software',  # Use software rendering instead of hardware - BLOCKS VIDEO
                # 'QT_QUICK_BACKEND': 'software',  # Software backend for Qt Quick - BLOCKS VIDEO
                # 'QT_XCB_GL_INTEGRATION': 'none',  # Disable XCB OpenGL integration - BLOCKS VIDEO
                # 'LIBGL_ALWAYS_SOFTWARE': '1',  # Force software OpenGL - BLOCKS VIDEO
                # 'QT_OPENGL_BUGLIST': 'disable',  # Disable OpenGL bug workarounds - BLOCKS VIDEO
            }

            for key, value in linux_env_vars.items():
                if key not in os.environ:
                    os.environ[key] = value
                    logger.debug(f"Set Linux env var: {key}={value}")

            # Handle headless/server environments
            if not os.environ.get('DISPLAY'):
                logger.warning("No DISPLAY environment variable - may be running headless")
                os.environ['QT_QPA_PLATFORM'] = 'offscreen'
            else:
                # Disable screensaver and DPMS to keep display always on
                try:
                    import subprocess
                    logger.info("Disabling screensaver and DPMS to keep display always on")

                    # Disable screensaver
                    result_s = subprocess.run(['xset', 's', 'off'], capture_output=True, text=True, timeout=5)
                    if result_s.returncode == 0:
                        logger.info("Screensaver disabled successfully")
                    else:
                        logger.warning(f"Failed to disable screensaver: {result_s.stderr}")

                    # Disable DPMS (Display Power Management Signaling)
                    result_dpms = subprocess.run(['xset', '-dpms'], capture_output=True, text=True, timeout=5)
                    if result_dpms.returncode == 0:
                        logger.info("DPMS disabled successfully")
                    else:
                        logger.warning(f"Failed to disable DPMS: {result_dpms.stderr}")

                except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError) as e:
                    logger.warning(f"Could not disable screensaver/DPMS (xset not available or failed): {e}")

        except Exception as e:
            logger.warning(f"Linux system configuration warning: {e}")
    
    def _configure_linux_app_settings(self):
        """Configure Linux-specific QApplication settings - DISABLED to match test script"""
        import platform
        
        if platform.system() != 'Linux' or not self.app:
            return
            
        try:
            # CRITICAL: Disable all Qt attributes to match simple test_video_debug.py approach
            logger.info("DISABLING all Qt application attributes to match test script - simple QApplication setup")
            # COMMENTED OUT: These attributes may interfere with video rendering
            # self.app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True)
            # self.app.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True)
            # if hasattr(Qt.ApplicationAttribute, 'AA_X11InitThreads'):
            #     self.app.setAttribute(Qt.ApplicationAttribute.AA_X11InitThreads, True)
            
            logger.info("Qt attributes disabled - using simple QApplication like test script")
            
        except Exception as e:
            logger.warning(f"Linux application configuration warning: {e}")
    
    def _get_web_server_base_url(self) -> str:
        """Get the web server base URL for API requests"""
        try:
            # Return the stored web dashboard URL, or default
            return self.web_dashboard_url or "http://127.0.0.1:5001"
        except Exception as e:
            logger.error(f"Failed to get web server base URL: {e}")
            return "http://127.0.0.1:5001"

    def _load_default_overlay(self):
        """Load default overlay display with enhanced WebChannel readiness checking"""
        try:
            default_data = {
                'title': 'MbetterClient PyQt6 Player',
                'subtitle': 'Ready for Content',
                'ticker': 'Welcome to MbetterClient • Professional Video Overlay System • Real-time Updates • Hardware Accelerated Playback',
                'showStats': False,
                'webServerBaseUrl': self._get_web_server_base_url()
            }

            # NOTE: Removed fixture data pre-fetching - overlay templates handle their own data fetching via JavaScript
            # The fixtures.html template uses WebSocket, WebChannel, or API calls to get fixture data

            if not self.window:
                logger.debug("Window not ready for overlay loading")
                return

            # Use the separate window overlay instead of video widget overlay
            if hasattr(self.window, 'window_overlay'):
                overlay_view = self.window.window_overlay

                # Check overlay type and handle accordingly
                if self.window._is_native_overlay(overlay_view):
                    # Native overlay is always ready
                    logger.debug("Native overlay ready, updating immediately...")
                    self._send_safe_overlay_update(overlay_view, default_data)
                elif isinstance(overlay_view, OverlayWebView):
                    # WebEngine overlay - enhanced readiness checking
                    if not self._is_webengine_ready(overlay_view):
                        logger.debug("WebEngine overlay not ready, retrying in 1 second...")
                        QTimer.singleShot(1000, self._load_default_overlay)
                        return

                    # Additional delay before sending data to ensure JavaScript is fully initialized
                    logger.debug("WebEngine ready, scheduling overlay update...")
                    QTimer.singleShot(500, lambda: self._send_safe_overlay_update(overlay_view, default_data))
                else:
                    logger.warning(f"Unknown overlay type: {type(overlay_view)}")
            else:
                logger.warning("No window_overlay available for default overlay loading")

        except Exception as e:
            logger.error(f"Failed to load default overlay: {e}")
            # Retry on error with longer delay
            QTimer.singleShot(3000, self._load_default_overlay)
    
    def _is_webengine_ready(self, overlay_view):
        """Check if WebEngine overlay is fully ready for updates"""
        try:
            # Check if overlay_view is a QWebEngineView
            if not hasattr(overlay_view, 'page'):
                return False
                
            page = overlay_view.page()
            if not page:
                return False
            
            # Check if WebChannel exists
            if not page.webChannel():
                return False
                
            # Check if URL is loaded (not about:blank)
            current_url = overlay_view.url().toString()
            if current_url == 'about:blank' or not current_url:
                return False
            
            # Check if the overlay_channel object exists and is ready
            if not (hasattr(overlay_view, 'overlay_channel') and overlay_view.overlay_channel):
                return False
                
            return True
            
        except Exception as e:
            logger.debug(f"WebEngine readiness check failed: {e}")
            return False
    
    def _clean_overlay_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Clean overlay data by removing null/undefined values"""
        cleaned_data = {}
        
        # Define valid overlay properties and their expected types
        valid_properties = {
            'title': str,
            'subtitle': str,
            'ticker': str,
            'currentTime': str,
            'showStats': bool,
            'customCSS': str
        }
        
        for key, value in data.items():
            # Skip null, undefined, or empty string values
            if value is None or value == "":
                logger.debug(f"Skipping null/empty property '{key}' in overlay data")
                continue
            
            # Validate property type if it's a known property
            if key in valid_properties:
                expected_type = valid_properties[key]
                if not isinstance(value, expected_type):
                    logger.debug(f"Converting property '{key}' from {type(value)} to {expected_type}")
                    try:
                        # Convert to expected type
                        if expected_type == str:
                            cleaned_data[key] = str(value)
                        elif expected_type == bool:
                            cleaned_data[key] = bool(value)
                        else:
                            cleaned_data[key] = value
                    except (ValueError, TypeError):
                        logger.warning(f"Failed to convert property '{key}' to {expected_type}, skipping")
                        continue
                else:
                    cleaned_data[key] = value
            else:
                # Unknown property, pass through if not null
                cleaned_data[key] = value
        
        data_keys = list(cleaned_data.keys()) if isinstance(cleaned_data, dict) else []
        logger.debug(f"Cleaned overlay data: {len(cleaned_data)} items with keys: {data_keys}")
        return cleaned_data
    
    def _send_safe_overlay_update(self, overlay_view, data):
        """Send overlay update with additional safety checks"""
        try:
            # Clean data before sending to prevent null property issues
            cleaned_data = self._clean_overlay_data(data)
            if not cleaned_data:
                logger.debug("No valid data to send to overlay after cleaning")
                return
            
            if hasattr(self, 'window') and self.window:
                # Use the PlayerWindow's _update_overlay_safe method for consistency
                self.window._update_overlay_safe(overlay_view, cleaned_data)
                logger.info("Default WebEngine overlay loaded successfully")
            else:
                # Fallback to direct update if no window available
                if isinstance(overlay_view, OverlayWebView):
                    if self._is_webengine_ready(overlay_view):
                        overlay_view.update_overlay_data(cleaned_data)
                        logger.info("WebEngine overlay updated successfully")
                    else:
                        logger.debug("WebEngine overlay not ready, skipping update")
                else:
                    # Native overlay
                    overlay_view.update_overlay_data(cleaned_data)
                    logger.info("Native overlay updated successfully")
            
        except Exception as e:
            logger.error(f"Failed to send safe overlay update: {e}")
    
    def start_message_processing(self):
        """Start message processing using Qt timer on main thread"""
        if self.message_timer and self.message_timer.isActive():
            logger.warning("Message processing timer is already running")
            return
            
        logger.info(f"Starting message processing timer for component: {self.name}")
        logger.info(f"Message bus registered components: {list(self.message_bus._queues.keys()) if hasattr(self.message_bus, '_queues') else 'Unknown'}")
        
        # Create timer that runs on Qt main thread
        self.message_timer = QTimer()
        self.message_timer.timeout.connect(self._process_messages_main_thread)
        self.message_timer.start(10)  # Process messages every 10ms
        
        logger.info("QtPlayer message processing timer started successfully on Qt main thread")
    
    def stop_message_processing(self):
        """Stop message processing timer"""
        if self.message_timer and self.message_timer.isActive():
            self.message_timer.stop()
            logger.info("QtPlayer message processing timer stopped")
        else:
            logger.info("QtPlayer message processing timer already stopped")
    
    def _process_messages_main_thread(self):
        """Process messages from message bus on Qt main thread via QTimer"""
        try:
            # Process multiple messages per timer event for efficiency
            messages_processed = 0
            max_messages_per_cycle = 5
            
            while messages_processed < max_messages_per_cycle:
                try:
                    # Get message with no timeout (non-blocking)
                    message = self.message_bus.get_message(self.name, timeout=0.001)
                    if not message:
                        break
                        
                    messages_processed += 1
                    if self.debug_player:
                        logger.info(f"QtPlayer RECEIVED message: {message.type.value} from {message.sender}")
                        # Don't log full message data to avoid cluttering logs with HTML content
                        logger.debug(f"Message data keys: {list(message.data.keys()) if isinstance(message.data, dict) else 'non-dict data'}")

                    # Process message directly on main thread - no threading issues!
                    self._process_message(message)
                    
                except Exception as e:
                    logger.error(f"QtPlayer message processing error: {e}")
                    break
            
            # Send periodic progress updates if playing
            if (self.window and
                self.window.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState):
                self._send_progress_update()
                
        except Exception as e:
            logger.error(f"QtPlayer main thread message processing failed: {e}")
    
    def run(self):
        """Run the Qt event loop (this should be called on the main thread)"""
        try:
            logger.info("QtVideoPlayer starting Qt event loop")
            
            # Send ready status
            logger.info(f"QtVideoPlayer sending ready status from component: {self.name}")
            ready_message = MessageBuilder.system_status(
                sender=self.name,
                status="ready",
                details={"fullscreen": self.settings.fullscreen, "version": "PyQt6-2.0.0"}
            )
            publish_result = self.message_bus.publish(ready_message)
            logger.info(f"Ready status message publish result: {publish_result}")
            
            # Start message processing timer on main thread
            self.start_message_processing()
            
            # Run Qt event loop (this blocks until app quits)
            if self.app:
                return self.app.exec()
            else:
                logger.error("No QApplication instance available")
                return 1
                
        except Exception as e:
            logger.error(f"QtVideoPlayer run failed: {e}")
            return 1
    
    def _setup_signal_handlers(self):
        """Setup Qt-specific signal handlers - let main app handle Python signals"""
        try:
            # Only handle Qt application aboutToQuit signal
            # Let the main application handle SIGINT/SIGTERM to avoid conflicts
            if self.app:
                self.app.aboutToQuit.connect(self._handle_app_quit)
            
            logger.debug("Qt signal handlers setup completed")
            
        except Exception as e:
            logger.warning(f"Qt signal handler setup failed: {e}")
    
    def _handle_app_quit(self):
        """Handle Qt application quit signal"""
        logger.info("Qt application about to quit")
        
        # Ensure message processing timer is stopped
        self.stop_message_processing()
    
    def shutdown(self):
        """Shutdown video player"""
        try:
            logger.info("Shutting down QtVideoPlayer...")
            
            # Stop message processing timer
            self.stop_message_processing()
            
            with QMutexLocker(self.mutex):
                if self.window:
                    self.window.close()
                    self.window = None
                
                if self.app and not self.app.closingDown():
                    self.app.quit()
            
        except Exception as e:
            logger.error(f"QtVideoPlayer shutdown error: {e}")
    
    def _process_message(self, message: Message):
        """Process received message by routing to appropriate handlers"""
        try:
            if self.debug_player:
                logger.info(f"QtPlayer processing message type: {message.type.value}")

            # Route messages to appropriate handlers
            if message.type == MessageType.VIDEO_PLAY:
                if self.debug_player:
                    logger.info("Calling _handle_video_play handler")
                self._handle_video_play(message)
            elif message.type == MessageType.VIDEO_PAUSE:
                if self.debug_player:
                    logger.info("Calling _handle_video_pause handler")
                self._handle_video_pause(message)
            elif message.type == MessageType.VIDEO_STOP:
                if self.debug_player:
                    logger.info("Calling _handle_video_stop handler")
                self._handle_video_stop(message)
            elif message.type == MessageType.VIDEO_SEEK:
                if self.debug_player:
                    logger.info("Calling _handle_video_seek handler")
                self._handle_video_seek(message)
            elif message.type == MessageType.VIDEO_VOLUME:
                if self.debug_player:
                    logger.info("Calling _handle_video_volume handler")
                self._handle_video_volume(message)
            elif message.type == MessageType.VIDEO_FULLSCREEN:
                if self.debug_player:
                    logger.info("Calling _handle_video_fullscreen handler")
                self._handle_video_fullscreen(message)
            elif message.type == MessageType.TEMPLATE_CHANGE:
                if self.debug_player:
                    logger.info("Calling _handle_template_change handler")
                self._handle_template_change(message)
            elif message.type == MessageType.OVERLAY_UPDATE:
                if self.debug_player:
                    logger.info("Calling _handle_overlay_update handler")
                self._handle_overlay_update(message)
            elif message.type == MessageType.STATUS_REQUEST:
                if self.debug_player:
                    logger.info("Calling _handle_status_request handler")
                self._handle_status_request(message)
            elif message.type == MessageType.START_INTRO:
                if self.debug_player:
                    logger.info("Calling _handle_start_intro handler")
                self._handle_start_intro(message)
            elif message.type == MessageType.PLAY_VIDEO_MATCH:
                if self.debug_player:
                    logger.info("Calling _handle_play_video_match handler")
                self._handle_play_video_match(message)
            elif message.type == MessageType.PLAY_VIDEO_RESULT:
                if self.debug_player:
                    logger.info("Calling _handle_play_video_result handler")
                self._handle_play_video_result(message)
            elif message.type == MessageType.CUSTOM:
                if self.debug_player:
                    logger.info("Calling _handle_custom_message handler")
                self._handle_custom_message(message)
            else:
                if self.debug_player:
                    logger.warning(f"No handler for message type: {message.type.value}")

        except Exception as e:
            logger.error(f"Failed to process message: {e}")
            import traceback
            logger.error(f"Full traceback: {traceback.format_exc()}")
    
    def _on_position_changed(self, position: int, duration: int):
        """Handle position changes from player window"""
        try:
            # Progress updates are now handled by the throttled periodic _send_progress_update method
            # to prevent flooding the message bus with VIDEO_PROGRESS messages
            pass

        except Exception as e:
            logger.error(f"Failed to handle position change: {e}")
    
    def _on_video_loaded(self, file_path: str):
        """Handle video loaded event"""
        try:
            logger.info(f"Video loaded: {file_path}")
            # Send video loaded status message
            loaded_message = MessageBuilder.system_status(
                sender=self.name,
                status="video_loaded",
                details={"file_path": file_path}
            )
            self.message_bus.publish(loaded_message, broadcast=True)
        except Exception as e:
            logger.error(f"Failed to handle video loaded event: {e}")
    
    def _send_progress_update(self):
        """Send video progress update (throttled to 2 per second)"""
        try:
            # Throttle progress updates to 2 per second (500ms minimum interval)
            current_time = time.time()
            if current_time - self.last_progress_update_time >= self.progress_update_interval:
                if self.window and self.window.media_player.duration() > 0:
                    position = self.window.media_player.position()
                    duration = self.window.media_player.duration()
                    percentage = (position / duration) * 100 if duration > 0 else 0

                    progress_message = MessageBuilder.video_progress(
                        sender=self.name,
                        position=position / 1000.0,  # Convert to seconds
                        duration=duration / 1000.0,  # Convert to seconds
                        percentage=percentage
                    )
                    self.message_bus.publish(progress_message, broadcast=True)
                    self.last_progress_update_time = current_time

        except Exception as e:
            logger.error(f"Failed to send progress update: {e}")
    
    # Message handlers for various video control commands
    def _handle_video_play(self, message: Message):
        """Handle video play message with loop control - now running on Qt main thread"""
        try:
            file_path = message.data.get("file_path")
            template_data = message.data.get("overlay_data", {})
            template_name = message.data.get("template")  # Extract template name from message
            
            # Extract loop control data
            loop_data = {}
            loop_fields = ['action', 'loop_mode', 'infinite_loop', 'loop_count', 'continuous_playback', 'repeat']
            for field in loop_fields:
                if field in message.data:
                    loop_data[field] = message.data[field]
            
            logger.debug(f"LOOP DEBUG: VIDEO_PLAY message received from {message.sender}")
            logger.debug(f"LOOP DEBUG: File path: {file_path}")
            logger.debug(f"LOOP DEBUG: Template name: {template_name}")
            logger.debug(f"LOOP DEBUG: Overlay data: {template_data}")
            logger.debug(f"LOOP DEBUG: Loop control data extracted: {loop_data}")
            logger.debug(f"LOOP DEBUG: Raw message data: {message.data}")
            
            if not file_path:
                logger.error("No file path provided for video play")
                return
            
            if not self.window:
                logger.error("Qt player window not available")
                return
            
            # Log current overlay configuration
            logger.info("Qt Player overlay configuration: QWebEngineView")
            
            # PERFECT: Now running directly on Qt main thread - no threading issues!
            logger.info(f"Handler thread: {threading.current_thread().name}")
            logger.info(f"Handler is main thread: {threading.current_thread() is threading.main_thread()}")
            logger.info("CALLING play_video() DIRECTLY on Qt main thread - no cross-thread issues!")
            
            # Direct call - we're already on the main thread! Pass template name and loop data
            self.window.play_video(file_path, template_data, template_name, loop_data)
            logger.info("play_video() called successfully on main thread with loop support")
            
        except Exception as e:
            logger.error(f"Failed to handle video play: {e}")
            import traceback
            logger.error(f"Full traceback: {traceback.format_exc()}")
    
    def _handle_video_pause(self, message: Message):
        """Handle video pause message - now running on Qt main thread"""
        try:
            if self.window:
                # Direct call - we're already on the main thread!
                self.window.media_player.pause()
        except Exception as e:
            logger.error(f"Failed to handle video pause: {e}")
    
    def _handle_video_stop(self, message: Message):
        """Handle video stop message - now running on Qt main thread"""
        try:
            if self.window:
                # Direct call - we're already on the main thread!
                self.window.stop_playback()
        except Exception as e:
            logger.error(f"Failed to handle video stop: {e}")
    
    def _handle_video_seek(self, message: Message):
        """Handle video seek message - now running on Qt main thread"""
        try:
            position = message.data.get("position", 0)
            if self.window:
                # Direct call - we're already on the main thread!
                self._do_video_seek(position)
        except Exception as e:
            logger.error(f"Failed to handle video seek: {e}")
    
    def _do_video_seek(self, position):
        """Execute video seek on main thread"""
        try:
            duration = self.window.media_player.duration()
            if duration > 0:
                percentage = int(position * 100 / duration)
                self.window.seek_to_position(percentage)
        except Exception as e:
            logger.error(f"Failed to execute video seek: {e}")
    
    def _handle_video_volume(self, message: Message):
        """Handle video volume message - now running on Qt main thread"""
        try:
            volume = message.data.get("volume", 100)
            if self.window:
                # Direct call - we're already on the main thread!
                self.window.set_volume(volume)
        except Exception as e:
            logger.error(f"Failed to handle video volume: {e}")
    
    def _handle_video_fullscreen(self, message: Message):
        """Handle video fullscreen message - now running on Qt main thread"""
        try:
            fullscreen = message.data.get("fullscreen", True)
            if self.window:
                # Direct call - we're already on the main thread!
                if fullscreen:
                    self.window.showFullScreen()
                else:
                    self.window.showNormal()
        except Exception as e:
            logger.error(f"Failed to handle video fullscreen: {e}")
    
    def _handle_template_change(self, message: Message):
        """Handle template change message - now running on Qt main thread"""
        try:
            template_name = message.data.get("template_name", "")
            template_data = message.data.get("template_data", {})
            reload_template = template_data.get("reload_template", False)
            load_specific_template = template_data.get("load_specific_template", "")

            if self.debug_overlay:
                logger.debug(f"GREEN SCREEN DEBUG: Template change message received")
                logger.debug(f"GREEN SCREEN DEBUG: Template name: {template_name}")
                logger.debug(f"GREEN SCREEN DEBUG: Reload template: {reload_template}")
                logger.debug(f"GREEN SCREEN DEBUG: Load specific template: {load_specific_template}")

            if self.window and hasattr(self.window, 'window_overlay'):
                overlay_view = self.window.window_overlay

                # Check video player state before template change
                if hasattr(self.window, 'media_player') and self.debug_overlay:
                    video_state = self.window.media_player.playbackState()
                    logger.debug(f"GREEN SCREEN DEBUG: Video playback state during template change: {video_state}")
                    logger.debug(f"GREEN SCREEN DEBUG: Video position: {self.window.media_player.position()}")
                    logger.debug(f"GREEN SCREEN DEBUG: Video duration: {self.window.media_player.duration()}")

                # Check overlay window transparency state (only in multi-window mode)
                if hasattr(self.window, 'overlay_window') and self.window.overlay_window and not self.window.settings.overlay_single_window and self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Overlay window geometry: {self.window.overlay_window.geometry()}")
                    logger.debug(f"GREEN SCREEN DEBUG: Overlay window visible: {self.window.overlay_window.isVisible()}")
                elif self.window.settings.overlay_single_window and self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Single window overlay mode - no separate overlay window")

                # CRITICAL FIX: Protect video context during template changes
                video_widget = None
                if hasattr(self.window, 'video_widget') and hasattr(self.window.video_widget, 'get_video_widget'):
                    video_widget = self.window.video_widget.get_video_widget()
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN FIX: Video widget state before template change - visible: {video_widget.isVisible() if video_widget else 'N/A'}")

                # Load specific template if requested and using WebEngine overlay
                if load_specific_template and isinstance(overlay_view, OverlayWebView):
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN DEBUG: About to load specific template: {load_specific_template}")
                        logger.debug(f"GREEN SCREEN FIX: Protecting video rendering during template load")
                    overlay_view.load_template(load_specific_template)

                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN DEBUG: Specific template load initiated")
                # Otherwise reload current template if requested and using WebEngine overlay
                elif reload_template and isinstance(overlay_view, OverlayWebView):
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN DEBUG: About to reload current template")
                        logger.debug(f"GREEN SCREEN FIX: Protecting video rendering during template reload")
                    overlay_view.reload_current_template()
                    if self.debug_overlay:
                        logger.debug(f"GREEN SCREEN DEBUG: Current template reload initiated")

                # CRITICAL FIX: Force video widget refresh after template change
                if video_widget and self.debug_overlay:
                    logger.debug(f"GREEN SCREEN FIX: Forcing video widget refresh after template change")
                    video_widget.repaint()
                    video_widget.update()

                    # Force media player to refresh video output
                    if hasattr(self.window, 'media_player'):
                        logger.debug(f"GREEN SCREEN FIX: Refreshing media player video output")
                        current_output = self.window.media_player.videoOutput()
                        if current_output:
                            # Force video output refresh by briefly setting to None and back
                            self.window.media_player.setVideoOutput(None)
                            self.window.media_player.setVideoOutput(current_output)
                            logger.debug(f"GREEN SCREEN FIX: Media player video output refreshed")

                    logger.debug(f"GREEN SCREEN FIX: Video widget state after template change - visible: {video_widget.isVisible()}")

                # Update overlay data if provided (excluding template control flags)
                if template_data:
                    # Remove template control flags from data to be sent to overlay
                    data_to_send = {k: v for k, v in template_data.items()
                                  if k not in ['reload_template', 'load_specific_template']}

                    if data_to_send:
                        # Log data summary instead of full content to avoid cluttering logs with HTML
                        if self.debug_overlay:
                            data_keys = list(data_to_send.keys())
                            logger.debug(f"GREEN SCREEN DEBUG: Sending overlay data with keys: {data_keys}")
                        # Validate and clean template_data before sending to overlay
                        cleaned_data = self._clean_overlay_data(data_to_send)
                        if cleaned_data:  # Only send if we have valid data after cleaning
                            # Send data after a short delay to ensure page has loaded
                            QTimer.singleShot(500, lambda: self.window._update_overlay_safe(overlay_view, cleaned_data))
                        else:
                            logger.debug("Template data contained only null/undefined values, skipping update")

                if self.debug_overlay:
                    logger.debug(f"GREEN SCREEN DEBUG: Template change handler completed")

        except Exception as e:
            if self.debug_overlay:
                logger.debug(f"GREEN SCREEN DEBUG: Template change handler failed: {e}")
            logger.error(f"Failed to handle template change: {e}")
    
    def _handle_overlay_update(self, message: Message):
        """Handle overlay update message - now running on Qt main thread"""
        try:
            overlay_data = message.data.get("overlay_data", {})
            
            if self.window and overlay_data and hasattr(self.window, 'window_overlay'):
                # Validate and clean overlay_data before sending to overlay
                cleaned_data = self._clean_overlay_data(overlay_data)
                if cleaned_data:  # Only send if we have valid data after cleaning
                    overlay_view = self.window.window_overlay
                    self.window._update_overlay_safe(overlay_view, cleaned_data)
                else:
                    logger.debug("Overlay data contained only null/undefined values, skipping update")
                
        except Exception as e:
            logger.error(f"Failed to handle overlay update: {e}")
    
    def _handle_status_request(self, message: Message):
        """Handle status request message - now running on Qt main thread"""
        try:
            if self.window:
                # Direct call - we're already on the main thread!
                self._do_status_request(message)
        except Exception as e:
            logger.error(f"Failed to handle status request: {e}")

    def _handle_start_intro(self, message: Message):
        """Handle START_INTRO message - find and play intro video with configured template sequence"""
        try:
            logger.info("Handling START_INTRO message")

            # Get match_id and fixture_id from message
            match_id = message.data.get("match_id")
            fixture_id = message.data.get("fixture_id")

            # Find the correct intro video file
            intro_video_path = self._find_intro_video_file(match_id)

            if intro_video_path:
                logger.info(f"Found intro video: {intro_video_path}")

                # Load intro templates configuration
                intro_templates = self._load_intro_templates_config()

                if intro_templates and intro_templates.get('templates'):
                    # Use configured template sequence
                    template_sequence = intro_templates['templates']
                    logger.info(f"Using configured template sequence: {[t['name'] for t in template_sequence]}")

                    # Set up loop control for intro video with template rotation
                    loop_data = {
                        'infinite_loop': True,  # Loop indefinitely until PLAY_VIDEO_MATCH
                        'continuous_playback': True,
                        'template_sequence': template_sequence,
                        'rotating_time': intro_templates.get('rotating_time', '00:15')
                    }

                    # Start with first template in sequence
                    first_template = template_sequence[0]['name'] if template_sequence else 'news_template'

                    # Play the intro video with template rotation
                    if self.window:
                        self.window.play_video(
                            str(intro_video_path),
                            template_data={"title": "Intro Video", "subtitle": "Preparing for match..."},
                            template_name=first_template,
                            loop_data=loop_data
                        )
                        logger.info(f"Intro video started with template rotation: {first_template}")
                    else:
                        logger.error("No window available for intro video playback")
                else:
                    # Fallback to default behavior if no templates configured
                    logger.warning("No intro templates configured, using default news_template")
                    loop_data = {
                        'infinite_loop': True,
                        'continuous_playback': True
                    }

                    if self.window:
                        self.window.play_video(
                            str(intro_video_path),
                            template_data={"title": "Intro Video", "subtitle": "Preparing for match..."},
                            template_name="news_template",
                            loop_data=loop_data
                        )
                        logger.info("Intro video started in loop mode with default template")
                    else:
                        logger.error("No window available for intro video playback")
            else:
                logger.warning("No intro video found, skipping intro playback")

        except Exception as e:
            logger.error(f"Failed to handle START_INTRO message: {e}")

    def _handle_play_video_match(self, message: Message):
        """Handle PLAY_VIDEO_MATCH message - play match video with overlay template and wait for messages"""
        try:
            logger.info("Handling PLAY_VIDEO_MATCH message")

            match_id = message.data.get("match_id")
            video_filename = message.data.get("video_filename")
            fixture_id = message.data.get("fixture_id")
            result = message.data.get("result")  # Extract result parameter

            if not match_id or not video_filename:
                logger.error("Missing match_id or video_filename in PLAY_VIDEO_MATCH message")
                return

            # Use result to determine video filename if not provided or to validate
            if result:
                expected_filename = f"{result}.mp4"
                if video_filename != expected_filename:
                    logger.warning(f"Video filename mismatch: expected {expected_filename}, got {video_filename}")
                    video_filename = expected_filename  # Override with result-based filename
                logger.info(f"Match result: {result}, using video: {video_filename}")
            else:
                logger.warning("No result provided in PLAY_VIDEO_MATCH message")

            # Stop the current intro video loop and template rotation
            if self.window and hasattr(self.window, 'media_player'):
                logger.info("Stopping intro video loop and template rotation")
                self.window.stop_playback()

                # Explicitly stop template rotation timer
                if hasattr(self.window, 'template_rotation_timer') and self.window.template_rotation_timer and self.window.template_rotation_timer.isActive():
                    self.window.template_rotation_timer.stop()
                    logger.info("Template rotation timer stopped for match video")

            # Ensure the match ZIP file is extracted before trying to find the video
            logger.info(f"Ensuring ZIP file is extracted for match {match_id} before finding video")
            unzip_success = self._unzip_match_zip_file(match_id)

            if not unzip_success:
                logger.error(f"ZIP extraction failed for match {match_id}, marking as failed and skipping video playback")
                return

            # Find the match video file from the ZIP
            logger.info(f"Looking for match video: {video_filename} for match {match_id}")
            match_video_path = self._find_match_video_file(match_id, video_filename)

            if match_video_path:
                logger.info(f"Successfully found match video: {match_video_path}")
            else:
                logger.error(f"Match video not found: {video_filename} for match {match_id}")
                logger.error("ZIP extraction may have failed or video file may not exist in ZIP")

            if match_video_path:
                logger.info(f"Found match video: {match_video_path}")

                # Set match video tracking flags on PlayerWindow
                self.window.is_playing_match_video = True
                self.window.current_match_id = match_id
                self.window.current_match_video_filename = video_filename
                self.window.current_fixture_id = fixture_id

                # Reset loop state for match videos (they don't loop)
                self.loop_enabled = False
                self.infinite_loop = False
                self.loop_count = 0
                self.current_loop_iteration = 0

                # Play the match video (no looping)
                if self.window:
                    # Update overlay with result information and apply overlay template
                    overlay_data = {
                        "title": f"Match {match_id}",
                        "subtitle": f"Result: {result}" if result else "Live Action",
                        "result": result,
                        "match_id": match_id,
                        "fixture_id": fixture_id
                    }

                    self.window.play_video(
                        str(match_video_path),
                        template_data=overlay_data,
                        template_name="match_video.html",  # Use match_video template for overlay
                        loop_data=None  # No looping for match videos
                    )
                    logger.info(f"Match video started: {video_filename} with result: {result} and overlay template applied")

                    # Player should now wait for other messages while video plays
                    # Video play will continue until PLAY_VIDEO_MATCH_DONE is sent
                else:
                    logger.error("No window available for match video playback")
            else:
                logger.error(f"Match video not found: {video_filename} for match {match_id}")

        except Exception as e:
            logger.error(f"Failed to handle PLAY_VIDEO_MATCH message: {e}")

    def _load_intro_templates_config(self) -> Optional[Dict[str, Any]]:
        """Load intro templates configuration from database with retry mechanism"""
        try:
            from ..database.manager import DatabaseManager
            from ..database.models import GameConfigModel
            import json
            import time

            logger.info("QtPlayer: Starting to load intro templates configuration")

            # Retry mechanism for database access during startup
            max_retries = 3
            retry_delay = 0.5  # 500ms delay between retries

            for attempt in range(max_retries):
                try:
                    logger.debug(f"QtPlayer: Database access attempt {attempt + 1}")

                    # Get database manager from message bus or create one
                    db_manager = None
                    if hasattr(self, 'message_bus') and self.message_bus:
                        logger.debug("QtPlayer: Trying to get db_manager from message bus")
                        # Try to get db_manager from web_dashboard component
                        try:
                            web_dashboard_queue = self.message_bus._queues.get('web_dashboard')
                            if web_dashboard_queue and hasattr(web_dashboard_queue, 'component'):
                                component = web_dashboard_queue.component
                                if hasattr(component, 'db_manager'):
                                    db_manager = component.db_manager
                                    logger.info(f"QtPlayer: Got db_manager from web_dashboard component (attempt {attempt + 1})")
                        except Exception as e:
                            logger.debug(f"QtPlayer: Could not get db_manager from message bus (attempt {attempt + 1}): {e}")

                    if not db_manager:
                        logger.debug("QtPlayer: Creating database manager directly")
                        # Fallback: create database manager directly
                        from ..config.settings import get_user_data_dir
                        db_path = get_user_data_dir() / "mbetterclient.db"
                        logger.debug(f"QtPlayer: Database path: {db_path}")
                        db_manager = DatabaseManager(str(db_path))
                        # Initialize the database manager
                        if not db_manager.initialize():
                            logger.warning(f"QtPlayer: Failed to initialize database manager in Qt player (attempt {attempt + 1})")
                            if attempt < max_retries - 1:
                                time.sleep(retry_delay)
                                continue
                            return self._get_default_intro_config()

                    logger.debug("QtPlayer: Database manager ready, getting session")
                    session = db_manager.get_session()
                    try:
                        logger.debug("QtPlayer: Querying for intro_templates_config")
                        # Get intro templates configuration from database
                        intro_config = session.query(GameConfigModel).filter_by(
                            config_key='intro_templates_config'
                        ).first()

                        if intro_config:
                            logger.debug(f"QtPlayer: Found intro config, value length: {len(intro_config.config_value)}")
                            try:
                                config = json.loads(intro_config.config_value)
                                logger.info(f"QtPlayer: Successfully loaded intro templates config from database: {len(config.get('templates', []))} templates")
                                return config
                            except (json.JSONDecodeError, TypeError) as e:
                                logger.warning(f"QtPlayer: Invalid JSON in intro templates config: {e}")
                                return self._get_default_intro_config()
                        else:
                            logger.info("QtPlayer: No intro templates config found in database, using defaults")
                            return self._get_default_intro_config()

                    finally:
                        session.close()

                except Exception as e:
                    logger.warning(f"QtPlayer: Database access attempt {attempt + 1} failed: {str(e)}")
                    if attempt < max_retries - 1:
                        time.sleep(retry_delay)
                        continue
                    else:
                        logger.error(f"QtPlayer: All database access attempts failed: {str(e)}")
                        return self._get_default_intro_config()

            # If we get here, all retries failed
            logger.error("QtPlayer: All retries exhausted, returning defaults")
            return self._get_default_intro_config()

        except Exception as e:
            logger.error(f"QtPlayer: Error loading intro templates from database: {str(e)}")
            return self._get_default_intro_config()

    def _get_default_intro_config(self) -> Dict[str, Any]:
        """Get default intro templates configuration"""
        return {
            'templates': [],
            'default_show_time': '00:30',
            'rotating_time': '00:15'
        }

    def _find_intro_video_file(self, match_id: int) -> Optional[Path]:
        """Find the correct intro video file based on priority"""
        try:
            logger.debug(f"DEBUG: Searching for intro video file for match_id: {match_id}")

            # Priority 1: Check for INTRO.mp4 in the ZIP file for the match
            if match_id:
                import zipfile
                from ..database.manager import DatabaseManager
                from ..config.settings import get_user_data_dir

                # Get database manager
                db_manager = self._get_database_manager()
                if db_manager:
                    session = db_manager.get_session()
                    try:
                        # Get the match from database
                        from ..database.models import MatchModel
                        match = session.query(MatchModel).filter_by(id=match_id).first()

                        if match and match.zip_filename:
                            # Determine ZIP file location
                            user_data_dir = get_user_data_dir()
                            zip_file_path = user_data_dir / "zip_files" / match.zip_filename

                            logger.debug(f"DEBUG: Checking ZIP file: {zip_file_path}")

                            if zip_file_path.exists():
                                # Check if INTRO.mp4 exists in the ZIP file without extracting
                                try:
                                    with zipfile.ZipFile(str(zip_file_path), 'r') as zip_ref:
                                        file_list = zip_ref.namelist()
                                        if "INTRO.mp4" in file_list:
                                            logger.info(f"Found INTRO.mp4 in ZIP file: {zip_file_path}")

                                            # Ensure the ZIP is extracted so we can access the file
                                            unzip_success = self._unzip_match_zip_file(match_id)
                                            if unzip_success:
                                                # Find the extracted INTRO.mp4 file
                                                import tempfile
                                                temp_base = Path(tempfile.gettempdir())
                                                temp_dir_pattern = f"match_{match_id}_"

                                                for temp_dir in temp_base.glob(f"{temp_dir_pattern}*"):
                                                    if temp_dir.is_dir():
                                                        intro_file = temp_dir / "INTRO.mp4"
                                                        if intro_file.exists():
                                                            logger.info(f"Using INTRO.mp4 from match ZIP: {intro_file}")
                                                            return intro_file
                                            else:
                                                logger.warning(f"Failed to extract ZIP file containing INTRO.mp4: {zip_file_path}")
                                        else:
                                            logger.debug(f"INTRO.mp4 not found in ZIP file: {zip_file_path}")
                                except zipfile.BadZipFile:
                                    logger.warning(f"Invalid ZIP file: {zip_file_path}")
                                except Exception as zip_error:
                                    logger.error(f"Error checking ZIP file contents: {zip_error}")
                            else:
                                logger.debug(f"ZIP file not found: {zip_file_path}")
                    finally:
                        session.close()

            # Priority 2: Check for uploaded intro video
            # This would need to be implemented based on how uploaded intro videos are stored
            # For now, we'll skip this and go to priority 3
            logger.debug("DEBUG: Skipping uploaded intro video check (not implemented)")

            # Priority 3: Fallback to INTRO.mp4 in assets directory
            assets_dir = Path(__file__).parent.parent.parent / "assets"
            assets_intro = assets_dir / "INTRO.mp4"
            logger.debug(f"DEBUG: Checking fallback INTRO.mp4 in assets: {assets_intro}")
            logger.debug(f"DEBUG: Assets directory exists: {assets_dir.exists()}")
            logger.debug(f"DEBUG: Assets directory contents: {list(assets_dir.iterdir()) if assets_dir.exists() else 'N/A'}")
            if assets_intro.exists():
                logger.info(f"Using fallback INTRO.mp4 from assets: {assets_intro}")
                return assets_intro

            logger.warning("No intro video found in any location")
            return None

        except Exception as e:
            logger.error(f"Failed to find intro video file: {e}")
            return None

    def _get_database_manager(self):
        """Get database manager from message bus"""
        try:
            logger.info("QtPlayer: DEBUG - Getting database manager")
            if hasattr(self, '_message_bus') and self._message_bus:
                logger.info("QtPlayer: DEBUG - Message bus available, trying to get db_manager from web_dashboard")
                # Try to get db_manager from web_dashboard component
                try:
                    web_dashboard_queue = self._message_bus._queues.get('web_dashboard')
                    logger.info(f"QtPlayer: DEBUG - Web dashboard queue: {web_dashboard_queue}")
                    if web_dashboard_queue and hasattr(web_dashboard_queue, 'component'):
                        component = web_dashboard_queue.component
                        logger.info(f"QtPlayer: DEBUG - Web dashboard component: {component}")
                        if hasattr(component, 'db_manager'):
                            logger.info("QtPlayer: DEBUG - Got db_manager from web_dashboard component")
                            return component.db_manager
                        else:
                            logger.info("QtPlayer: DEBUG - Web dashboard component has no db_manager attribute")
                    else:
                        logger.info("QtPlayer: DEBUG - No web dashboard queue or component")
                except Exception as e:
                    logger.error(f"QtPlayer: DEBUG - Could not get db_manager from message bus: {e}")

            logger.info("QtPlayer: DEBUG - Falling back to create database manager directly")
            # Fallback: create database manager directly
            from ..config.settings import get_user_data_dir
            from ..database.manager import DatabaseManager
            db_path = get_user_data_dir() / "mbetterclient.db"
            logger.info(f"QtPlayer: DEBUG - Creating database manager directly: {db_path}")
            db_manager = DatabaseManager(str(db_path))
            logger.info("QtPlayer: DEBUG - Database manager created, initializing")
            if db_manager.initialize():
                logger.info("QtPlayer: DEBUG - Database manager initialized successfully")
                return db_manager
            else:
                logger.warning("QtPlayer: DEBUG - Failed to initialize database manager")
                return None

        except Exception as e:
            logger.error(f"QtPlayer: DEBUG - Failed to get database manager: {e}")
            import traceback
            logger.error(f"QtPlayer: DEBUG - Full traceback: {traceback.format_exc()}")
            return None

    def _unzip_match_zip_file(self, match_id: int) -> bool:
        """Unzip the ZIP file associated with a match to a temporary directory

        Returns:
            bool: True if extraction was successful, False if it failed
        """
        try:
            import zipfile
            import tempfile
            import os
            from pathlib import Path

            logger.info(f"DEBUG: Starting ZIP extraction for match {match_id}")

            # Get database manager from message bus
            db_manager = self._get_database_manager()
            if not db_manager:
                logger.error("DEBUG: No database manager available for ZIP extraction")
                return False

            session = db_manager.get_session()
            try:
                # Get the match from database
                from ..database.models import MatchModel
                match = session.query(MatchModel).filter_by(id=match_id).first()

                if not match:
                    logger.warning(f"DEBUG: Match {match_id} not found in database, skipping ZIP extraction")
                    return False

                logger.info(f"DEBUG: Found match {match_id}, zip_filename: {match.zip_filename}")

                if not match.zip_filename:
                    logger.info(f"DEBUG: Match {match_id} has no associated ZIP file, skipping extraction")
                    return False

                # Determine ZIP file location (ZIP files are stored in the zip_files directory)
                from ..config.settings import get_user_data_dir
                user_data_dir = get_user_data_dir()
                zip_file_path = user_data_dir / "zip_files" / match.zip_filename

                logger.info(f"DEBUG: Looking for ZIP file at: {zip_file_path}")
                logger.info(f"DEBUG: ZIP file exists: {zip_file_path.exists()}")

                if not zip_file_path.exists():
                    logger.warning(f"DEBUG: ZIP file not found: {zip_file_path}")
                    # Mark match as failed
                    match.status = 'failed'
                    session.commit()
                    logger.info(f"DEBUG: Marked match {match_id} as failed due to missing ZIP file")
                    return False

                logger.info(f"DEBUG: ZIP file size: {zip_file_path.stat().st_size} bytes")

                # Create temporary directory for extraction
                temp_dir = Path(tempfile.mkdtemp(prefix=f"match_{match_id}_"))
                logger.info(f"DEBUG: Created temp directory: {temp_dir}")

                # Extract the ZIP file with error handling
                logger.info(f"DEBUG: Starting ZIP extraction...")
                try:
                    with zipfile.ZipFile(str(zip_file_path), 'r') as zip_ref:
                        file_list = zip_ref.namelist()
                        logger.info(f"DEBUG: ZIP contains {len(file_list)} files: {file_list}")
                        zip_ref.extractall(str(temp_dir))
                except zipfile.BadZipFile as e:
                    logger.error(f"DEBUG: Invalid or corrupted ZIP file for match {match_id}: {e}")
                    # Mark match as failed
                    match.status = 'failed'
                    session.commit()
                    logger.info(f"DEBUG: Marked match {match_id} as failed due to invalid ZIP file")
                    return False
                except Exception as e:
                    logger.error(f"DEBUG: Error during ZIP extraction for match {match_id}: {e}")
                    # Mark match as failed
                    match.status = 'failed'
                    session.commit()
                    logger.info(f"DEBUG: Marked match {match_id} as failed due to ZIP extraction error")
                    return False

                # Log extraction results
                extracted_files = list(temp_dir.rglob("*"))
                logger.info(f"DEBUG: Successfully extracted {len(extracted_files)} files from {match.zip_filename}")
                for extracted_file in extracted_files:
                    if extracted_file.is_file():
                        logger.info(f"DEBUG: Extracted file: {extracted_file} (size: {extracted_file.stat().st_size} bytes)")

                # Store the temporary directory path for potential cleanup
                # In a real implementation, you might want to track this for cleanup
                match.temp_extract_path = str(temp_dir)

                # Update match in database with temp path (optional)
                session.commit()

                logger.info(f"DEBUG: ZIP extraction completed for match {match_id}")
                return True

            finally:
                session.close()

        except Exception as e:
            logger.error(f"DEBUG: Failed to unzip ZIP file for match {match_id}: {e}")
            import traceback
            logger.error(f"DEBUG: Full traceback: {traceback.format_exc()}")
            return False

    def _find_match_video_file(self, match_id: int, video_filename: str) -> Optional[Path]:
        """Find the match video file from the unzipped ZIP"""
        try:
            import tempfile
            from pathlib import Path

            # Look for temp directories created by _unzip_match_zip_file
            temp_base = Path(tempfile.gettempdir())
            temp_dir_pattern = f"match_{match_id}_"
            logger.info(f"DEBUG: Looking for match video '{video_filename}' for match {match_id}")
            logger.info(f"DEBUG: Searching in temp directory: {temp_base}")
            logger.info(f"DEBUG: Looking for directories with pattern '{temp_dir_pattern}'")

            matching_dirs = list(temp_base.glob(f"{temp_dir_pattern}*"))
            logger.info(f"DEBUG: Found {len(matching_dirs)} directories matching pattern '{temp_dir_pattern}*'")

            for temp_dir in matching_dirs:
                if temp_dir.is_dir():
                    logger.info(f"DEBUG: Checking directory: {temp_dir}")
                    contents = list(temp_dir.iterdir())
                    logger.info(f"DEBUG: Directory contents: {[str(f) for f in contents]}")

                    video_file = temp_dir / video_filename
                    logger.info(f"DEBUG: Looking for video file: {video_file}")
                    if video_file.exists():
                        logger.info(f"DEBUG: FOUND match video: {video_file} (size: {video_file.stat().st_size} bytes)")
                        return video_file
                    else:
                        logger.warning(f"DEBUG: Video file {video_filename} not found in {temp_dir}")

            logger.error(f"Match video not found: {video_filename} for match {match_id}")
            logger.error(f"DEBUG: Searched in {len(matching_dirs)} directories:")
            for temp_dir in matching_dirs:
                logger.error(f"  - {temp_dir} (exists: {temp_dir.exists()})")
                if temp_dir.exists():
                    contents = list(temp_dir.iterdir()) if temp_dir.is_dir() else []
                    logger.error(f"    Contents: {[str(f) for f in contents]}")

            # Also check if there are any match_* directories at all
            all_match_dirs = list(temp_base.glob("match_*"))
            logger.info(f"DEBUG: Total match directories in temp: {len(all_match_dirs)}")
            for d in all_match_dirs[:5]:  # Show first 5
                logger.info(f"  - {d}")

            return None

        except Exception as e:
            logger.error(f"Failed to find match video file: {e}")
            import traceback
            logger.error(f"DEBUG: Full traceback: {traceback.format_exc()}")
            return None

    def _start_template_rotation(self):
        """Start the template rotation timer"""
        try:
            if not self.template_sequence or len(self.template_sequence) <= 1:
                logger.debug("Template rotation not needed - insufficient templates")
                return

            # Parse rotating time (format: MM:SS)
            try:
                minutes, seconds = map(int, self.rotating_time.split(':'))
                rotation_interval_ms = (minutes * 60 + seconds) * 1000  # Convert to milliseconds
            except (ValueError, AttributeError):
                logger.warning(f"Invalid rotating_time format '{self.rotating_time}', using default 15 seconds")
                rotation_interval_ms = 15 * 1000  # Default 15 seconds

            logger.info(f"Starting template rotation every {rotation_interval_ms}ms ({self.rotating_time})")

            # Stop any existing timer
            if self.template_rotation_timer and self.template_rotation_timer.isActive():
                self.template_rotation_timer.stop()

            # Start the rotation timer
            self.template_rotation_timer.start(rotation_interval_ms)
            self.current_template_index = 0  # Reset to first template

        except Exception as e:
            logger.error(f"Failed to start template rotation: {e}")

    def _rotate_template(self):
        """Rotate to the next template in the sequence"""
        try:
            if not self.template_sequence or len(self.template_sequence) == 0:
                logger.debug("No template sequence available for rotation")
                return

            # Move to next template
            self.current_template_index = (self.current_template_index + 1) % len(self.template_sequence)
            next_template = self.template_sequence[self.current_template_index]['name']

            logger.info(f"Rotating to template: {next_template} (index {self.current_template_index})")

            # Load the new template
            if hasattr(self, 'window_overlay') and isinstance(self.window_overlay, OverlayWebView):
                self.window_overlay.load_template(next_template)
                logger.info(f"Template rotated to: {next_template}")
            else:
                logger.warning("No WebEngine overlay available for template rotation")

        except Exception as e:
            logger.error(f"Failed to rotate template: {e}")

    def _handle_play_video_result(self, message: Message):
        """Handle PLAY_VIDEO_RESULTS message - play result video with results overlay"""
        try:
            fixture_id = message.data.get("fixture_id")
            match_id = message.data.get("match_id")
            result = message.data.get("result")
            under_over_result = message.data.get("under_over_result")
            winning_outcomes = message.data.get("winning_outcomes", [])

            logger.info(f"Handling PLAY_VIDEO_RESULTS: fixture={fixture_id}, match={match_id}, result={result}, under_over={under_over_result}, winning_outcomes={winning_outcomes}")

            # Find the result video file
            result_video_path = self._find_result_video_file(match_id, result)

            if not result_video_path:
                logger.error(f"Result video not found for match {match_id}, result {result}")
                return

            # Calculate video duration
            video_duration = self._get_video_duration(str(result_video_path))
            logger.info(f"Result video duration: {video_duration} seconds")

            # Create result video info
            result_video_info = {
                'path': str(result_video_path),
                'fixture_id': fixture_id,
                'match_id': match_id,
                'result': result,
                'under_over_result': under_over_result,
                'winning_outcomes': winning_outcomes,
                'duration': video_duration,
                'is_result_video': True
            }

            # Check if currently playing match video
            currently_playing_match = (hasattr(self.window, 'current_match_id') and
                                      self.window.current_match_id is not None and
                                      hasattr(self.window, 'current_match_video_filename') and
                                      self.window.current_match_video_filename is not None)

            if currently_playing_match:
                # Stop the current match video and play result video immediately
                logger.info("Match video currently playing, stopping it and playing result video")
                if self.window and hasattr(self.window, 'media_player'):
                    self.window.stop_playback()
                self._play_result_video(result_video_info)
            else:
                # Play result video immediately
                logger.info("No match video playing, playing result video immediately")
                self._play_result_video(result_video_info)

        except Exception as e:
            logger.error(f"Failed to handle PLAY_VIDEO_RESULTS: {e}")

    def _find_result_video_file(self, match_id: int, result: str) -> Optional[Path]:
        """Find the result video file ({RESULT}.mp4) from the match ZIP"""
        try:
            import tempfile
            from pathlib import Path

            # Look for temp directories created by _unzip_match_zip_file
            temp_base = Path(tempfile.gettempdir())
            temp_dir_pattern = f"match_{match_id}_"

            for temp_dir in temp_base.glob(f"{temp_dir_pattern}*"):
                if temp_dir.is_dir():
                    result_video_file = temp_dir / f"{result}.mp4"
                    if result_video_file.exists():
                        logger.info(f"Found result video: {result_video_file}")
                        return result_video_file

            logger.warning(f"Result video {result}.mp4 not found for match {match_id}")
            return None

        except Exception as e:
            logger.error(f"Failed to find result video file: {e}")
            return None

    def _get_video_duration(self, video_path: str) -> float:
        """Get video duration in seconds"""
        try:
            # For now, return a default duration. In a real implementation,
            # you'd use a video library to get the actual duration
            # For example, using moviepy or opencv
            logger.info(f"Getting duration for video: {video_path}")

            # Placeholder - return 10 seconds as default
            # TODO: Implement actual video duration detection
            return 10.0

        except Exception as e:
            logger.error(f"Failed to get video duration: {e}")
            return 10.0  # Default fallback

    def _queue_result_video(self, result_video_info: Dict[str, Any]):
        """Queue result video to play after current match video"""
        try:
            # Store the result video info to be played when match video finishes
            self.queued_result_video = result_video_info
            logger.info(f"Queued result video for match {result_video_info['match_id']}, result {result_video_info['result']}")

        except Exception as e:
            logger.error(f"Failed to queue result video: {e}")

    def _play_result_video(self, result_video_info: Dict[str, Any]):
        """Play result video with results overlay template"""
        try:
            fixture_id = result_video_info['fixture_id']
            match_id = result_video_info['match_id']
            result = result_video_info['result']
            under_over_result = result_video_info.get('under_over_result')
            winning_outcomes = result_video_info.get('winning_outcomes', [])
            video_path = result_video_info['path']
            duration = result_video_info['duration']

            logger.info(f"Playing result video: {result}.mp4 for match {match_id}")

            # Get match details from database
            match_details = self._get_match_details_for_results(match_id)
            if not match_details:
                logger.error(f"Could not get match details for match {match_id}")
                return

            # Use the under_over_result from the message data (already determined by games_thread)
            main_result = result
            if result in ['UNDER', 'OVER']:
                main_result = None  # No separate main result

            # Prepare winning outcomes list in the format expected by the template
            winning_outcomes_list = [{'outcome': outcome} for outcome in winning_outcomes]

            # Add under_over_result to winning outcomes if it's not already included
            if under_over_result and under_over_result not in [o['outcome'] for o in winning_outcomes_list]:
                winning_outcomes_list.append({'outcome': under_over_result})
                logger.info(f"Added under_over_result '{under_over_result}' to winning outcomes")

            # Prepare overlay data for results template
            overlay_data = {
                'outcome': main_result,
                'result': main_result,  # For backwards compatibility
                'under_over_result': under_over_result,
                'winningOutcomes': winning_outcomes_list,  # Include winning outcomes without amounts
                'match': {
                    'fighter1_township': match_details.get('fighter1_township', 'Fighter 1'),
                    'fighter2_township': match_details.get('fighter2_township', 'Fighter 2'),
                    'venue': match_details.get('venue', 'Venue')
                },
                'match_id': match_id,
                'fixture_id': fixture_id,
                'is_result_video': True
            }

            logger.info(f"Sending results data to overlay: outcome={main_result}, under_over={under_over_result}, winning_outcomes_count={len(winning_outcomes_list)}")

            # Play the result video with results overlay template
            self.window.play_video(
                video_path,
                template_data=overlay_data,
                template_name="results.html",  # Use results template
                loop_data=None
            )

            # Store result video info for end-of-video handling
            logger.info(f"RESULT DEBUG: Setting current_result_video_info = {result_video_info}")
            self.current_result_video_info = result_video_info
            result_filename = Path(video_path).name
            logger.info(f"RESULT DEBUG: Setting current_result_video_filename = '{result_filename}'")
            self.current_result_video_filename = result_filename

            logger.info(f"RESULT DEBUG: Result video tracking set - info: {self.current_result_video_info}, filename: '{self.current_result_video_filename}'")
            logger.info(f"Result video started with results overlay template for result: {result}")

        except Exception as e:
            logger.error(f"Failed to play result video: {e}")

    def _switch_to_result_overlay(self):
        """Switch overlay to result template 3 seconds before video ends"""
        try:
            if not hasattr(self, 'current_result_video_info') or not self.current_result_video_info:
                logger.warning("No current result video info for overlay switch")
                return

            result_info = self.current_result_video_info
            result = result_info['result']

            logger.info(f"Switching overlay to result template for result: {result}")

            # Get result template from configuration
            result_template = self._get_result_template(result)

            # Update overlay with result template
            result_overlay_data = {
                'result': result,
                'is_final_result': True
            }

            # Update the overlay
            if hasattr(self.window, 'overlay') and self.window.overlay:
                # This would need to be implemented based on how overlay updates work
                logger.info(f"Overlay switched to result template: {result_template}")

        except Exception as e:
            logger.error(f"Failed to switch result overlay: {e}")

    def _get_result_template(self, result: str) -> str:
        """Get configured result template for the given result"""
        try:
            # TODO: Get from template configuration
            # For now, return default result template
            return "results.html"
        except Exception as e:
            logger.error(f"Failed to get result template: {e}")
            return "results.html"

    def _send_match_video_done_message(self, match_id=None, video_filename=None, fixture_id=None):
        """Send PLAY_VIDEO_MATCH_DONE message when match video finishes"""
        try:
            # Use provided parameters or fall back to instance attributes
            current_match_id = match_id if match_id is not None else getattr(self, 'current_match_id', None)
            current_video_filename = video_filename if video_filename is not None else getattr(self, 'current_match_video_filename', None)
            current_fixture_id = fixture_id if fixture_id is not None else getattr(self, 'current_fixture_id', None)

            if (current_match_id is not None and current_video_filename is not None):

                from ..core.message_bus import MessageBuilder

                done_message = MessageBuilder.play_video_match_done(
                    sender=self.name,
                    match_id=current_match_id,
                    video_filename=current_video_filename,
                    fixture_id=current_fixture_id
                )

                self.message_bus.publish(done_message, broadcast=True)
                logger.info(f"Sent PLAY_VIDEO_MATCH_DONE for match {current_match_id}, video {current_video_filename}")

                # Check if there's a queued result video to play
                if hasattr(self, 'queued_result_video') and self.queued_result_video:
                    logger.info("Playing queued result video after match video completion")
                    queued_video = self.queued_result_video
                    self.queued_result_video = None  # Clear the queue
                    self._play_result_video(queued_video)

        except Exception as e:
            logger.error(f"Failed to send match video done message: {e}")

    def _send_result_video_done_after_delay(self):
        """Send PLAY_VIDEO_RESULT_DONE message after the 5-second delay"""
        try:
            logger.info("RESULT DEBUG: _send_result_video_done_after_delay called - 5 second delay has elapsed")

            # Get the current result video info
            current_result_info = getattr(self, 'current_result_video_info', None)
            logger.info(f"RESULT DEBUG: current_result_info = {current_result_info}")

            if current_result_info:
                logger.info("RESULT DEBUG: current_result_info is not None, proceeding to send message")

                from ..core.message_bus import MessageBuilder

                fixture_id = current_result_info.get('fixture_id')
                match_id = current_result_info.get('match_id')
                result = current_result_info.get('result')

                logger.info(f"RESULT DEBUG: Extracted values - fixture_id={fixture_id}, match_id={match_id}, result={result}")

                done_message = MessageBuilder.play_video_result_done(
                    sender=self.name,
                    fixture_id=fixture_id,
                    match_id=match_id,
                    result=result
                )

                logger.info(f"RESULT DEBUG: Created PLAY_VIDEO_RESULT_DONE message: {done_message}")
                logger.info(f"RESULT DEBUG: Message data: {done_message.data}")

                publish_result = self.message_bus.publish(done_message, broadcast=True)
                logger.info(f"RESULT DEBUG: Message publish result: {publish_result}")
                logger.info(f"Sent PLAY_VIDEO_RESULT_DONE (after 5s delay) for fixture {fixture_id}, match {match_id}, result {result}")

                # Reset result video tracking after sending the message
                self.current_result_video_info = None
                self.current_result_video_filename = None
                logger.debug("RESULT DEBUG: Reset result video tracking after sending PLAY_VIDEO_RESULT_DONE")

            else:
                logger.warning("RESULT DEBUG: current_result_info is None - cannot send PLAY_VIDEO_RESULT_DONE message")

        except Exception as e:
            logger.error(f"RESULT DEBUG: Failed to send result video done message after delay: {e}")
            import traceback
            logger.error(f"RESULT DEBUG: Full traceback: {traceback.format_exc()}")

    def _get_result_video_filenames(self) -> List[str]:
        """Dynamically get result video filenames from database, excluding OVER/UNDER"""
        try:
            # Get database manager from the QtVideoPlayer component
            db_manager = None
            if hasattr(self, 'qt_player') and self.qt_player:
                # Try to get db_manager from qt_player's message bus
                if hasattr(self.qt_player, 'message_bus') and self.qt_player.message_bus:
                    # Try to get db_manager from web_dashboard component
                    try:
                        web_dashboard_queue = self.qt_player.message_bus._queues.get('web_dashboard')
                        if web_dashboard_queue and hasattr(web_dashboard_queue, 'component'):
                            component = web_dashboard_queue.component
                            if hasattr(component, 'db_manager'):
                                db_manager = component.db_manager
                                logger.debug("PlayerWindow: Got db_manager from web_dashboard component")
                    except Exception as e:
                        logger.debug(f"PlayerWindow: Could not get db_manager from message bus: {e}")

            if not db_manager:
                # Fallback: create database manager directly
                from ..config.settings import get_user_data_dir
                from ..database.manager import DatabaseManager
                db_path = get_user_data_dir() / "mbetterclient.db"
                db_manager = DatabaseManager(str(db_path))
                if not db_manager.initialize():
                    logger.warning("PlayerWindow: Failed to initialize database manager")
                    # Return default result videos if database is not available (excluding OVER/UNDER)
                    return ["WIN1.mp4", "WIN2.mp4", "KO1.mp4", "KO2.mp4", "DRAW.mp4", "RET1.mp4", "RET2.mp4"]

            session = db_manager.get_session()
            try:
                # Query active result options from database, excluding OVER and UNDER
                from ..database.models import ResultOptionModel
                active_results = session.query(ResultOptionModel).filter(
                    ResultOptionModel.is_active == True,
                    ResultOptionModel.result_name.notin_(['OVER', 'UNDER'])
                ).all()

                # Convert result names to video filenames
                result_videos = [f"{result.result_name}.mp4" for result in active_results]

                logger.debug(f"PlayerWindow: Found {len(active_results)} active result options (excluding OVER/UNDER): {[r.result_name for r in active_results]}")
                logger.debug(f"PlayerWindow: Generated result video filenames: {result_videos}")

                return result_videos

            finally:
                session.close()

        except Exception as e:
            logger.error(f"PlayerWindow: Failed to get result video filenames from database: {e}")
            # Return default result videos as fallback (excluding OVER/UNDER)
            return ["WIN1.mp4", "WIN2.mp4", "KO1.mp4", "KO2.mp4", "DRAW.mp4", "RET1.mp4", "RET2.mp4"]

    def _send_result_video_done_message(self, result_info=None):
        """Send PLAY_VIDEO_RESULT_DONE message when result video finishes"""
        try:
            # Use provided result_info or fall back to instance attribute
            current_result_info = result_info if result_info is not None else getattr(self, 'current_result_video_info', None)

            if current_result_info:
                from ..core.message_bus import MessageBuilder

                done_message = MessageBuilder.play_video_result_done(
                    sender=self.name,
                    fixture_id=current_result_info.get('fixture_id'),
                    match_id=current_result_info.get('match_id'),
                    result=current_result_info.get('result')
                )

                self.message_bus.publish(done_message, broadcast=True)
                logger.info(f"Sent PLAY_VIDEO_RESULT_DONE for fixture {current_result_info.get('fixture_id')}, match {current_result_info.get('match_id')}, result {current_result_info.get('result')}")

        except Exception as e:
            logger.error(f"Failed to send result video done message: {e}")

    
    def _do_status_request(self, message: Message):
        """Execute status request on main thread"""
        try:
            is_playing = (self.window.media_player.playbackState() ==
                        QMediaPlayer.PlaybackState.PlayingState)
            position = self.window.media_player.position() / 1000.0  # seconds
            duration = self.window.media_player.duration() / 1000.0  # seconds
            volume = self.window.audio_output.volume() * 100
            muted = self.window.audio_output.isMuted()
            fullscreen = self.window.isFullScreen()

            status_response = MessageBuilder.system_status(
                sender=self.name,
                status="status_response",
                details={
                    "player_status": "playing" if is_playing else "paused",
                    "position": position,
                    "duration": duration,
                    "volume": volume,
                    "muted": muted,
                    "fullscreen": fullscreen
                }
            )
            # Send response back to requester
            status_response.recipient = message.sender
            self.message_bus.publish(status_response)
        except Exception as e:
            logger.error(f"Failed to execute status request: {e}")

    def _request_initial_game_status(self):
        """Request initial game status to determine if we should play intro"""
        try:
            logger.info("QtPlayer: ===== REQUESTING INITIAL GAME STATUS =====")
            logger.debug("QtPlayer: About to publish status_request message")
            # Send GAME_STATUS message instead of SYSTEM_STATUS to match games_thread subscription
            status_request = MessageBuilder.system_status(
                sender=self.name,
                status="status_request",
                details={"request_type": "game_status"}
            )
            logger.info(f"QtPlayer: Created status_request message: {status_request}")
            logger.info(f"QtPlayer: Message sender: {status_request.sender}")
            logger.info(f"QtPlayer: Message type: {status_request.type}")
            logger.info(f"QtPlayer: Message data: {status_request.data}")
            self.message_bus.publish(status_request, broadcast=True)
            logger.info("QtPlayer: ===== SUCCESSFULLY PUBLISHED STATUS_REQUEST MESSAGE =====")
        except Exception as e:
            logger.error(f"QtPlayer: Failed to request initial game status: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
            # If message creation fails, try to play intro anyway
            logger.info("QtPlayer: Message creation failed, trying to play intro directly")
            self._check_and_play_intro()

    def _handle_game_status(self, message: Message):
        """Handle GAME_STATUS messages to determine when to play intro"""
        try:
            status = message.data.get("status")
            game_active = message.data.get("game_active", False)
            logger.info(f"QtPlayer: ===== RECEIVED GAME_STATUS: {status} =====")
            logger.info(f"QtPlayer: Message sender: {message.sender}")
            logger.info(f"QtPlayer: Message recipient: {message.recipient}")
            logger.info(f"QtPlayer: Full message data: {message.data}")

            # Check if this indicates no active game
            if status in ["ready", "shutdown", "completed_no_old_matches"]:
                logger.info("QtPlayer: No active game detected, checking if we should play intro")
                logger.debug("QtPlayer: Calling _check_and_play_intro()")
                self._check_and_play_intro()
            elif status == "started":
                logger.info("QtPlayer: Game is active, intro will be handled by START_INTRO message")
            elif status == "already_active":
                # Check if game is actually active or just matches exist
                if game_active:
                    logger.info("QtPlayer: Game already active, no intro needed")
                else:
                    logger.info("QtPlayer: Matches exist but no active game, checking if we should play intro")
                    logger.debug("QtPlayer: Calling _check_and_play_intro() for already_active but inactive game")
                    self._check_and_play_intro()
            else:
                logger.debug(f"QtPlayer: Unhandled game status: {status}")

        except Exception as e:
            logger.error(f"QtPlayer: Failed to handle game status: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
            # If handling fails, try to play intro anyway
            logger.info("QtPlayer: Game status handling failed, trying to play intro directly")
            self._check_and_play_intro()

    def _handle_system_status(self, message: Message):
        """Handle SYSTEM_STATUS messages that might contain game status info"""
        try:
            status = message.data.get("status")
            logger.info(f"QtPlayer: ===== RECEIVED SYSTEM_STATUS: {status} =====")
            logger.info(f"QtPlayer: Message sender: {message.sender}")
            logger.info(f"QtPlayer: Message data: {message.data}")

            # If this is a game status response, handle it like GAME_STATUS
            if status in ["ready", "shutdown", "completed_no_old_matches", "started", "already_active"]:
                logger.info("QtPlayer: SYSTEM_STATUS contains game status, treating as GAME_STATUS")
                self._handle_game_status(message)

        except Exception as e:
            logger.error(f"QtPlayer: Failed to handle system status: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
            # If handling fails, try to play intro anyway
            logger.info("QtPlayer: System status handling failed, trying to play intro directly")
            self._check_and_play_intro()

    def _handle_custom_message(self, message: Message):
        """Handle custom messages, including timer updates for WebChannel"""
        try:
            # Forward timer update messages to the OverlayWebChannel
            if message.data.get("timer_update"):
                logger.debug(f"QtPlayer: Forwarding timer update to OverlayWebChannel")
                if hasattr(self, 'window') and self.window and hasattr(self.window, 'window_overlay'):
                    overlay_view = self.window.window_overlay
                    if isinstance(overlay_view, OverlayWebView) and hasattr(overlay_view, 'overlay_channel'):
                        overlay_view.overlay_channel._handle_timer_update(message)
                        logger.debug("QtPlayer: Timer update forwarded to OverlayWebChannel")
                    else:
                        logger.debug("QtPlayer: No OverlayWebView or overlay_channel available for timer update")
                else:
                    logger.debug("QtPlayer: No window or window_overlay available for timer update")
            else:
                logger.debug(f"QtPlayer: Received custom message without timer_update: {message.data}")

        except Exception as e:
            logger.error(f"QtPlayer: Failed to handle custom message: {e}")

    def _handle_web_dashboard_ready(self, message: Message):
        """Handle web dashboard ready messages to update server URL"""
        try:
            sender = message.sender
            status = message.data.get("status")
            details = message.data.get("details", {})

            # Check if this is a web_dashboard ready message
            if sender == "web_dashboard" and status == "ready":
                host = details.get("host")
                port = details.get("port")
                ssl_enabled = details.get("ssl_enabled", False)

                if host and port:
                    protocol = "https" if ssl_enabled else "http"
                    web_server_url = f"{protocol}://{host}:{port}"
                    logger.info(f"QtPlayer: Updated web server URL from web_dashboard ready message: {web_server_url}")

                    # Update the web dashboard URL for API calls
                    self.web_dashboard_url = web_server_url

                    # Update any existing overlay with the correct URL
                    if hasattr(self, 'window') and self.window and hasattr(self.window, 'window_overlay'):
                        overlay_view = self.window.window_overlay
                        if isinstance(overlay_view, OverlayWebView):
                            logger.info(f"QtPlayer: Sending updated webServerBaseUrl to overlay: {web_server_url}")
                            self.window._update_overlay_safe(overlay_view, {'webServerBaseUrl': web_server_url})

        except Exception as e:
            logger.error(f"QtPlayer: Failed to handle web dashboard ready message: {e}")

    def _check_and_play_intro(self):
        """Check if we should play the intro video when no game is active"""
        try:
            logger.info("QtPlayer: ===== CHECKING IF INTRO SHOULD PLAY =====")
            # Only play intro if we're not already playing something
            playback_state = self.window.media_player.playbackState()
            currently_playing = (playback_state == QMediaPlayer.PlaybackState.PlayingState)
            logger.info(f"QtPlayer: Checking if intro should play - currently playing: {currently_playing}")
            logger.info(f"QtPlayer: Media player state: {playback_state}")
            logger.info(f"QtPlayer: Media player state name: {playback_state.name if hasattr(playback_state, 'name') else 'unknown'}")
            logger.info(f"QtPlayer: Media player source: {self.window.media_player.source()}")
            logger.info(f"QtPlayer: Media player duration: {self.window.media_player.duration()}")
            logger.info(f"QtPlayer: Media player position: {self.window.media_player.position()}")

            if self.window and playback_state == QMediaPlayer.PlaybackState.StoppedState:
                logger.info("QtPlayer: Player is stopped, calling _play_waiting_intro()")
                logger.debug("QtPlayer: About to call _play_waiting_intro()")
                self._play_waiting_intro()
            else:
                logger.info("QtPlayer: Player is NOT stopped, skipping intro")
                logger.info(f"QtPlayer: Skipping intro due to playback state: {playback_state} ({playback_state.name if hasattr(playback_state, 'name') else 'unknown'})")
        except Exception as e:
            logger.error(f"QtPlayer: Failed to check and play intro: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")

    def _play_waiting_intro(self):
        """Play the INTRO video on repeat with waiting overlay when no game is active"""
        try:
            logger.info("QtPlayer: ===== STARTING _play_waiting_intro() =====")
            logger.debug("QtPlayer: About to find intro video file")

            # Find the INTRO video file
            intro_path = self._find_intro_video_file_for_waiting()
            logger.info(f"QtPlayer: Intro path result: {intro_path}")
            logger.info(f"QtPlayer: Intro path exists: {intro_path.exists() if intro_path else 'N/A'}")
            logger.info(f"QtPlayer: Intro path is file: {intro_path.is_file() if intro_path else 'N/A'}")

            if intro_path:
                logger.info(f"QtPlayer: ===== PLAYING INTRO VIDEO: {intro_path} =====")
                logger.debug("QtPlayer: Setting up loop data for infinite repeat")
                # Set up loop control for infinite repeat
                loop_data = {
                    'infinite_loop': True,
                    'continuous_playback': True
                }

                # Create overlay data for waiting state
                overlay_data = {
                    "title": "Waiting for game to start...",
                    "message": "Waiting for game to start...",
                    "icon": "⏳"
                }
                logger.info(f"QtPlayer: Overlay data prepared: {overlay_data}")

                # Play the intro video with loop and overlay
                if self.window:
                    logger.info("QtPlayer: Window available, calling play_video")
                    logger.info(f"QtPlayer: About to call window.play_video with file: {str(intro_path)}")
                    self.window.play_video(
                        str(intro_path),
                        template_data=overlay_data,
                        template_name="default.html",  # Use default template
                        loop_data=loop_data
                    )
                    logger.info("QtPlayer: ===== WAITING INTRO VIDEO STARTED SUCCESSFULLY =====")
                    logger.debug("QtPlayer: play_video call completed successfully")
                else:
                    logger.error("QtPlayer: No window available for waiting intro playback")
            else:
                logger.warning("QtPlayer: No INTRO video found for waiting state")
                logger.warning("QtPlayer: Intro path was None, cannot play waiting intro")

        except Exception as e:
            logger.error(f"QtPlayer: Failed to play waiting intro: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")

    def _find_intro_video_file_for_waiting(self) -> Optional[Path]:
        """Find the INTRO video file for waiting state"""
        try:
            logger.info("QtPlayer: ===== SEARCHING FOR INTRO VIDEO FILE =====")

            # Check if we're running from a PyInstaller bundle
            import sys
            if getattr(sys, 'frozen', False):
                # Running in a PyInstaller bundle
                logger.info("QtPlayer: Running from PyInstaller bundle")
                bundle_dir = Path(sys._MEIPASS)
                assets_intro = bundle_dir / "assets" / "INTRO.mp4"
                logger.info(f"QtPlayer: Bundle directory: {bundle_dir}")
                logger.info(f"QtPlayer: Looking for INTRO.mp4 at: {assets_intro}")
            else:
                # Running from source
                logger.info("QtPlayer: Running from source")
                assets_intro = Path(__file__).parent.parent / "assets" / "INTRO.mp4"

            logger.info(f"QtPlayer: INTRO.mp4 path: {assets_intro}")
            logger.info(f"QtPlayer: INTRO.mp4 exists: {assets_intro.exists()}")

            if assets_intro.exists():
                logger.info(f"QtPlayer: ===== FOUND INTRO.mp4: {assets_intro} =====")
                logger.info(f"QtPlayer: File size: {assets_intro.stat().st_size} bytes")
                logger.info(f"QtPlayer: File permissions: {oct(assets_intro.stat().st_mode)}")
                logger.info(f"QtPlayer: File is readable: {assets_intro.stat().st_mode & 0o400 != 0}")
                return assets_intro
            else:
                logger.warning(f"QtPlayer: INTRO.mp4 not found at: {assets_intro}")

                # Debug: Check what files are available in the bundle directory
                if getattr(sys, 'frozen', False):
                    bundle_dir = Path(sys._MEIPASS)
                    logger.warning(f"QtPlayer: Bundle directory contents: {list(bundle_dir.iterdir()) if bundle_dir.exists() else 'bundle dir not found'}")
                    assets_dir = bundle_dir / "assets"
                    if assets_dir.exists():
                        logger.warning(f"QtPlayer: Assets directory contents: {list(assets_dir.iterdir())}")
                    else:
                        logger.warning("QtPlayer: Assets directory not found in bundle")
                else:
                    source_assets = Path(__file__).parent.parent / "assets"
                    logger.warning(f"QtPlayer: Source assets directory contents: {list(source_assets.iterdir()) if source_assets.exists() else 'source assets dir not found'}")

            logger.warning("QtPlayer: ===== NO INTRO VIDEO FOUND =====")
            return None

        except Exception as e:
            logger.error(f"QtPlayer: Failed to find intro video for waiting: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
            return None

    def _find_intro_video_file_for_waiting_safe(self) -> Optional[Path]:
        """Safe version of intro video file finder that handles PyInstaller bundle issues"""
        try:
            logger.info("QtPlayer: ===== SAFE SEARCH FOR INTRO VIDEO FILE =====")

            # Check if we're running from a PyInstaller bundle
            import sys
            if getattr(sys, 'frozen', False):
                # Running in a PyInstaller bundle - use sys._MEIPASS
                logger.info("QtPlayer: Running from PyInstaller bundle")
                try:
                    bundle_dir = Path(sys._MEIPASS)
                    assets_intro = bundle_dir / "assets" / "INTRO.mp4"
                    logger.info(f"QtPlayer: Bundle directory: {bundle_dir}")
                    logger.info(f"QtPlayer: Looking for INTRO.mp4 at: {assets_intro}")

                    if assets_intro.exists():
                        logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 in bundle: {assets_intro} =====")
                        return assets_intro
                    else:
                        logger.warning(f"QtPlayer: INTRO.mp4 not found in bundle at: {assets_intro}")
                        # Try alternative locations in bundle
                        alt_locations = [
                            bundle_dir / "INTRO.mp4",
                            bundle_dir / "mbetterclient" / "assets" / "INTRO.mp4",
                            bundle_dir / "assets" / "INTRO.mp4"
                        ]
                        for alt_path in alt_locations:
                            if alt_path.exists():
                                logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 at alternative location: {alt_path} =====")
                                return alt_path
                            else:
                                logger.debug(f"QtPlayer: Not found at: {alt_path}")

                except Exception as bundle_error:
                    logger.error(f"QtPlayer: Error accessing PyInstaller bundle: {bundle_error}")
                    # Fallback to source directory
                    logger.info("QtPlayer: Falling back to source directory")
                    assets_intro = Path(__file__).parent.parent.parent / "assets" / "INTRO.mp4"
                    if assets_intro.exists():
                        logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 in source fallback: {assets_intro} =====")
                        return assets_intro
            else:
                # Running from source
                logger.info("QtPlayer: Running from source")
                assets_intro = Path(__file__).parent.parent.parent / "assets" / "INTRO.mp4"
                if assets_intro.exists():
                    logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 in source: {assets_intro} =====")
                    return assets_intro

            logger.warning("QtPlayer: ===== NO INTRO VIDEO FOUND IN ANY LOCATION =====")
            return None

        except Exception as e:
            logger.error(f"QtPlayer: Failed to find intro video safely: {e}")
            import traceback
            logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
            return None

    def _get_match_details_for_results(self, match_id: int) -> Optional[Dict[str, Any]]:
        """Get match details for results overlay"""
        try:
            from ..database.models import MatchModel

            logger.info(f"QtPlayer: DEBUG - Getting match details for match {match_id}")

            # First try to get results from game thread (for matches that aren't done yet)
            logger.info("QtPlayer: DEBUG - Trying to get match results from game thread")
            game_thread_results = self._get_match_results_from_game_thread(match_id)
            if game_thread_results:
                logger.info(f"QtPlayer: DEBUG - Got match results from game thread: {game_thread_results}")
                return game_thread_results

            logger.info("QtPlayer: DEBUG - No results from game thread, trying database")

            # Get database manager
            logger.info("QtPlayer: DEBUG - Getting database manager")
            db_manager = self._get_database_manager()
            if not db_manager:
                logger.error("QtPlayer: DEBUG - Database manager not available for match details, using defaults")
                return {
                    'fighter1_township': 'Fighter 1',
                    'fighter2_township': 'Fighter 2',
                    'venue': 'Venue'
                }

            logger.info("QtPlayer: DEBUG - Database manager available, getting session")
            session = db_manager.get_session()
            logger.info("QtPlayer: DEBUG - Database session obtained")

            try:
                logger.info(f"QtPlayer: DEBUG - Executing query for match {match_id}")
                # Get match details
                match = session.query(MatchModel).filter_by(id=match_id).first()
                logger.info(f"QtPlayer: DEBUG - Query executed, match object: {match}")

                if not match:
                    logger.warning(f"QtPlayer: DEBUG - Match {match_id} not found in database, using defaults")
                    return {
                        'fighter1_township': 'Fighter 1',
                        'fighter2_township': 'Fighter 2',
                        'venue': 'Venue'
                    }

                logger.info(f"QtPlayer: DEBUG - Match found, extracting details")
                match_details = {
                    'fighter1_township': match.fighter1_township or 'Fighter 1',
                    'fighter2_township': match.fighter2_township or 'Fighter 2',
                    'venue': match.venue_kampala_township or 'Venue'
                }

                logger.info(f"QtPlayer: DEBUG - Retrieved match details for match {match_id}: {match_details}")
                return match_details

            finally:
                logger.info("QtPlayer: DEBUG - Closing database session")
                session.close()

        except Exception as e:
            logger.error(f"QtPlayer: DEBUG - Failed to get match details for results: {e}, using defaults")
            import traceback
            logger.error(f"QtPlayer: DEBUG - Full traceback: {traceback.format_exc()}")
            return {
                'fighter1_township': 'Fighter 1',
                'fighter2_township': 'Fighter 2',
                'venue': 'Venue'
            }

    def _get_match_results_from_game_thread(self, match_id: int) -> Optional[Dict[str, Any]]:
        """Get match results from game thread temporary storage"""
        try:
            logger.info(f"QtPlayer: DEBUG - Requesting match results from game thread for match {match_id}")

            # Send message to game thread to get match results
            if hasattr(self, '_message_bus') and self._message_bus:
                from ..core.message_bus import Message, MessageType

                request_message = Message(
                    type=MessageType.CUSTOM,
                    sender="qt_player",
                    recipient="games_thread",
                    data={
                        "request": "get_match_results",
                        "match_id": match_id
                    }
                )

                logger.info("QtPlayer: DEBUG - Sending request to game thread")
                # For synchronous response, we can't easily wait, so this might not work
                # The game thread would need to respond asynchronously
                # For now, return None and rely on database
                logger.info("QtPlayer: DEBUG - Game thread request sent (async), returning None for now")
                return None

            else:
                logger.info("QtPlayer: DEBUG - No message bus available for game thread request")
                return None

        except Exception as e:
            logger.error(f"QtPlayer: DEBUG - Failed to get match results from game thread: {e}")
            return None
