"""
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([])

    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
                today = datetime.now().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"""
        try:
            from ..database.models import BetDetailModel, MatchModel
            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}")
                # Get aggregated winning amounts by outcome for this match
                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(winning_outcomes_query)} results")

                # Convert to dictionary format for JavaScript
                outcomes_data = []
                for outcome_name, total_amount in 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 for match {match_id}: {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.debug("OverlayWebView.setup_web_view() - Starting setup")

        # Detect Mesa software rendering
        import os
        is_mesa = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or \
                  os.environ.get('MESA_GL_VERSION_OVERRIDE') is not None

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

        if is_mesa:
            # Mesa-specific transparency settings
            logger.debug("Mesa software rendering detected - applying Mesa 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
            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.debug(f"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured")

        # Setup WebChannel
        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
        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)

        # 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
        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)

    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.debug(f"Loading overlay template: {template_name}")
            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()

            # 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'

            # First try uploaded templates directory (user uploads take priority)
            template_path = self.uploaded_templates_dir / template_name
            template_source = "uploaded"

            # 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"

            # 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"
                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():
                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()
                            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

                self.load(QUrl.fromLocalFile(str(template_path)))
                self.current_template = template_name

                # CRITICAL FIX: Force visibility recovery after template load
                if was_visible and not self.isVisible():
                    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"Loaded template: {template_path} (source: {template_source})")
            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}")

    # Removed _on_javaScript_console_message method as console capturing now uses WebChannel






