# youtube_guide.py
import json
import random
import xbmc
import xbmcgui
import xbmcgui
import xbmcaddon
import urllib.request
import urllib.parse
import os
import threading
from datetime import datetime, timedelta
from tools import debug_log

# Global variables for YouTube playlist management
_youtube_playback_cancelled = False  # Module-level global for cancellation
_current_youtube_playlist = []
_cancellation_lock = threading.Lock()
_cancellation_event = threading.Event()  # More reliable than flag checking

def cancel_youtube_playback():
    """Cancel any running YouTube sequential playback and stop all YouTube content"""
    global _youtube_playback_cancelled
    # File-based cancellation flag for cross-process visibility
    cancel_flag_path = os.path.join(os.path.dirname(__file__), 'youtube_cancel.flag')
    try:
        with open(cancel_flag_path, 'w') as f:
            f.write('cancelled')
    except Exception as e:
        print(f"DEBUG: Could not create cancel flag file: {e}")
    debug_log(f"DEBUG: cancel_youtube_playback called - setting flag from {_youtube_playback_cancelled} to True", xbmc.LOGINFO)
    _youtube_playback_cancelled = True
    try:
        with _cancellation_lock:
            debug_log(f"DEBUG: cancel_youtube_playback (lock) - flag now {_youtube_playback_cancelled}", xbmc.LOGINFO)
        _cancellation_event.set()
        debug_log(f"DEBUG: cancel_youtube_playback - event set: {_cancellation_event.is_set()}", xbmc.LOGINFO)
        player = xbmc.Player()
        if player.isPlaying():
            try:
                playing_file = player.getPlayingFile()
                debug_log(f"DEBUG: Currently playing: {playing_file}", xbmc.LOGINFO)
                player.stop()
                for i in range(30):
                    if not player.isPlaying():
                        break
                    xbmc.sleep(100)
                debug_log(f"DEBUG: Player stopped, isPlaying: {player.isPlaying()}", xbmc.LOGINFO)
            except Exception as e:
                debug_log(f"DEBUG: Error getting/stopping playing file: {str(e)}", xbmc.LOGINFO)
                try:
                    player.stop()
                except:
                    pass
        try:
            xbmc.executebuiltin('Dialog.Close(busydialog)')
            xbmc.executebuiltin('Dialog.Close(all)')
            xbmc.sleep(500)
        except:
            pass
    except Exception as e:
        print(f"DEBUG: Error in cancel_youtube_playback: {str(e)}")

def reset_youtube_playback_state():
    """Reset the YouTube playback cancellation state"""
    global _youtube_playback_cancelled
    # Remove file-based cancel flag
    cancel_flag_path = os.path.join(os.path.dirname(__file__), 'youtube_cancel.flag')
    try:
        if os.path.exists(cancel_flag_path):
            os.remove(cancel_flag_path)
    except Exception as e:
        print(f"DEBUG: Could not remove cancel flag file: {e}")
    with _cancellation_lock:
        _youtube_playback_cancelled = False
        _cancellation_event.clear()
        debug_log("DEBUG: YouTube playback state reset via reset_youtube_playback_state()", xbmc.LOGINFO)

def get_youtube_sleep_time():
    """Get the sleep time setting from addon settings"""
    try:
        # import xbmcaddon
        addon = xbmcaddon.Addon()
        sleep_time = int(addon.getSetting('youtube_sleep_time'))
        return sleep_time
    except:
        return 5  # Default fallback

def fetch_links(url):
    """Fetch directory links from URL, returns (dirs, files) tuples"""
    try:
        debug_log(f"DEBUG: fetch_links called with URL: {url}", xbmc.LOGINFO)
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=10) as resp:
            content = resp.read().decode('utf-8', errors='ignore')
        
        import re
        # Find directory links (ending with /)
        dir_links = re.findall(r'href="([^"]+)/"[^>]*>([^<]+)', content)
        # Find file links (ending with .json)
        file_links = re.findall(r'href="([^"]+\.json)"[^>]*>([^<]+)', content)
        
        # Filter out navigation links
        dirs = [(href, name) for href, name in dir_links if href not in ['.', '..']]
        files = [(href, name) for href, name in file_links]
        
        return dirs, files
    except Exception as e:
        debug_log(f"DEBUG: Failed to fetch links from {url}: {str(e)}", xbmc.LOGERROR)
        return [], []

