from __future__ import annotations
from typing import TYPE_CHECKING
import random
from datetime import datetime, timedelta
from plexapi.video import Episode

from plex_auto_languages.alerts.base import PlexAlert
from plex_auto_languages.utils.logger import get_logger
from plex_auto_languages.constants import EventType

if TYPE_CHECKING:
    from plex_auto_languages.plex_server import PlexServer


logger = get_logger()


class PlexPlaying(PlexAlert):
    """
    Handles media playback events from Plex server.

    This class processes notifications related to media playback sessions,
    tracking session states and managing audio/subtitle track selection
    for TV show episodes.

    Attributes:
        TYPE (str): The alert type identifier ('playing').
    """

    TYPE = "playing"

    @property
    def client_identifier(self) -> str:
        """
        Gets the client identifier from the message.

        Returns:
            str: The unique identifier of the client device playing the media.
        """
        return self._message.get("clientIdentifier", None)

    @property
    def item_key(self) -> str:
        """
        Gets the media item key from the message.

        Returns:
            str: The key identifying the media item in Plex.
        """
        return self._message.get("key", None)

    @property
    def session_key(self) -> str:
        """
        Gets the session key from the message.

        Returns:
            str: The unique identifier for the current playback session.
        """
        return self._message.get("sessionKey", None)

    @property
    def session_state(self) -> str:
        """
        Gets the current state of the playback session.

        Returns:
            str: The playback state (e.g., 'playing', 'paused', 'stopped').
        """
        return self._message.get("state", None)

    def process(self, plex: 'PlexServer') -> None:
        """
        Processes the playback event and manages track selection.

        This method handles media playback events by:
        1. Identifying the user and their Plex instance
        2. Verifying the media is a TV show episode
        3. Checking if the library or show should be ignored
        4. Tracking session state changes
        5. Managing session cache when playback stops
        6. Detecting changes in selected audio/subtitle streams
        7. Triggering track selection based on user preferences

        Args:
            plex (PlexServer): The Plex server instance to interact with.

        Returns:
            None
        """

        # Get User id and user's Plex instance
        if self.client_identifier not in plex.cache.user_clients:
            user_id, username = plex.get_user_from_client_identifier(self.client_identifier)
            if user_id is None:
                return
            plex.cache.user_clients[self.client_identifier] = (user_id, username, datetime.now())
        else:
            cached_client_data = plex.cache.user_clients[self.client_identifier]
            if isinstance(cached_client_data, tuple) and len(cached_client_data) >= 2:
                user_id, username = cached_client_data[0], cached_client_data[1]
            else:
                user_id, username = cached_client_data  # old format
            plex.cache.user_clients[self.client_identifier] = (user_id, username, datetime.now())
        user_plex = plex.get_plex_instance_of_user(user_id)
        if user_plex is None:
            return

        # Check if key is streaming live TV
        if self.item_key is None or self.item_key.startswith("/livetv"):
            return

        # Skip if not an Episode
        item = user_plex.fetch_item(self.item_key)
        if item is None or not isinstance(item, Episode):
            return

        # Skip if the library should be ignored
        if plex.should_ignore_library(item.librarySectionTitle):
            logger.debug(f"[Play Session] Ignoring show: '{item.show().title}' episode: 'S{item.seasonNumber:02}E{item.episodeNumber:02}' due to ignored library: '{item.librarySectionTitle}'")
            return

        # Skip if the show should be ignored
        if plex.should_ignore_show(item.show()):
            logger.debug(f"[Play Session] Ignoring show: '{item.show().title}' episode: 'S{item.seasonNumber:02}E{item.episodeNumber:02}' due to Plex show labels")
            return

        # Skip if the file path matches an ignore pattern
        if plex.should_ignore_filepath(item):
            logger.debug(f"[Play Session] Ignoring show: '{item.show().title}' episode: 'S{item.seasonNumber:02}E{item.episodeNumber:02}' due to file path matching ignore pattern")
            return

        # Read selected streams before deciding whether this event is a duplicate.
        # A stream change can happen while the session state remains "playing".
        item.reload()
        audio_stream, subtitle_stream = plex.get_selected_streams(item)
        selected_streams_ids = (
            audio_stream.id if audio_stream is not None else None,
            subtitle_stream.id if subtitle_stream is not None else None
        )

        cached_session_state = None
        if self.session_key in plex.cache.session_states:
            cached_session_data = plex.cache.session_states[self.session_key]
            if isinstance(cached_session_data, tuple):
                cached_session_state = cached_session_data[0]
            else:
                cached_session_state = cached_session_data

        cached_streams_ids = plex.cache.default_streams.get(item.key)

        # Skip only if both the session state and selected streams are unchanged.
        if cached_session_state == self.session_state and cached_streams_ids == selected_streams_ids:
            return

        logger.debug(
            f"[Play Session] Session: {self.session_key} | "
            f"State: '{self.session_state}' | "
            f"User id: {user_id} | "
            f"Episode: {item} | "
            f"Streams: {selected_streams_ids}"
        )

        plex.cache.session_states[self.session_key] = (self.session_state, datetime.now())
        plex.cache.default_streams[item.key] = selected_streams_ids

        # Reset cache if the session is stopped
        if self.session_state == "stopped":
            logger.debug(f"[Play Session] End of session {self.session_key} for user {user_id}")
            if self.session_key in plex.cache.session_states:
                del plex.cache.session_states[self.session_key]
            if self.client_identifier in plex.cache.user_clients:
                del plex.cache.user_clients[self.client_identifier]
            return

        # Limit the size of default_streams to prevent memory leak
        if len(plex.cache.default_streams) > 10000:
            # Remove 10% of entries randomly to prevent unbounded growth
            num_to_remove = len(plex.cache.default_streams) // 10
            keys_to_remove = random.sample(list(plex.cache.default_streams.keys()), num_to_remove)
            for key in keys_to_remove:
                del plex.cache.default_streams[key]

        # Change tracks if needed
        plex.change_tracks(username, item, EventType.PLAY_OR_ACTIVITY)