class NativeOverlayWidget(QWidget):
    """Native Qt overlay widget - no WebEngine to prevent freezing"""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        # Initialize overlay_data BEFORE setup_ui() to prevent attribute errors
        self.overlay_data = {
            'title': 'MbetterClient PyQt6 Player',
            'subtitle': 'Ready for Content',
            'ticker': 'Welcome to MbetterClient • Professional Video Overlay System • Real-time Updates',
            'currentTime': '00:00:00'
        }
        # Add overlay_channel as None for compatibility with WebEngine interface
        self.overlay_channel = None
        self.setup_ui()
        logger.info("NativeOverlayWidget initialized")
    
    def setup_ui(self):
        """Setup native Qt overlay widgets with forced visibility"""
        logger.debug("NativeOverlayWidget.setup_ui() - Starting")
        
        # FORCE widget to be completely visible with bright background
        self.setStyleSheet("""
            NativeOverlayWidget {
                background-color: rgba(255, 0, 255, 200);
                border: 5px solid cyan;
            }
        """)
        
        # Ensure widget is visible
        self.setVisible(True)
        self.setAutoFillBackground(True)
        self.raise_()  # Bring to front
        
        # Main title label - ENHANCED VISIBILITY
        self.title_label = QLabel()
        self.title_label.setStyleSheet("""
            QLabel {
                color: white;
                font-size: 32px;
                font-weight: bold;
                background: rgba(0,0,0,0.8);
                padding: 15px;
                border-radius: 8px;
                border: 2px solid rgba(255,255,255,0.3);
                text-shadow: 2px 2px 4px rgba(0,0,0,1.0);
            }
        """)
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title_label.setWordWrap(True)
        
        # Subtitle label - ENHANCED VISIBILITY
        self.subtitle_label = QLabel()
        self.subtitle_label.setStyleSheet("""
            QLabel {
                color: #ffffff;
                font-size: 18px;
                background: rgba(0,0,0,0.7);
                padding: 10px;
                border-radius: 5px;
                text-shadow: 1px 1px 2px rgba(0,0,0,1.0);
            }
        """)
        self.subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        # Time display - ENHANCED VISIBILITY
        self.time_label = QLabel()
        self.time_label.setStyleSheet("""
            QLabel {
                color: yellow;
                font-size: 16px;
                font-weight: bold;
                background: rgba(0,0,0,0.8);
                padding: 8px;
                border-radius: 5px;
                border: 1px solid yellow;
            }
        """)
        
        # Ticker label - ENHANCED VISIBILITY
        self.ticker_label = QLabel()
        self.ticker_label.setStyleSheet("""
            QLabel {
                color: white;
                font-size: 14px;
                font-weight: bold;
                background: rgba(220, 53, 69, 0.95);
                padding: 12px;
                border-radius: 5px;
                border: 2px solid rgba(255,255,255,0.3);
            }
        """)
        self.ticker_label.setWordWrap(True)
        
        # Layout
        layout = QVBoxLayout(self)
        layout.setContentsMargins(20, 20, 20, 20)
        
        # Top section
        layout.addStretch(1)
        layout.addWidget(self.title_label)
        layout.addWidget(self.subtitle_label)
        layout.addStretch(2)
        
        # Bottom section
        layout.addWidget(self.ticker_label)
        layout.addSpacing(10)
        
        # Position time label in top-right corner
        self.time_label.setParent(self)
        
        # Update display with initial data
        self.update_display()
        
        logger.debug("NativeOverlayWidget UI setup completed")
    
    def resizeEvent(self, event):
        """Handle resize events"""
        super().resizeEvent(event)
        # Position time label in top-right corner
        if hasattr(self, 'time_label') and self.time_label:
            self.time_label.adjustSize()
            self.time_label.move(self.width() - self.time_label.width() - 20, 20)
    
    def update_overlay_data(self, data: Dict[str, Any]):
        """Update overlay display with new data"""
        try:
            updated = False
            
            if 'title' in data:
                self.overlay_data['title'] = data['title']
                updated = True
            
            if 'subtitle' in data:
                self.overlay_data['subtitle'] = data['subtitle']
                updated = True
            
            if 'ticker' in data:
                self.overlay_data['ticker'] = data['ticker']
                updated = True
            
            if 'currentTime' in data:
                self.overlay_data['currentTime'] = data['currentTime']
                updated = True
            
            if updated:
                self.update_display()
            
            logger.debug(f"Native overlay updated: {data}")
            
        except Exception as e:
            logger.error(f"Failed to update native overlay: {e}")
    
    def update_display(self):
        """Update all display elements"""
        try:
            self.title_label.setText(self.overlay_data.get('title', ''))
            self.subtitle_label.setText(self.overlay_data.get('subtitle', ''))
            self.ticker_label.setText(self.overlay_data.get('ticker', ''))
            self.time_label.setText(self.overlay_data.get('currentTime', ''))
            
            # Adjust time label position
            self.time_label.adjustSize()
            if self.width() > 0:
                self.time_label.move(self.width() - self.time_label.width() - 20, 20)
            
        except Exception as e:
            logger.error(f"Failed to update native overlay display: {e}")
    
    def update_position(self, position: float, duration: float):
        """Update playback position (compatible with WebEngine interface)"""
        try:
            current_time = self.format_time(position)
            total_time = self.format_time(duration)
            self.update_overlay_data({'currentTime': f"{current_time} / {total_time}"})
        except Exception as e:
            logger.error(f"Failed to update position in native overlay: {e}")
    
    def update_video_info(self, info: Dict[str, Any]):
        """Update video information (compatible with WebEngine interface)"""
        # Native overlay doesn't show detailed video stats by default
        # This is for compatibility with the WebEngine interface
        pass
    
    def format_time(self, seconds: float) -> str:
        """Format time in seconds to MM:SS"""
        mins = int(seconds // 60)
        secs = int(seconds % 60)
        return f"{mins:02d}:{secs:02d}"


class VideoWidget(QWidget):
    """Composite video widget with selectable overlay type"""
    
    def __init__(self, parent=None, use_native_overlay=False):
        super().__init__(parent)
        self.use_native_overlay = use_native_overlay
        self.setup_ui()
        logger.info(f"VideoWidget initialized (native_overlay={use_native_overlay})")
    
    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
        overlay_type = "Native Qt Widgets" if self.settings.use_native_overlay else "QWebEngineView"
        logger.info(f"PlayerWindow: Overlay configuration - use_native_overlay={self.settings.use_native_overlay}, using {overlay_type}")
        
        # Create simple video widget without any overlay (VideoWidget no longer handles overlays)
        self.video_widget = VideoWidget(parent=central_widget, use_native_overlay=False)
        layout.addWidget(self.video_widget, 1)
        
        # THREADING FIXED: Re-enable overlay system with proper Qt main thread architecture
        # Create overlay as SEPARATE TOP-LEVEL WINDOW
        self.overlay_window = QWidget()
        self.overlay_window.setWindowFlags(
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.WindowStaysOnTopHint |
            Qt.WindowType.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.debug("Mesa overlay window configured with composition transparency")
        else:
            # Standard hardware transparency
            self.overlay_window.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
            self.overlay_window.setStyleSheet("background: transparent;")
            logger.debug("Hardware overlay window configured with native transparency")

        # Create overlay based on configuration - matching test_video_debug.py behavior
        if self.settings.use_native_overlay:
            self.window_overlay = NativeOverlayWidget(self.overlay_window)
            logger.debug("PlayerWindow: Created NativeOverlayWidget overlay as separate window")
        else:
            # Pass debug_overlay setting and web server URL to OverlayWebView
            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("OVERLAY SYSTEM RE-ENABLED: Threading conflicts resolved with Qt main thread architecture")
        
        # 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:
            if hasattr(self, 'overlay_window') and self.overlay_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()}")

        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)
        if hasattr(self, 'overlay_window') and self.overlay_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)
        if hasattr(self, 'overlay_window') and self.overlay_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.isVisible()}")
            logger.info(f"Overlay window size: {hasattr(self, 'overlay_window') and self.overlay_window.size()}")

            # 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
            if hasattr(self, 'overlay_window') and self.overlay_window:
                self.overlay_window.close()
                logger.debug("Overlay window closed")

        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 isinstance(overlay_view, NativeOverlayWidget)
    
    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
                    if hasattr(self, 'overlay_window') and self.overlay_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 - TEMPORARILY DISABLED FOR TESTING"""
        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'
            
        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
            overlay_type = "Native Qt Widgets" if self.settings.use_native_overlay else "QWebEngineView"
            logger.info(f"Qt Player overlay configuration: {overlay_type}")
            
            # 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
                if hasattr(self.window, 'overlay_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()}")

                # 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 unzipped ZIP file of the match
            if match_id:
                from ..database.manager import DatabaseManager
                from ..config.settings import get_user_data_dir

                # Get database manager (assuming it's available via message bus or settings)
                # For now, we'll use a simplified approach
                user_data_dir = get_user_data_dir()
                temp_dir_pattern = f"match_{match_id}_"

                # Look for temp directories created by _unzip_match_zip_file
                import tempfile
                import os
                temp_base = Path(tempfile.gettempdir())
                logger.debug(f"DEBUG: Looking for match temp dirs in: {temp_base} with pattern: {temp_dir_pattern}")

                for temp_dir in temp_base.glob(f"{temp_dir_pattern}*"):
                    if temp_dir.is_dir():
                        intro_file = temp_dir / "INTRO.mp4"
                        logger.debug(f"DEBUG: Checking for INTRO.mp4 in match ZIP: {intro_file}")
                        if intro_file.exists():
                            logger.info(f"Found INTRO.mp4 in match ZIP: {intro_file}")
                            return intro_file

            # 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")

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

            # 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,
                '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']
            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

            # Determine under/over result if applicable
            under_over_result = None
            main_result = result
            if result in ['UNDER', 'OVER']:
                under_over_result = result
                main_result = None  # No separate main result
            elif result not in ['UNDER', 'OVER']:
                # For main results, check if there's a separate under/over from database
                # This is a simplified approach - in practice, you'd need to determine this from the match outcome
                pass

            # Prepare overlay data for results template
            overlay_data = {
                'outcome': main_result,
                'result': main_result,  # For backwards compatibility
                'under_over_result': under_over_result,
                '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: {overlay_data}")

            # 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