def fetch_json_content(json_url):
    """Fetch JSON from URL as a dict (in-memory, no file) with progress dialog"""
    try:
        # import xbmcgui
        dialog = xbmcgui.DialogProgress()
        dialog.create("YouTube Guide", f"Fetching playlist/guide...\n{json_url}")
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
        req = urllib.request.Request(json_url, headers=headers)
        with urllib.request.urlopen(req, timeout=10) as resp:
            dialog.update(25, f"Downloading content...\n{json_url}")
            data = resp.read().decode('utf-8')
        dialog.update(50, "Parsing JSON content...")
        parsed = json.loads(data)
        dialog.update(100, "Done!")
        dialog.close()
        return parsed  # Return as dict for easy parsing
    except Exception as e:
        debug_log(f"DEBUG: Failed to fetch/parse {json_url}: {str(e)}", xbmc.LOGERROR)
        try:
            dialog.close()
        except:
            pass
        return None

def youtube_json_to_epggrid(data_source, source_name=None):
    """
    Parse YouTube JSON: now accepts path (str) or dict/content (str/dict)
    Shows progress dialog during parsing.
    """
    debug_log(f"DEBUG: youtube_json_to_epggrid called with data_source type: {type(data_source)}, value: {str(data_source)[:100]}...", xbmc.LOGINFO)
    # import xbmcgui
    dialog = xbmcgui.DialogProgress()
    dialog.create("YouTube Guide", "Parsing playlist...")
    try:
        if isinstance(data_source, str) and not data_source.startswith(('http://', 'https://')) and os.path.exists(data_source):
            dialog.update(10, f"Loading from local file: {data_source}")
            with open(data_source, 'r', encoding='utf-8') as f:
                data = json.load(f)
            channel_name = os.path.splitext(os.path.basename(data_source))[0]
        elif isinstance(data_source, dict):
            dialog.update(10, "Parsing from dict content...")
            data = data_source
            channel_name = source_name or data.get('title', 'Unknown Channel')
        elif isinstance(data_source, str) and (data_source.startswith(('http://', 'https://')) or not os.path.exists(data_source)):
            if data_source.startswith(('http://', 'https://')):
                dialog.close()
                debug_log(f"ERROR: URL incorrectly passed to youtube_json_to_epggrid: {data_source}", xbmc.LOGERROR)
                debug_log(f"ERROR: This is likely the source of the file path error!", xbmc.LOGERROR)
                raise ValueError("URLs should not be passed directly to youtube_json_to_epggrid. Use fetch_json_content first.")
            else:
                dialog.update(10, "Parsing from raw JSON string...")
                data = json.loads(data_source)
                channel_name = source_name or 'Unknown Channel'
        else:
            dialog.close()
            raise ValueError("Invalid data_source: must be file path, dict, or JSON string")
        tvg_id = channel_name.lower().replace(' ', '_').replace('-', '_')
        logo = data['items'][0].get('thumbnail', '') if data.get('items') else ''
        channel = {
            'name': channel_name,
            'tvg_id': tvg_id,
            'logo': logo,
            'group': 'YouTube'
        }
        programs = []
        start_time = datetime.now()
        sleep_time = get_youtube_sleep_time()
        items = data.get('items', [])
        random.shuffle(items)
        total = len(items)
        for idx, item in enumerate(items):
            dialog.update(10 + int((idx / max(1, total)) * 80), f"Processing entry {idx+1}/{total}: {item.get('title','')}")
            duration_str = item.get('duration', '0:00')
            try:
                duration_parts = duration_str.split(':')
                if len(duration_parts) == 2:
                    duration_seconds = int(duration_parts[0]) * 60 + int(duration_parts[1])
                elif len(duration_parts) == 3:
                    duration_seconds = int(duration_parts[0]) * 3600 + int(duration_parts[1]) * 60 + int(duration_parts[2])
                else:
                    duration_seconds = 300  # Default to 5 minutes
            except Exception:
                duration_seconds = 300  # Default to 5 minutes if invalid
            end_time = start_time + timedelta(seconds=duration_seconds)
            programs.append({
                'title': item['title'],
                'channel': tvg_id,
                'desc': item.get('summary', ''),
                'start': start_time,
                'stop': end_time,  # EPGGrid expects 'stop' not 'end'
                'link': item['link'],
                'logo': item.get('thumbnail', ''),
            })
            start_time = end_time + timedelta(seconds=sleep_time)
        dialog.update(100, "Preparing guide window...")
        dialog.close()
        return [channel], programs
    except Exception as e:
        debug_log(f"DEBUG: Error parsing YouTube JSON: {str(e)}", xbmc.LOGERROR)
        try:
            dialog.close()
        except:
            pass
        raise
