"""
Server-side match timer component for synchronized countdown across all clients
"""

import time
import logging
import threading
import random
from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta

from .thread_manager import ThreadedComponent
from .message_bus import MessageBus, Message, MessageType, MessageBuilder
from ..database.manager import DatabaseManager
from ..config.manager import ConfigManager

logger = logging.getLogger(__name__)


class MatchTimerComponent(ThreadedComponent):
    """Server-side match timer that synchronizes countdown across all clients"""

    def __init__(self, message_bus: MessageBus, db_manager: DatabaseManager,
                 config_manager: ConfigManager):
        super().__init__("match_timer", message_bus)
        self.db_manager = db_manager
        self.config_manager = config_manager

        # Timer state
        self.timer_running = False
        self.timer_start_time: Optional[float] = None
        self.timer_duration_seconds = 0
        self.current_fixture_id: Optional[str] = None
        self.current_match_id: Optional[int] = None
        self.pending_match_id: Optional[int] = None  # Match prepared by START_INTRO

        # Synchronization
        self._timer_lock = threading.RLock()
        self._last_update = time.time()

        # Register component with message bus
        self.message_bus.register_component(self.name)

        # Register message handlers
        self.message_bus.subscribe(self.name, MessageType.GAME_STARTED, self._handle_game_started)
        self.message_bus.subscribe(self.name, MessageType.SCHEDULE_GAMES, self._handle_schedule_games)
        self.message_bus.subscribe(self.name, MessageType.CUSTOM, self._handle_custom_message)
        self.message_bus.subscribe(self.name, MessageType.NEXT_MATCH, self._handle_next_match)
        self.message_bus.subscribe(self.name, MessageType.START_INTRO, self._handle_start_intro)

        logger.info("MatchTimer component initialized")

    def initialize(self) -> bool:
        """Initialize the match timer component"""
        try:
            logger.info("MatchTimer component initialized successfully")
            return True
        except Exception as e:
            logger.error(f"Failed to initialize MatchTimer: {e}")
            return False

    def run(self):
        """Main timer loop"""
        logger.info("MatchTimer component started")

        while self.running and not self.shutdown_event.is_set():
            try:
                # Process any pending messages first
                message = self.message_bus.get_message(self.name, timeout=0.1)
                if message:
                    self._process_message(message)

                current_time = time.time()

                # Check if timer needs to be updated
                if self.timer_running and self.timer_start_time:
                    elapsed = current_time - self.timer_start_time
                    remaining = self.timer_duration_seconds - elapsed

                    if remaining <= 0:
                        # Timer reached zero, start next match
                        self._on_timer_expired()
                    else:
                        # Send periodic timer updates (every 1 second)
                        if current_time - self._last_update >= 1.0:
                            self._send_timer_update()
                            self._last_update = current_time

                # Check if we should stop the timer (no more matches)
                if self.timer_running and self._should_stop_timer():
                    self._stop_timer()
                    logger.info("Timer stopped - no more matches to process")

                # Update heartbeat for health monitoring
                self.heartbeat()

                # Sleep for a short interval
                time.sleep(0.1)

            except Exception as e:
                logger.error(f"MatchTimer run loop error: {e}")
                time.sleep(1.0)

        logger.info("MatchTimer component stopped")

    def _process_message(self, message):
        """Process incoming messages directly"""
        try:
            logger.debug(f"MatchTimer processing message: {message}")

            # Handle messages directly since some messages don't trigger subscription handlers
            if message.type == MessageType.GAME_STARTED:
                self._handle_game_started(message)
            elif message.type == MessageType.SCHEDULE_GAMES:
                self._handle_schedule_games(message)
            elif message.type == MessageType.CUSTOM:
                self._handle_custom_message(message)
            elif message.type == MessageType.NEXT_MATCH:
                self._handle_next_match(message)
            elif message.type == MessageType.START_INTRO:
                self._handle_start_intro(message)

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

    def shutdown(self):
        """Shutdown the match timer"""
        with self._timer_lock:
            self.timer_running = False
            self.timer_start_time = None
            self.current_fixture_id = None
            self.current_match_id = None
            self.pending_match_id = None

        # Unregister from message bus
        self.message_bus.unregister_component(self.name)

        logger.info("MatchTimer component shutdown")

    def get_timer_state(self) -> Dict[str, Any]:
        """Get current timer state for API responses"""
        with self._timer_lock:
            if not self.timer_running or not self.timer_start_time:
                return {
                    "running": False,
                    "remaining_seconds": 0,
                    "total_seconds": 0,
                    "fixture_id": None,
                    "match_id": None,
                    "start_time": None
                }

            elapsed = time.time() - self.timer_start_time
            remaining = max(0, self.timer_duration_seconds - elapsed)

            return {
                "running": True,
                "remaining_seconds": int(remaining),
                "total_seconds": self.timer_duration_seconds,
                "fixture_id": self.current_fixture_id,
                "match_id": self.current_match_id,
                "start_time": self.timer_start_time,
                "elapsed_seconds": int(elapsed)
            }

    def _handle_game_started(self, message: Message):
        """Handle GAME_STARTED message"""
        try:
            fixture_id = message.data.get("fixture_id")

            logger.info(f"Received GAME_STARTED message for fixture: {fixture_id}")

            # Get match interval from configuration
            match_interval = self._get_match_interval()

            # Start the timer
            self._start_timer(match_interval * 60, fixture_id)

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

    def _handle_schedule_games(self, message: Message):
        """Handle SCHEDULE_GAMES message"""
        try:
            logger.info("Received SCHEDULE_GAMES message")

            # Get match interval from configuration
            match_interval = self._get_match_interval()

            # Start the timer without specific fixture (will find first available)
            self._start_timer(match_interval * 60, None)

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

    def _handle_custom_message(self, message: Message):
        """Handle custom messages (like timer state requests)"""
        try:
            request = message.data.get("request")

            if request == "get_timer_state":
                # Send timer state response
                timer_state = self.get_timer_state()

                response = Message(
                    type=MessageType.CUSTOM,
                    sender=self.name,
                    recipient=message.sender,
                    data={
                        "response": "timer_state",
                        "timer_state": timer_state,
                        "timestamp": time.time()
                    },
                    correlation_id=message.correlation_id
                )
                self.message_bus.publish(response)

            elif request == "stop":
                # Stop the timer
                self._stop_timer()

                response = Message(
                    type=MessageType.CUSTOM,
                    sender=self.name,
                    recipient=message.sender,
                    data={
                        "response": "timer_stopped",
                        "timestamp": time.time()
                    },
                    correlation_id=message.correlation_id
                )
                self.message_bus.publish(response)

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

    def _handle_next_match(self, message: Message):
        """Handle NEXT_MATCH message - restart timer for next match interval"""
        try:
            fixture_id = message.data.get("fixture_id")
            match_id = message.data.get("match_id")

            logger.info(f"Received NEXT_MATCH message for fixture {fixture_id}, match {match_id}")
            logger.info("Previous match completed - restarting timer for next interval")

            # Start timer first to ensure countdown is visible immediately
            match_interval = self._get_match_interval()
            self._start_timer(match_interval * 60, fixture_id)
            logger.info(f"Timer started for {match_interval} minute interval")

            # Then find and start the next match
            match_info = self._find_and_start_next_match()

            if match_info:
                logger.info(f"Prepared next match {match_info['match_id']} in fixture {match_info['fixture_id']}")

                # Update timer with correct fixture_id if different
                if match_info['fixture_id'] != fixture_id:
                    with self._timer_lock:
                        self.current_fixture_id = match_info['fixture_id']
                    # Send updated timer info
                    self._send_timer_update()
                    logger.info(f"Timer updated with fixture {match_info['fixture_id']}")
            else:
                logger.info("No more matches to start, stopping timer")
                self._stop_timer()

        except Exception as e:
            logger.error(f"Failed to handle NEXT_MATCH message: {e}")
            # On error, try to restart timer anyway
            try:
                match_interval = self._get_match_interval()
                self._start_timer(match_interval * 60, self.current_fixture_id)
            except Exception as restart_e:
                logger.error(f"Failed to restart timer after NEXT_MATCH error: {restart_e}")

    def _handle_start_intro(self, message: Message):
        """Handle START_INTRO message - store the match_id for later MATCH_START"""
        try:
            fixture_id = message.data.get("fixture_id")
            match_id = message.data.get("match_id")

            logger.info(f"Received START_INTRO message for fixture {fixture_id}, match {match_id}")

            # Store the match_id for when timer expires
            with self._timer_lock:
                self.pending_match_id = match_id
                self.current_fixture_id = fixture_id

            logger.info(f"Stored pending match_id {match_id} for timer expiration")

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

    def _start_timer(self, duration_seconds: int, fixture_id: Optional[str]):
        """Start the countdown timer"""
        with self._timer_lock:
            self.timer_duration_seconds = duration_seconds
            self.timer_start_time = time.time()
            self.timer_running = True
            self.current_fixture_id = fixture_id

            logger.info(f"Match timer started: {duration_seconds}s for fixture {fixture_id}")

            # Send timer started notification
            self._send_timer_update()

    def _stop_timer(self):
        """Stop the countdown timer"""
        with self._timer_lock:
            self.timer_running = False
            self.timer_start_time = None
            self.current_fixture_id = None
            self.current_match_id = None

            logger.info("Match timer stopped")

            # Send timer stopped notification
            self._send_timer_update()

    def _on_timer_expired(self):
        """Handle timer expiration - start the match that was prepared by START_INTRO"""
        try:
            logger.info("Match timer expired, starting prepared match...")

            with self._timer_lock:
                pending_match_id = self.pending_match_id
                fixture_id = self.current_fixture_id

            if pending_match_id:
                # Send MATCH_START message for the prepared match
                match_start_message = MessageBuilder.match_start(
                    sender=self.name,
                    fixture_id=fixture_id,
                    match_id=pending_match_id
                )

                self.message_bus.publish(match_start_message)
                logger.info(f"Sent MATCH_START for prepared match {pending_match_id} in fixture {fixture_id}")
                logger.info("Timer stopped - will restart after match completion (NEXT_MATCH)")

                # Clear the pending match and stop the timer
                with self._timer_lock:
                    self.pending_match_id = None
                self._stop_timer()
            else:
                logger.warning("No pending match prepared by START_INTRO, stopping timer")
                self._stop_timer()

        except Exception as e:
            logger.error(f"Failed to handle timer expiration: {e}")
            # Stop timer on error - don't restart automatically
            self._stop_timer()

    def _find_and_start_next_match(self) -> Optional[Dict[str, Any]]:
        """Find and start the next available match"""
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import MatchModel

                # Priority 1: Use current fixture if specified
                target_fixture_id = None
                target_match = None

                if self.current_fixture_id:
                    # Find next match in current fixture
                    matches = session.query(MatchModel).filter_by(
                        fixture_id=self.current_fixture_id
                    ).order_by(MatchModel.match_number.asc()).all()

                    target_match = self._find_next_match_in_list(matches)

                if not target_match:
                    # Priority 2: Find matches in today's fixtures (using UTC for consistency)
                    today = datetime.utcnow().date()
                    today_matches = session.query(MatchModel).filter(
                        MatchModel.start_time >= datetime.combine(today, datetime.min.time()),
                        MatchModel.start_time < datetime.combine(today, datetime.max.time())
                    ).order_by(MatchModel.created_at.asc()).all()

                    if today_matches:
                        # Group by fixture and find first available match
                        fixtures = {}
                        for match in today_matches:
                            if match.fixture_id not in fixtures:
                                fixtures[match.fixture_id] = []
                            fixtures[match.fixture_id].append(match)

                        # Try each fixture
                        for fixture_id, matches in fixtures.items():
                            target_match = self._find_next_match_in_list(matches)
                            if target_match:
                                target_fixture_id = fixture_id
                                break

                if not target_match:
                    # Priority 3: Find any pending matches
                    all_matches = session.query(MatchModel).filter_by(
                        status='pending'
                    ).order_by(MatchModel.created_at.asc()).all()

                    if all_matches:
                        target_match = all_matches[0]
                        target_fixture_id = target_match.fixture_id

                if target_match:
                    fixture_id = target_fixture_id or target_match.fixture_id

                    # Ensure there are at least 5 next matches in the fixture before sending START_INTRO
                    remaining_matches = self._count_remaining_matches_in_fixture(fixture_id, session)
                    logger.info(f"Fixture {fixture_id} has {remaining_matches} remaining matches")

                    if remaining_matches < 5:
                        logger.info(f"Only {remaining_matches} matches remaining (minimum 5 required) - ensuring minimum matches")
                        self._ensure_minimum_matches_in_fixture(fixture_id, 5 - remaining_matches, session)
                        # Recount after adding matches
                        remaining_matches = self._count_remaining_matches_in_fixture(fixture_id, session)
                        logger.info(f"After ensuring minimum matches, fixture {fixture_id} now has {remaining_matches} remaining matches")

                    # Send START_INTRO message
                    start_intro_message = MessageBuilder.start_intro(
                        sender=self.name,
                        fixture_id=fixture_id,
                        match_id=target_match.id
                    )

                    self.message_bus.publish(start_intro_message)

                    # Set the pending match for timer expiration
                    with self._timer_lock:
                        self.pending_match_id = target_match.id
                        self.current_fixture_id = fixture_id

                    return {
                        "fixture_id": fixture_id,
                        "match_id": target_match.id,
                        "match_number": target_match.match_number
                    }

                return None

            finally:
                session.close()

        except Exception as e:
            logger.error(f"Failed to find and start next match: {e}")
            return None

    def _find_next_match_in_list(self, matches: list) -> Optional[Any]:
        """Find the next match to start from a list of matches"""
        # Priority order: bet -> scheduled -> pending
        for status in ['bet', 'scheduled', 'pending']:
            for match in matches:
                if match.status == status:
                    return match
        return None

    def _should_stop_timer(self) -> bool:
        """Check if timer should be stopped (no more matches)"""
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import MatchModel

                # Count matches that can be started
                active_matches = session.query(MatchModel).filter(
                    MatchModel.status.in_(['bet', 'scheduled', 'pending'])
                ).count()

                return active_matches == 0

            finally:
                session.close()

        except Exception as e:
            logger.error(f"Failed to check if timer should stop: {e}")
            return False

    def _get_match_interval(self) -> int:
        """Get match interval from configuration"""
        try:
            if self.config_manager:
                general_config = self.config_manager.get_section_config("general") or {}
                return general_config.get('match_interval', 5)
            else:
                return 20  # Default fallback
        except Exception as e:
            logger.error(f"Failed to get match interval: {e}")
            return 20

    def _count_remaining_matches_in_fixture(self, fixture_id: str, session) -> int:
        """Count remaining matches in fixture that can still be played"""
        try:
            from ..database.models import MatchModel

            # Count matches that are not in terminal states (done, cancelled, failed, paused)
            remaining_count = session.query(MatchModel).filter(
                MatchModel.fixture_id == fixture_id,
                MatchModel.status.notin_(['done', 'cancelled', 'failed', 'paused']),
                MatchModel.active_status == True
            ).count()

            return remaining_count

        except Exception as e:
            logger.error(f"Failed to count remaining matches in fixture {fixture_id}: {e}")
            return 0

    def _ensure_minimum_matches_in_fixture(self, fixture_id: str, minimum_required: int, session):
        """Ensure fixture has at least minimum_required matches by creating new ones from old completed matches"""
        try:
            from ..database.models import MatchModel

            logger.info(f"Ensuring fixture {fixture_id} has at least {minimum_required} additional matches")

            # Get the last played match ID to exclude it from selection
            last_played_match_id = self._get_last_played_match_id(fixture_id, session)
            logger.info(f"Last played match ID: {last_played_match_id}")

            # Select random completed matches, excluding the last played one
            old_matches = self._select_random_completed_matches_excluding_last(minimum_required, last_played_match_id, session)

            if old_matches:
                logger.info(f"Selected {len(old_matches)} old matches to create new ones")
                self._create_matches_from_old_matches(fixture_id, old_matches, session)
                logger.info(f"Created {len(old_matches)} new matches in fixture {fixture_id}")
            else:
                logger.warning(f"No suitable old matches found to create new ones for fixture {fixture_id}")

        except Exception as e:
            logger.error(f"Failed to ensure minimum matches in fixture {fixture_id}: {e}")

    def _get_last_played_match_id(self, fixture_id: str, session) -> Optional[int]:
        """Get the ID of the last match that was played in this fixture"""
        try:
            from ..database.models import MatchModel

            # Find the most recently completed match in this fixture
            last_match = session.query(MatchModel).filter(
                MatchModel.fixture_id == fixture_id,
                MatchModel.status.in_(['done', 'end', 'cancelled', 'failed']),
                MatchModel.active_status == True
            ).order_by(MatchModel.updated_at.desc()).first()

            if last_match:
                return last_match.id
            else:
                return None

        except Exception as e:
            logger.error(f"Failed to get last played match ID for fixture {fixture_id}: {e}")
            return None

    def _select_random_completed_matches_excluding_last(self, count: int, exclude_match_id: Optional[int], session) -> List[Any]:
        """Select random completed matches from the database, excluding matches with same fighters as the last played match"""
        try:
            from ..database.models import MatchModel
            from sqlalchemy.orm import joinedload

            # Build query for completed matches
            # Exclude matches from fixtures that contain "_recycle_" in the fixture name
            query = session.query(MatchModel).options(joinedload(MatchModel.outcomes)).filter(
                MatchModel.status.in_(['done', 'end', 'cancelled', 'failed']),
                MatchModel.active_status == True,
                ~MatchModel.fixture_id.like('%_recycle_%')
            )

            # Exclude matches with same fighters as the last played match
            if exclude_match_id:
                last_match = session.query(MatchModel).filter(MatchModel.id == exclude_match_id).first()
                if last_match:
                    # Exclude matches with same fighter combinations (both directions)
                    query = query.filter(
                        ~((MatchModel.fighter1_township == last_match.fighter1_township) &
                          (MatchModel.fighter2_township == last_match.fighter2_township)) &
                        ~((MatchModel.fighter1_township == last_match.fighter2_township) &
                          (MatchModel.fighter2_township == last_match.fighter1_township))
                    )

            completed_matches = query.all()

            if len(completed_matches) >= count:
                # Select random matches
                selected_matches = random.sample(completed_matches, count)
                logger.info(f"Selected {len(selected_matches)} random completed matches (excluding same fighters as last match)")
                return selected_matches
            else:
                # Not enough matches with exclusions
                if completed_matches:
                    logger.warning(f"Only {len(completed_matches)} completed matches available (excluding same fighters), requested {count} - returning available")
                    return completed_matches
                else:
                    # No matches found with exclusions, recycle the oldest match from database
                    logger.warning("🚨 No matches available after exclusions - recycling the oldest match from database")
                    oldest_match = session.query(MatchModel).filter(
                        MatchModel.status.in_(['done', 'end', 'cancelled', 'failed']),
                        MatchModel.active_status == True
                    ).order_by(MatchModel.created_at.asc()).first()

                    if oldest_match:
                        logger.info(f"♻️ Recycled oldest match: {oldest_match.match_number} ({oldest_match.fighter1_township} vs {oldest_match.fighter2_township})")
                        return [oldest_match]
                    else:
                        logger.error("🚨 No completed matches found in database at all")
                        return []

        except Exception as e:
            logger.error(f"Failed to select random completed matches excluding same fighters: {e}")
            return []

    def _create_matches_from_old_matches(self, fixture_id: str, old_matches: List[Any], session):
        """Create new matches in the fixture by copying from old completed matches"""
        try:
            from ..database.models import MatchModel, MatchOutcomeModel

            now = datetime.utcnow()

            # Determine the status for new matches based on system state
            new_match_status = self._determine_new_match_status(fixture_id, session)

            # Find the maximum match_number in the fixture and increment from there
            max_match_number = session.query(MatchModel.match_number).filter(
                MatchModel.fixture_id == fixture_id
            ).order_by(MatchModel.match_number.desc()).first()

            match_number = (max_match_number[0] + 1) if max_match_number else 1

            for old_match in old_matches:
                # Create a new match based on the old one
                new_match = MatchModel(
                    match_number=match_number,
                    fighter1_township=old_match.fighter1_township,
                    fighter2_township=old_match.fighter2_township,
                    venue_kampala_township=old_match.venue_kampala_township,
                    start_time=now,
                    status=new_match_status,
                    fixture_id=fixture_id,
                    filename=old_match.filename,
                    file_sha1sum=old_match.file_sha1sum,
                    active_status=True,
                    zip_filename=old_match.zip_filename,
                    zip_sha1sum=old_match.zip_sha1sum,
                    zip_upload_status='completed',  # Assume ZIP is already available
                    fixture_active_time=int(now.timestamp())
                )

                session.add(new_match)
                session.flush()  # Get the ID

                # Copy match outcomes
                for outcome in old_match.outcomes:
                    new_outcome = MatchOutcomeModel(
                        match_id=new_match.id,
                        column_name=outcome.column_name,
                        float_value=outcome.float_value
                    )
                    session.add(new_outcome)

                logger.debug(f"Created new match #{match_number} from old match #{old_match.match_number} with status {new_match_status}")
                match_number += 1

            session.commit()
            logger.info(f"Created {len(old_matches)} new matches in fixture {fixture_id} with status {new_match_status}")

        except Exception as e:
            logger.error(f"Failed to create matches from old matches: {e}")
            session.rollback()
            raise

    def _determine_new_match_status(self, fixture_id: str, session) -> str:
        """Determine the status for new matches based on system state"""
        try:
            from ..database.models import MatchModel

            # Get betting mode configuration
            betting_mode = self._get_betting_mode_config()

            # If betting mode is "all_bets_on_start", new matches should be 'bet'
            if betting_mode == "all_bets_on_start":
                logger.info(f"Betting mode is 'all_bets_on_start' - new matches will be in bet status")
                return 'bet'

            # Check if the last match in the fixture is in 'bet' status
            last_match = session.query(MatchModel).filter(
                MatchModel.fixture_id == fixture_id,
                MatchModel.active_status == True
            ).order_by(MatchModel.match_number.desc()).first()

            if last_match and last_match.status == 'bet':
                logger.info(f"Last match in fixture {fixture_id} is in bet status - new matches will be in bet status")
                return 'bet'

            # Default status
            return 'scheduled'

        except Exception as e:
            logger.error(f"Failed to determine new match status: {e}")
            return 'scheduled'  # Default fallback

    def _get_betting_mode_config(self) -> str:
        """Get global betting mode configuration from game config (default: 'all_bets_on_start')"""
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import GameConfigModel

                # Get global betting mode configuration from game_config table
                betting_mode_config = session.query(GameConfigModel).filter_by(
                    config_key='betting_mode'
                ).first()

                if betting_mode_config:
                    return betting_mode_config.get_typed_value()
                else:
                    # Default to 'all_bets_on_start' if no configuration found
                    logger.debug("No betting mode configuration found, using default: 'all_bets_on_start'")
                    return 'all_bets_on_start'

            finally:
                session.close()

        except Exception as e:
            logger.error(f"Failed to get betting mode config: {e}")
            # Default fallback
            return 'all_bets_on_start'

    def _send_timer_update(self):
        """Send timer update message to all clients"""
        try:
            timer_state = self.get_timer_state()

            update_message = Message(
                type=MessageType.CUSTOM,  # Use custom type for timer updates
                sender=self.name,
                data={
                    "timer_update": timer_state,
                    "timestamp": time.time()
                }
            )

            # Broadcast to all components including qt_player and web_dashboard
            self.message_bus.publish(update_message, broadcast=True)

        except Exception as e:
            logger.error(f"Failed to send timer update: {e}")