# wcs/playback/PlaylistCountdownMonitor.py
"""
Monitor pro countdown dialog pri prehravani z MyTV playlistu.
Sleduje cas prehravani a pred koncem kazde epizody zobrazi
AutoplayCountdownDialog s info o dalsi polozce v playlistu.

Pouziva poll-based pristup (ne xbmc.Player callback) protoze
playlist polozky bezi v separatnich Python procesech.
"""

import xbmc
import xbmcgui
import xbmcaddon
import threading
import time
from urllib.parse import urlparse, parse_qs, unquote_plus

from wcs.playback.dialogs.countdown import AutoplayCountdownDialog


class PlaylistCountdownMonitor:
    """Monitor pro countdown dialog pri playlist prehravani.
    
    Akce:
    - Play Now / Timeout -> playnext() (preskoci na dalsi v playlistu)
    - Cancel -> stop() (zastavi cele prehravani)
    """
    
    ACTION_PLAY_NOW = 1
    ACTION_CANCEL = 2
    ACTION_TIMEOUT = 3
    
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._inited = False
        return cls._instance
    
    def __init__(self):
        if self._inited:
            return
        self._active = False
        self._monitor_thread = None
        self._player = xbmc.Player()
        self._external_meta = None
        self._load_settings()
        self._inited = True
        xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Initialized", level=xbmc.LOGINFO)
    
    def _load_settings(self):
        """Nacte nastaveni z addonu."""
        try:
            addon = xbmcaddon.Addon()
            self._countdown_seconds = int(addon.getSetting('autoplay_countdown_seconds') or '30')
            self._countdown_seconds = max(10, min(120, self._countdown_seconds))
            self._trigger_seconds = int(addon.getSetting('autoplay_trigger_seconds') or '5')
            self._trigger_seconds = max(3, self._trigger_seconds)
            if self._trigger_seconds >= self._countdown_seconds:
                self._trigger_seconds = max(3, self._countdown_seconds - 5)
            self._countdown_duration = max(5, self._countdown_seconds - self._trigger_seconds)
        except (ValueError, TypeError):
            self._countdown_seconds = 30
            self._trigger_seconds = 5
            self._countdown_duration = 25
    
    # --- Public API ---
    
    def activate(self, meta=None):
        """Aktivuje monitoring prehravani.
        
        Args:
            meta: Volitelny dict s klici 'channel_id', 'tmdb_id', 'season', 'episode'.
                  Pokud je nastaven, pouzije se misto parsovani playlist URL.
                  Nutne pro manual playback (PlayMedia bez playlistu).
        """
        self._load_settings()
        self._external_meta = meta
        self._active = True
        
        # Zastavit predchozi monitor thread pokud bezi
        if self._monitor_thread and self._monitor_thread.is_alive():
            self._active = False
            self._monitor_thread.join(timeout=2)
            self._active = True
        
        mode = 'external meta' if meta else 'playlist URL'
        xbmc.log(
            f"[plugin.video.wcs] PlaylistCountdownMonitor: Activated "
            f"(mode={mode}, countdown at {self._countdown_seconds}s, trigger at {self._trigger_seconds}s)",
            level=xbmc.LOGINFO
        )
        
        # Spustit monitoring thread -- poll-based (ne callback)
        self._monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self._monitor_thread.start()
    
    def deactivate(self):
        """Deaktivuje monitoring."""
        self._active = False
        self._external_meta = None
        xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Deactivated", level=xbmc.LOGINFO)
    
    # --- Poll-based monitoring loop ---
    
    def _monitor_loop(self):
        """Hlavni monitoring smycka -- sleduje playlist prehravani."""
        xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Monitor loop started", level=xbmc.LOGINFO)
        
        # Pockat na start prehravani (max 30s)
        if not self._wait_for_playback_start():
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Playback never started, exiting", level=xbmc.LOGWARNING)
            self._active = False
            return
        
        # PlayMedia mode: neni playlist, metadata mame externalne
        # (PlayMedia nepridava do video playlistu, getposition() vraci -1)
        if self._external_meta:
            total_time = self._wait_for_total_time()
            if total_time > 0:
                xbmc.log(
                    f"[plugin.video.wcs] PlaylistCountdownMonitor: PlayMedia mode "
                    f"(total: {total_time:.0f}s)",
                    level=xbmc.LOGINFO
                )
                self._monitor_episode(total_time)
            else:
                xbmc.log(
                    "[plugin.video.wcs] PlaylistCountdownMonitor: PlayMedia mode - no total time",
                    level=xbmc.LOGWARNING
                )
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Monitor loop ended", level=xbmc.LOGINFO)
            self._active = False
            return
        
        # Playlist mode: sleduje kazdy item v playlistu
        last_playlist_pos = -1
        
        while self._active:
            if not self._player.isPlaying():
                # Prehravani skoncilo
                xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Playback stopped, exiting", level=xbmc.LOGINFO)
                break
            
            # Detekce noveho itemu v playlistu
            try:
                playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
                current_pos = playlist.getposition()
            except Exception:
                current_pos = -1
            
            if current_pos != last_playlist_pos:
                if last_playlist_pos >= 0:
                    # Preskok na novou polozku -- pockat az se nacte nove video
                    xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Position changed {last_playlist_pos} -> {current_pos}, waiting for new video", level=xbmc.LOGINFO)
                    if not self._wait_for_new_video():
                        break
                
                last_playlist_pos = current_pos
                xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Monitoring playlist position {current_pos}", level=xbmc.LOGINFO)
                
                # Log zda existuje dalsi polozka (monitor bezi i bez nej -- resume se uklada)
                if not self._has_next_in_playlist():
                    xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: No next item (single episode mode)", level=xbmc.LOGINFO)
                
                # Pockat na total time
                total_time = self._wait_for_total_time()
                if total_time <= 0:
                    time.sleep(2)
                    continue
                
                # Monitor tuto epizodu
                self._monitor_episode(total_time)
                # Po navratu z _monitor_episode pokracujeme -- detekujeme dalsi position change
                continue
            
            time.sleep(1)
        
        self._active = False
        xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Monitor loop ended", level=xbmc.LOGINFO)
    
    def _wait_for_playback_start(self):
        """Pocka az zacne prehravani (max 30s)."""
        for _ in range(60):  # 30s
            if not self._active:
                return False
            if self._player.isPlaying():
                return True
            time.sleep(0.5)
        return False
    
    def _wait_for_total_time(self):
        """Pocka na dostupnost total time (max 10s)."""
        for _ in range(100):  # 10s
            if not self._active or not self._player.isPlaying():
                return 0
            try:
                total = self._player.getTotalTime()
                if total > 0:
                    return total
            except Exception:
                pass
            time.sleep(0.1)
        return 0
    
    def _wait_for_new_video(self):
        """Pocka az se nacte nove video po PlayerControl(Next).
        
        Kodi po preskoku na dalsi playlist item chvili vraci
        getTotalTime()/getTime() stareho videa. Musime pockat
        az se totalTime zmeni (jiny nez stary).
        
        Returns:
            True pokud se nove video nactelo, False pokud prehravani skoncilo
        """
        # Zapamatovat stary totalTime pro porovnani
        old_total = 0
        try:
            old_total = self._player.getTotalTime()
        except Exception:
            pass
        
        xbmc.log(
            f"[plugin.video.wcs] PlaylistCountdownMonitor: Waiting for new video (old_total: {old_total:.0f}s)",
            level=xbmc.LOGINFO
        )
        
        # Fixni pauza -- dat Kodi cas na prechod prehravace
        time.sleep(5)
        
        # Overit ze prehravani jeste bezi
        if not self._active or not self._player.isPlaying():
            time.sleep(2)
            if not self._active or not self._player.isPlaying():
                return False
        
        # Cekat az se getTotalTime zmeni oproti staremu
        for _ in range(100):  # max 10s
            if not self._active:
                return False
            if not self._player.isPlaying():
                time.sleep(1)
                if not self._player.isPlaying():
                    return False
                continue
            try:
                current_total = self._player.getTotalTime()
                current_time = self._player.getTime()
                
                # totalTime se zmenil -- nove video potvrzeno
                if current_total > 0 and abs(current_total - old_total) > 10:
                    xbmc.log(
                        f"[plugin.video.wcs] PlaylistCountdownMonitor: New video confirmed "
                        f"(total: {current_total:.0f}s vs old: {old_total:.0f}s, time: {current_time:.1f}s)",
                        level=xbmc.LOGINFO
                    )
                    return True
            except Exception:
                pass
            time.sleep(0.1)
        
        # Timeout -- totalTime se nezmenil (mozna stejna delka epizody)
        # Pouzijeme getTime < 60 jako fallback (po 5s + 10s cekani)
        try:
            current_time = self._player.getTime()
            xbmc.log(
                f"[plugin.video.wcs] PlaylistCountdownMonitor: TotalTime unchanged, using time fallback "
                f"(time: {current_time:.1f}s)",
                level=xbmc.LOGWARNING
            )
        except Exception:
            pass
        return True
    
    def _get_current_item_meta(self):
        """Ziska channel_id, tmdb_id, season, episode.
        
        Pouzije externi meta (z activate) nebo parsuje URL z playlistu.
        
        Returns:
            dict s klici 'channel_id', 'tmdb_id', 'season', 'episode' nebo None
        """
        # 1. Externi meta (manual playback pres PlayMedia)
        if self._external_meta:
            return dict(self._external_meta)
        
        # 2. Fallback: parsovani playlist URL
        try:
            playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
            pos = playlist.getposition()
            if pos < 0:
                return None
            item = playlist[pos]
            path = item.getPath()
            if not path or 'plugin://' not in path:
                return None
            parsed = urlparse(path)
            params = parse_qs(parsed.query)
            channel_id = unquote_plus(params.get('mytv_channel_id', [''])[0])
            tmdb_id = params.get('tmdb_id', [''])[0]
            season = params.get('season_number', [''])[0]
            episode = params.get('episode_number', [''])[0]
            if not channel_id or not tmdb_id:
                return None
            return {
                'channel_id': channel_id,
                'tmdb_id': tmdb_id,
                'season': int(season) if season else 0,
                'episode': int(episode) if episode else 0,
            }
        except Exception as e:
            xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Error getting item meta: {e}", level=xbmc.LOGERROR)
            return None
    
    def _save_resume_point(self, current_time, total_time):
        """Ulozi aktualni resume pozici do ChannelHistory."""
        # DEBUG
        xbmc.log(f"[DEBUG] PlaylistCountdownMonitor._save_resume_point: called with time={current_time:.1f} total={total_time:.1f}", xbmc.LOGINFO)
        meta = self._get_current_item_meta()
        if not meta:
            # DEBUG
            xbmc.log("[DEBUG] PlaylistCountdownMonitor._save_resume_point: meta is None!", xbmc.LOGINFO)
            return
        # DEBUG
        xbmc.log(f"[DEBUG] PlaylistCountdownMonitor._save_resume_point: meta={meta}", xbmc.LOGINFO)
        try:
            from wcs.ai import ChannelHistory
            ChannelHistory.set_resume_point(
                meta['channel_id'], meta['tmdb_id'],
                meta['season'], meta['episode'],
                current_time, total_time
            )
        except Exception as e:
            xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Error saving resume: {e}", level=xbmc.LOGERROR)
    
    def _mark_episode_watched(self):
        """Oznaci aktualni epizodu jako shlednutou a smaze resume."""
        meta = self._get_current_item_meta()
        if not meta:
            return
        try:
            from wcs.ai import ChannelHistory
            ChannelHistory.update_series_progress(
                meta['channel_id'], meta['tmdb_id'],
                meta['season'], meta['episode']
            )
            ChannelHistory.clear_resume_point(meta['channel_id'], meta['tmdb_id'])
            xbmc.log(
                f"[plugin.video.wcs] PlaylistCountdownMonitor: Marked as watched "
                f"S{meta['season']:02d}E{meta['episode']:02d} (channel={meta['channel_id']})",
                level=xbmc.LOGINFO
            )
        except Exception as e:
            xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Error marking watched: {e}", level=xbmc.LOGERROR)
    
    def _monitor_episode(self, total_time):
        """Sleduje jednu epizodu a zobrazi countdown pred koncem."""
        countdown_shown = False
        last_resume_save = 0
        
        xbmc.log(
            f"[plugin.video.wcs] PlaylistCountdownMonitor: Monitoring episode "
            f"(total: {total_time:.0f}s, countdown at {self._countdown_seconds}s before end)",
            level=xbmc.LOGINFO
        )
        
        while self._active:
            if not self._player.isPlaying():
                break
            
            try:
                current_time = self._player.getTime()
                remaining = total_time - current_time
            except Exception:
                break
            
            # Prubezne ukladani resume pozice kazdych 30s
            now = time.time()
            if now - last_resume_save >= 30 and current_time > 10:
                # DEBUG
                xbmc.log(f"[DEBUG] PlaylistCountdownMonitor._monitor_episode: SAVING resume at {current_time:.1f}s (30s interval)", xbmc.LOGINFO)
                self._save_resume_point(current_time, total_time)
                last_resume_save = now
            
            if remaining <= self._countdown_seconds and not countdown_shown:
                countdown_shown = True
                
                # Oznacit epizodu jako shlednutou VED countdown
                self._mark_episode_watched()
                
                # Zkontrolovat ze dalsi polozka existuje
                if not self._has_next_in_playlist():
                    xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: No next item at countdown time", level=xbmc.LOGINFO)
                    break
                
                xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Showing countdown (remaining: {remaining:.1f}s)", level=xbmc.LOGINFO)
                self._show_countdown(remaining)
                break  # Po countdown se vracime do hlavni smycky
            
            time.sleep(1)
    
    # --- Playlist info ---
    
    def _has_next_in_playlist(self):
        """Kontroluje zda existuje dalsi polozka v playlistu."""
        try:
            playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
            pos = playlist.getposition()
            return pos >= 0 and pos < playlist.size() - 1
        except Exception:
            return False
    
    def _get_next_playlist_info(self):
        """Ziska metadata dalsi polozky z Kodi playlistu.
        
        Returns:
            dict nebo None
        """
        try:
            playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
            pos = playlist.getposition()
            
            if pos < 0 or pos >= playlist.size() - 1:
                return None
            
            next_item = playlist[pos + 1]
            
            info_tag = next_item.getVideoInfoTag()
            series_name = info_tag.getTVShowTitle() or next_item.getLabel() or ''
            episode_title = info_tag.getTitle() or ''
            season = info_tag.getSeason()
            episode = info_tag.getEpisode()
            
            season_episode = ''
            if season and episode:
                season_episode = f"S{season:02d}E{episode:02d}"
            
            thumb = next_item.getArt('thumb') or ''
            
            rating_val = info_tag.getRating()
            year_val = info_tag.getYear()
            
            rating_text = ''
            rating_only = ''
            year_only = str(year_val) if year_val else ''
            
            if rating_val and rating_val > 0:
                rating_formatted = f"{rating_val:.1f}"
                rating_only = f"★ {rating_formatted}/10"
                if year_val:
                    rating_text = f"★ {rating_formatted}/10 • {year_val}"
                else:
                    rating_text = rating_only
            elif year_val:
                rating_text = str(year_val)
            
            xbmc.log(
                f"[plugin.video.wcs] PlaylistCountdownMonitor: Next item: {series_name} {season_episode} "
                f"'{episode_title}' thumb={bool(thumb)}",
                level=xbmc.LOGINFO
            )
            
            return {
                'title': episode_title,
                'season_episode': season_episode,
                'thumb': thumb,
                'rating': rating_text,
                'rating_only': rating_only,
                'year_only': year_only,
                'series_name': series_name,
            }
            
        except Exception as e:
            xbmc.log(f"[plugin.video.wcs] PlaylistCountdownMonitor: Error reading playlist: {e}", level=xbmc.LOGERROR)
            return None
    
    # --- Countdown dialog ---
    
    def _show_countdown(self, remaining):
        """Zobrazi AutoplayCountdownDialog s daty z playlistu."""
        next_info = self._get_next_playlist_info()
        if not next_info:
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: No next item info", level=xbmc.LOGINFO)
            return
        
        # Vypocitat skutecny countdown duration
        real_duration = remaining - self._trigger_seconds
        real_duration = max(3, min(real_duration, self._countdown_duration))
        
        xbmc.log(
            f"[plugin.video.wcs] PlaylistCountdownMonitor: Countdown dialog "
            f"(duration: {real_duration:.0f}s, next: {next_info['series_name']} {next_info['season_episode']})",
            level=xbmc.LOGINFO
        )
        
        # Timeout callback -- preskocit na dalsi polozku v playlistu
        def timeout_callback():
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Timeout callback - executing PlayerControl(Next)", level=xbmc.LOGINFO)
            xbmc.executebuiltin('PlayerControl(Next)')
        
        addon = xbmcaddon.Addon()
        dialog_xml = AutoplayCountdownDialog.get_dialog_xml_filename()
        dialog = AutoplayCountdownDialog(
            dialog_xml,
            addon.getAddonInfo('path'),
            show_countdown_at=int(real_duration),
            next_episode_title=next_info['title'],
            next_episode_season_episode=next_info['season_episode'],
            next_episode_thumb=next_info['thumb'],
            series_name=next_info['series_name'],
            series_tmdb_id='',
            series_fanart='',
            current_episode_plot='',
            next_episode_rating=next_info.get('rating', ''),
            next_episode_rating_only=next_info.get('rating_only', ''),
            next_episode_year_only=next_info.get('year_only', ''),
            timeout_callback=timeout_callback,
        )
        
        dialog.doModal()
        user_action = dialog.get_user_action()
        del dialog
        
        # Zpracovani akce
        if user_action == AutoplayCountdownDialog.ACTION_PLAY_NOW:
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: User chose play now", level=xbmc.LOGINFO)
            # Delay -- Kodi ignoruje akce behem zaviraci animace dialogu
            def play_next_delayed():
                time.sleep(0.5)
                xbmc.executebuiltin('PlayerControl(Next)')
            threading.Thread(target=play_next_delayed, daemon=True).start()
        elif user_action == AutoplayCountdownDialog.ACTION_CANCEL_AUTOPLAY:
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: User stopped playback", level=xbmc.LOGINFO)
            xbmc.Player().stop()
            self.deactivate()
        elif user_action == AutoplayCountdownDialog.ACTION_DISMISS:
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: User dismissed countdown, letting episode finish", level=xbmc.LOGINFO)
            # Nic nedelat -- epizoda dohraje prirozene, Kodi playlist pusti dalsi
        elif user_action == AutoplayCountdownDialog.ACTION_TIMEOUT:
            xbmc.log("[plugin.video.wcs] PlaylistCountdownMonitor: Timeout - auto playnext", level=xbmc.LOGINFO)
            # Timeout callback uz zavolal playnext


# --- Singleton factory ---

_playlist_monitor = None


def get_playlist_monitor():
    """Vrati globalni instanci PlaylistCountdownMonitor."""
    global _playlist_monitor
    if _playlist_monitor is None:
        _playlist_monitor = PlaylistCountdownMonitor()
    return _playlist_monitor