def play_youtube_programs(programs, delay=None, is_new_channel=True):
    """
    YouTube playlist playback with improved conflict handling
    """
    global _youtube_playback_cancelled
    # Declare global variable first
    class PlaybackCancelled(Exception): pass
    if delay is None:
        delay = get_youtube_sleep_time()
        
    # import xbmc
    # import xbmcgui
    import re
    import random
    
    # Generate a unique thread ID for this playback session
    import time
    thread_id = int(time.time() * 1000) % 100000  # Last 5 digits of timestamp
    debug_log(f"DEBUG: Sequential thread {thread_id} starting", xbmc.LOGINFO)
    
    # Only reset cancellation flag for genuinely new channel selections
    # Don't reset if this is a continuation of existing playlist
    if is_new_channel:
        with _cancellation_lock:
            debug_log(f"DEBUG: Thread {thread_id} - Reset flag from {_youtube_playback_cancelled} to False", xbmc.LOGINFO)
            _youtube_playback_cancelled = False
            _cancellation_event.clear()  # Clear the event for new channel
            debug_log(f"DEBUG: Thread {thread_id} - Starting new channel - reset cancellation flag and event", xbmc.LOGINFO)
    else:
        with _cancellation_lock:
            debug_log(f"DEBUG: Thread {thread_id} - Continuing playlist - cancellation flag is {_youtube_playback_cancelled}", xbmc.LOGINFO)
    
    debug_log(f"DEBUG: play_youtube_programs started with {len(programs)} programs", xbmc.LOGINFO)
    
    # Force stop any existing playback more aggressively
    try:
        player = xbmc.Player()
        if player.isPlaying():
            debug_log("DEBUG: Stopping existing player before starting YouTube playback", xbmc.LOGINFO)
            player.stop()
            xbmc.sleep(2000)  # Longer wait for clean stop
    except Exception as e:
        debug_log(f"DEBUG: Error checking/stopping existing player: {str(e)}", xbmc.LOGINFO)
    
    if not programs:
        xbmcgui.Dialog().ok("Error", "No programs to play")
        return
    
    # Show playback options to user
    dialog = xbmcgui.Dialog()
    options = ["Play One Random Video", "Play All Videos Sequentially"]
    play_mode = dialog.select("YouTube Playback Mode", options)
    
    if play_mode == -1:  # User cancelled
        return
    elif play_mode == 0:  # Single video
        prog = random.choice(programs)
        url = prog['link']
        
        # Convert YouTube URL to plugin format
        match = re.search(r"(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})", url)
        if match:
            youtube_url = f"plugin://plugin.video.youtube/play/?video_id={match.group(1)}"
        else:
            youtube_url = url
        
        listitem = xbmcgui.ListItem(label=prog['title'])
        if prog.get('logo'):
            listitem.setArt({'thumb': prog['logo']})
        
        player = xbmc.Player()
        player.play(youtube_url, listitem)
        xbmcgui.Dialog().notification("YouTube Player", f"Playing: {prog['title']}", xbmcgui.NOTIFICATION_INFO, 3000)
        
    else:  # Sequential playback
        # Use a much simpler approach for sequential playback
        # Cancellation flag already reset at function start
        total_videos = len(programs)
        dialog = xbmcgui.DialogProgress()
        dialog.create("YouTube Playlist", f"Playing {total_videos} videos...")
        
        player = xbmc.Player()
        
        cancel_flag_path = os.path.join(os.path.dirname(__file__), 'youtube_cancel.flag')
        # Remove cancel flag before starting playback
        try:
            if os.path.exists(cancel_flag_path):
                os.remove(cancel_flag_path)
        except Exception as e:
            debug_log(f"DEBUG: Could not remove cancel flag file before playback: {e}", xbmc.LOGINFO)
        try:
            for i, prog in enumerate(programs):
                # File-based cancellation check
                if os.path.exists(cancel_flag_path):
                    debug_log(f"DEBUG: File-based cancel flag detected, exiting playback loop", xbmc.LOGINFO)
                    try:
                        dialog.close()
                    except:
                        pass
                    return
                # Immediate cancellation check at loop start
                debug_log(f"DEBUG: playback loop event check at loop start: {_cancellation_event.is_set()}", xbmc.LOGINFO)
                if _cancellation_event.is_set():
                    debug_log("DEBUG: Cancellation detected at loop start, exiting before playing next video", xbmc.LOGINFO)
                    try:
                        dialog.close()
                    except:
                        pass
                    raise PlaybackCancelled()
                # Check for cancellation using both flag and event
                with _cancellation_lock:
                    debug_log(f"DEBUG: playback loop cancellation check - global flag={_youtube_playback_cancelled}", xbmc.LOGINFO)
                event_set = _cancellation_event.is_set()
                if _youtube_playback_cancelled or event_set or dialog.iscanceled():
                    debug_log(f"DEBUG: Thread {thread_id} - YouTube playback cancelled - exiting function (global flag={_youtube_playback_cancelled}, event={event_set})", xbmc.LOGINFO)
                    try:
                        dialog.close()
                    except:
                        pass
                    return
                # Update progress
                dialog.update(int((i / total_videos) * 100), f"Playing: {prog['title']} ({i+1}/{total_videos})")
                url = prog['link']
                match = re.search(r"(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})", url)
                if match:
                    youtube_url = f"plugin://plugin.video.youtube/play/?video_id={match.group(1)}"
                else:
                    youtube_url = url
                listitem = xbmcgui.ListItem(label=prog['title'])
                if prog.get('logo'):
                    listitem.setArt({'thumb': prog['logo']})
                # Play the video
                # Check cancellation before starting next video
                with _cancellation_lock:
                    cancelled = _youtube_playback_cancelled
                event_set = _cancellation_event.is_set()
                if cancelled or event_set:
                    debug_log(f"DEBUG: Thread {thread_id} - YouTube playback cancelled before starting next video (flag={cancelled}, event={event_set})", xbmc.LOGINFO)
                    try:
                        dialog.close()
                    except:
                        pass
                    return
                # Final cancellation check before starting next video
                if _youtube_playback_cancelled or _cancellation_event.is_set():
                    debug_log(f"DEBUG: Cancellation detected before player.play, skipping video {prog['title']}", xbmc.LOGINFO)
                    try:
                        dialog.close()
                    except:
                        pass
                    return
                player.play(youtube_url, listitem)
                # Wait for video to start
                start_wait = 0
                while not player.isPlaying() and start_wait < 10:
                    debug_log(f"DEBUG: playback loop event check while waiting for start: {_cancellation_event.is_set()}", xbmc.LOGINFO)
                    if _cancellation_event.is_set() or dialog.iscanceled():
                        debug_log("DEBUG: YouTube playback cancelled while waiting for start", xbmc.LOGINFO)
                        try:
                            dialog.close()
                        except:
                            pass
                        raise PlaybackCancelled()
                    xbmc.sleep(1000)
                    start_wait += 1
                if not player.isPlaying():
                    debug_log(f"Failed to start {prog['title']}, skipping", xbmc.LOGWARNING)
                    continue
                # Close dialog after first video starts so user can watch
                if i == 0:
                    dialog.close()
                    xbmcgui.Dialog().notification("YouTube Playlist", f"Playing {total_videos} videos with {delay}s delays", xbmcgui.NOTIFICATION_INFO, 5000)
                # Wait for video to finish
                while player.isPlaying():
                    # Check both flag and event for cancellation
                    with _cancellation_lock:
                        cancelled = _youtube_playback_cancelled
                    event_set = _cancellation_event.is_set()
                    if cancelled or event_set:
                        debug_log(f"DEBUG: Thread {thread_id} - YouTube playback cancelled during video - stopping and exiting (flag={cancelled}, event={event_set})", xbmc.LOGINFO)
                        player.stop()
                        # Immediately exit loop after stop
                        break
                    debug_log(f"DEBUG: playback loop event check during video sleep: {_cancellation_event.is_set()}", xbmc.LOGINFO)
                    if _cancellation_event.is_set():
                        debug_log(f"DEBUG: YouTube playback cancelled during video sleep", xbmc.LOGINFO)
                        player.stop()
                        try:
                            dialog.close()
                        except:
                            pass
                        raise PlaybackCancelled()
                    xbmc.sleep(250)  # Check every 250ms for faster response
                    # Add periodic logging to track playback state - log every 5 seconds to avoid spam
                    import time
                    current_time = time.time()
                    if not hasattr(play_youtube_programs, '_last_log_time'):
                        play_youtube_programs._last_log_time = 0
                    if current_time - play_youtube_programs._last_log_time >= 5:  # Log every 5 seconds
                        with _cancellation_lock:
                            flag_state = _youtube_playback_cancelled
                        event_state = _cancellation_event.is_set()
                        debug_log(f"DEBUG: Thread {thread_id} - Video playing, cancellation flag = {flag_state}, event = {event_state}", xbmc.LOGINFO)
                        play_youtube_programs._last_log_time = current_time
            # After breaking out of for loop
            # Remove cancel flag file after playback ends or is cancelled
            try:
                if os.path.exists(cancel_flag_path):
                    os.remove(cancel_flag_path)
            except Exception as e:
                debug_log(f"DEBUG: Could not remove cancel flag file: {e}", xbmc.LOGINFO)
            if not _youtube_playback_cancelled:
                xbmcgui.Dialog().notification("YouTube Playlist", "Finished playing all videos", xbmcgui.NOTIFICATION_INFO, 3000)
        except PlaybackCancelled:
            debug_log("DEBUG: PlaybackCancelled exception caught, exiting sequential playback loop", xbmc.LOGINFO)
            try:
                dialog.close()
            except:
                pass
            return
        except Exception as e:
            debug_log(f"DEBUG: Error in play_youtube_programs: {str(e)}", xbmc.LOGERROR)
            xbmcgui.Dialog().ok("Playback Error", f"Failed to start YouTube playback: {str(e)}")

def export_epg_xml(programs, xml_path):
    from xml.etree.ElementTree import Element, SubElement, ElementTree
    tv = Element('tv')
    for prog in programs:
        channel_elem = SubElement(tv, 'channel', id=prog['channel'])
        SubElement(channel_elem, 'display-name').text = prog['title']
        SubElement(channel_elem, 'icon', src=prog['logo'])
        prog_elem = SubElement(tv, 'programme', start=prog['start'].strftime('%Y%m%d%H%M%S'), stop=prog['end'].strftime('%Y%m%d%H%M%S'), channel=prog['channel'])
        SubElement(prog_elem, 'title').text = prog['title']
        SubElement(prog_elem, 'desc').text = prog['desc']
        SubElement(prog_elem, 'url').text = prog['link']
    tree = ElementTree(tv)
    tree.write(xml_path, encoding='utf-8', xml_declaration=True)

def export_m3u8(programs, m3u_path):
    with open(m3u_path, 'w', encoding='utf-8') as f:
        f.write('#EXTM3U\n')
        for prog in programs:
            f.write(f'#EXTINF:-1 tvg-id="{prog["channel"]}" tvg-name="{prog["title"]}" tvg-logo="{prog["logo"]}",{prog["title"]}\n')
            f.write(f'{prog["link"]}\n')