import os
import sys
import time
import requests
import subprocess
import gzip
import io
from datetime import datetime, timedelta,timezone
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QListWidget, QWidget, QHBoxLayout,
    QListWidgetItem, QLabel, QSplitter, QFrame, QPushButton, QSlider, QSizePolicy,
    QScrollArea,QGridLayout,QFileDialog 
)
from PyQt5.QtCore import Qt, QTimer, QEvent, QTime, QUrl, QThread, pyqtSignal,QSize
from PyQt5.QtGui import QFont,QPainter, QColor
import pytz
import vlc
import xml.etree.ElementTree as ET

class VideoFrame(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setStyleSheet("background-color: black;")

class VLCPlayer:
    def __init__(self, video_frame):
        self.instance = vlc.Instance()
        self.media_player = self.instance.media_player_new()
        if sys.platform == "win32":
            self.media_player.set_hwnd(int(video_frame.winId()))
        else:
            self.media_player.set_xwindow(int(video_frame.winId()))

    def play_stream(self, url):
        media = self.instance.media_new(url)
        media.add_option(":network-caching=1000")
        self.media_player.set_media(media)
        self.media_player.play()
        self.media_player.audio_set_volume(70)

    def stop(self):
        self.media_player.stop()

    def release(self):
        self.media_player.release()
        self.instance.release()

class EPGDownloader(QThread):
    epg_downloaded = pyqtSignal(bytes)
    error_occurred = pyqtSignal(str)

    def __init__(self, epg_url):
        super().__init__()
        self.epg_url = epg_url

    def run(self):
        try:
            response = requests.get(self.epg_url, timeout=10)
            response.raise_for_status()
            self.epg_downloaded.emit(response.content)
        except Exception as e:
            self.error_occurred.emit(f"EPG download failed: {str(e)}")

class IPTVWindow(QMainWindow):
    def __init__(self, php_dir):
        self.channel_video_info = {}
        self.epg_grid_window = None

        super().__init__()
        self.setWindowTitle("IPTV Player with EPG")
        self.setGeometry(100, 100, 1280, 720)
        self.php_dir = php_dir
        self._is_fullscreen = False
        self._normal_geometry = None
        self._normal_flags = None
        self.current_channel_epg = None
        self.epg_url = None
        self.epg_data = None
        self.epg_last_update = None
        self.debug_log = []
        
        self.channel_order = []  # ✅ Prevent AttributeError


        
        self.init_ui()
        self.installEventFilter(self)

        self.epg_data = ET.Element("tv")  # placeholder in case it's accessed early
       
        
        self.download_playlist()  # This will later call load_playlist() which fills channel_order

       
        
        screen = QApplication.primaryScreen().availableGeometry()
        half_width = screen.width() // 2
        full_height = screen.height()

        self.resize(half_width, full_height - 50)  # Shrink to avoid going off screen
        self.move(half_width, 0)  # Position on right half

        # Position IPTV window in top-right quarter of screen
        screen = QApplication.primaryScreen().availableGeometry()
        half_width = screen.width() // 2
        half_height = screen.height() // 2
        self.setGeometry(half_width, 30, half_width, half_height - 30)


    def log_debug(self, message):
        """Add debug message to log and print to console"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        full_message = f"[{timestamp}] {message}"
        self.debug_log.append(full_message)
        print(full_message)
        
        if hasattr(self, 'debug_display'):
            self.debug_display.addItem(full_message)
            self.debug_display.scrollToBottom()

    def init_ui(self):
        self.splitter = QSplitter(Qt.Horizontal)
        
        # Left panel with channels
        self.channel_list = QListWidget()
        self.channel_list.itemClicked.connect(self.on_channel_clicked)
        self.splitter.addWidget(self.channel_list)
        self.channel_list.hide()  # ✅ Hide the old playlist

        # Right panel with video and EPG
        right_panel = QWidget()
        layout = QVBoxLayout(right_panel)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        self.video_container = QWidget()
        self.video_container.setStyleSheet("background-color: black;")
        self.video_container.setMinimumHeight(400)
        video_container_layout = QVBoxLayout(self.video_container)
        video_container_layout.setContentsMargins(0, 0, 0, 0)

        self.video_frame = VideoFrame(self.video_container)
        self.video_frame.mouseDoubleClickEvent = self.toggle_fullscreen
        video_container_layout.addWidget(self.video_frame)

        layout.addWidget(self.video_container, stretch=3)

        self.player = VLCPlayer(self.video_frame)

        # EPG Display with new formatting
        self.epg_display = QLabel()
        self.epg_display.setStyleSheet("""
            background-color: #222;
            color: white;
            padding: 10px;
        """)
        self.epg_display.setWordWrap(True)
        self.epg_display.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        layout.addWidget(self.epg_display, stretch=1)

        # Timers
        self.clock_timer = QTimer()
        self.clock_timer.timeout.connect(self.refresh_epg_time)
        self.clock_timer.start(1000)
       
        self.epg_update_timer = QTimer()
        self.epg_update_timer.timeout.connect(self.update_epg_data)
        self.epg_update_timer.start(3600000)  # Update EPG every hour

        # Controls
        self.controls = QWidget()
        ctl_layout = QHBoxLayout(self.controls)
        for text, slot in [("⏮️", self.play_previous_channel),
                           ("⏯️", self.toggle_play_pause),
                           ("⏭️", self.play_next_channel),
                           ("🖵", self.toggle_fullscreen),
                           ("📅", self.toggle_epg_grid),
                           ("🔄", self.update_epg_data)]:
            btn = QPushButton(text)
            btn.clicked.connect(slot)
            ctl_layout.addWidget(btn)

        self.volume_slider = QSlider(Qt.Horizontal)
        self.volume_slider.setRange(0, 200)
        self.volume_slider.setValue(70)
        self.volume_slider.setToolTip("Volume")
        self.volume_slider.valueChanged.connect(self.set_volume)
        ctl_layout.addWidget(self.volume_slider)

        self.mute_button = QPushButton("🔊")
        self.mute_button.setCheckable(True)
        self.mute_button.clicked.connect(self.toggle_mute)
        ctl_layout.addWidget(self.mute_button)

        layout.addWidget(self.controls)
        self.splitter.addWidget(right_panel)
        self.splitter.setSizes([300, 980])
        self.setCentralWidget(self.splitter)

    def set_volume(self, value):
        self.player.media_player.audio_set_volume(value)
        if value == 0:
            self.player.media_player.audio_set_mute(True)
            self.mute_button.setChecked(True)
            self.mute_button.setText("🔇")
        else:
            self.player.media_player.audio_set_mute(False)
            self.mute_button.setChecked(False)
            self.mute_button.setText("🔊")

    def toggle_mute(self):
        is_muted = self.player.media_player.audio_get_mute()
        self.player.media_player.audio_set_mute(not is_muted)
        self.mute_button.setText("🔇" if not is_muted else "🔊")
        self.mute_button.setChecked(not is_muted)

    def update_epg_info(self, name, resolution):
        if not hasattr(self, 'current_channel_name'):
            return
            
        # Create formatted EPG text
        epg_text = ""
        
        # Channel name (top left) and resolution (top right)
        channel_font = QFont()
        channel_font.setPointSize(14)
        channel_font.setBold(True)
        
        epg_text += "<table width='100%' style='font-size: 11pt; font-weight: bold;'>"
        epg_text += f"<tr><td align='left'>{name}</td><td align='right'>{resolution}</td></tr>"
        epg_text += "</table>"
        
        # Current program
        if self.current_channel_epg:
            # Title (big and bold)
            epg_text += f"<div style='font-size: 12pt; font-weight: bold; margin-top: 10px;'>"
            epg_text += f"{self.current_channel_epg.get('title', 'No Programme Information')}"
            epg_text += "</div>"
            
            # Time and duration
            if 'start' in self.current_channel_epg and 'stop' in self.current_channel_epg:
                start = self.current_channel_epg['start']
                stop = self.current_channel_epg['stop']
                try:
                    # Convert GMT times to aware datetime objects
                    gmt_start = datetime.strptime(start[:14], "%Y%m%d%H%M%S").replace(tzinfo=timezone.utc)
                    gmt_stop = datetime.strptime(stop[:14], "%Y%m%d%H%M%S").replace(tzinfo=timezone.utc)

                    # Convert to local time (Pakistan time here, change if needed)
                    local_tz = pytz.timezone("Asia/Karachi")
                    start_local = gmt_start.astimezone(local_tz)
                    stop_local = gmt_stop.astimezone(local_tz)
                    now_local = datetime.now(local_tz)

                    # Format time display
                    start_time_str = start_local.strftime("%I:%M%p").lstrip('0')
                    stop_time_str = stop_local.strftime("%I:%M%p").lstrip('0')
                    duration = int((stop_local - start_local).total_seconds() // 60)

                    # Progress percentage
                    total_seconds = (stop_local - start_local).total_seconds()
                    elapsed_seconds = (now_local - start_local).total_seconds()
                    progress_percent = max(0, min(100, int((elapsed_seconds / total_seconds) * 100))) if total_seconds > 0 else 0

                    # Build HTML progress bar
                    progress_bar_html = f"""
                    <div style='background: #444; width: 100%; height: 8px; border-radius: 4px; margin: 5px 0;'>
                    <div style='background: #0f0; width: {progress_percent}%; height: 100%; border-radius: 4px;'></div>
                    </div>
                    """

                    # Add everything to epg_text
                    epg_text += f"<div style='font-size: 10pt; margin-top: 5px;'>"
                    epg_text += f"{start_time_str} - {stop_time_str} ({duration} mins)"
                    epg_text += progress_bar_html
                    epg_text += "</div>"
                except:
                    pass
            
            # Description
            if 'desc' in self.current_channel_epg and self.current_channel_epg['desc']:
                epg_text += f"<div style='font-size: 10pt; margin-top: 5px;'>"
                epg_text += f"{self.current_channel_epg['desc']}"
                epg_text += "</div>"
            
            # Next program
            if self.next_program_epg:
                epg_text += f"<div style='font-size: 11pt; font-weight: bold; margin-top: 15px;'>"
                epg_text += f"Next: {self.next_program_epg.get('title', 'No information')}"
                epg_text += "</div>"
                
                if 'start' in self.next_program_epg and 'stop' in self.next_program_epg:
                    start = self.next_program_epg['start']
                    stop = self.next_program_epg['stop']
                    try:
                        # Convert GMT times to aware datetime objects
                        gmt_start = datetime.strptime(start[:14], "%Y%m%d%H%M%S").replace(tzinfo=timezone.utc)
                        gmt_stop = datetime.strptime(stop[:14], "%Y%m%d%H%M%S").replace(tzinfo=timezone.utc)

                        # Convert to local time (Pakistan time here, change if needed)
                        local_tz = pytz.timezone("Asia/Karachi")
                        start_local = gmt_start.astimezone(local_tz)
                        stop_local = gmt_stop.astimezone(local_tz)

                        # Format time display
                        start_time_str = start_local.strftime("%I:%M%p").lstrip('0')
                        stop_time_str = stop_local.strftime("%I:%M%p").lstrip('0')

                        epg_text += f"<div style='font-size: 10pt;'>"
                        epg_text += f"{start_time_str} - {stop_time_str}"
                        epg_text += "</div>"
                    except:
                        pass
                


                
        else:
            epg_text += "<div style='font-size: 12pt; font-weight: bold; margin-top: 10px;'>"
            epg_text += "No Programme Information"
            epg_text += "</div>"
        
        self.epg_display.setText(epg_text)

    def update_current_epg(self):
        if self.epg_data is None or not hasattr(self, 'current_channel_id'):
            return
            
        try:
            # Get current LOCAL time
            now_local = datetime.now()

            # Convert to equivalent GMT time string for matching with EPG (which is in GMT)
            now_gmt = now_local.astimezone(pytz.utc)
            current_time = now_gmt.strftime("%Y%m%d%H%M%S")
            found_current = False
            found_next = False
            
            for programme in self.epg_data.findall('.//programme'):
                channel_id = programme.get('channel')
                if channel_id != self.current_channel_id:
                    continue
                    
                start = programme.get('start')
                stop = programme.get('stop')
                
                if not found_current and start <= current_time <= stop:
                    # Current program
                    self.current_channel_epg = {
                        'title': programme.find('title').text if programme.find('title') is not None else 'No Programme Information',
                        'start': start,
                        'stop': stop,
                        'desc': programme.find('desc').text if programme.find('desc') is not None else ''
                    }
                    found_current = True
                elif found_current and not found_next and start > current_time:
                    # Next program
                    self.next_program_epg = {
                        'title': programme.find('title').text if programme.find('title') is not None else 'No information',
                        'start': start,
                        'stop': stop
                    }
                    found_next = True
                    break
            
            if not found_current:
                self.current_channel_epg = None
                self.next_program_epg = None
            elif not found_next:
                self.next_program_epg = None
                
            # Update the display
            if hasattr(self, 'current_channel_name') and hasattr(self, 'current_channel_resolution'):
                self.update_epg_info(self.current_channel_name, self.current_channel_resolution)
                                   
        except Exception as e:
            print(f"Error updating current EPG: {str(e)}")

    def refresh_epg_time(self):
        if not hasattr(self, 'current_channel_name') or not hasattr(self, 'current_channel_resolution'):
            return
            
        text = self.epg_display.text()
        parts = text.split("\n", 1)
        if len(parts) == 2:
            first_line, epg_line = parts
            if "|" in first_line:
                name_resolution = first_line.split("|")
                if len(name_resolution) >= 2:
                    name = name_resolution[0].replace("<b>", "").replace("</b>", "").strip()
                    resolution = name_resolution[1].strip()
                    self.update_epg_info(name, resolution, self.current_channel_epg)

    def update_epg_data(self):
        if not self.epg_url:
            self.log_debug("No EPG URL available")
            return
            
        self.log_debug(f"Downloading EPG data from: {self.epg_url}")
        
        self.epg_downloader = EPGDownloader(self.epg_url)
        self.epg_downloader.epg_downloaded.connect(self.process_epg_data)
        self.epg_downloader.error_occurred.connect(self.log_debug)
        self.epg_downloader.start()

    def process_epg_data(self, epg_bytes):
        try:
            # Try to decompress the data
            try:
                epg_xml = gzip.decompress(epg_bytes).decode('utf-8')
                self.log_debug("EPG data decompressed successfully")
            except:
                epg_xml = epg_bytes.decode('utf-8')
                self.log_debug("EPG data was not compressed")
            
            # Save the raw XML for debugging
            debug_epg_path = os.path.join(self.php_dir, "last_epg.xml")
            with open(debug_epg_path, "w", encoding="utf-8") as f:
                f.write(epg_xml)
            self.log_debug(f"Saved EPG data to {debug_epg_path}")
            
            # Parse the XML
            self.epg_data = ET.fromstring(epg_xml)
            self.epg_last_update = datetime.now()

            if self.epg_grid_window:
                self.epg_grid_window.update_epg(self.epg_data, self.channel_order, self.channel_video_info)
            else:
                # This should never happen, but safe fallback
                self.epg_grid_window = EPGGridWindow(self.epg_data, self.channel_order, self.channel_video_info)
                self.epg_grid_window.show()
                        
            
            
            self.log_debug(f"EPG data parsed successfully, last update: {self.epg_last_update}")
            
            # Update current channel's EPG if available
            if hasattr(self, 'current_channel_id'):
                self.update_current_epg()
                
        except Exception as e:
            self.log_debug(f"Error processing EPG data: {str(e)}")
 
    def toggle_epg_grid(self):
        if self.epg_grid_window is None:
            # Use dummy XML if EPG not ready yet
            dummy_epg = self.epg_data if self.epg_data is not None else ET.Element("tv")
            self.epg_grid_window = EPGGridWindow(dummy_epg, self.channel_order, self.channel_video_info, self)
        
        if self.epg_grid_window.isVisible():
            self.epg_grid_window.hide()
        else:
            self.epg_grid_window.show()

    def detect_resolution(self, item, base):
        w = self.player.media_player.video_get_width()
        h = self.player.media_player.video_get_height()
        fps = self.player.media_player.get_fps()
        if w > 0 and h > 0:
            label = "SD"
            if w >= 3840:
                label = "4K"
            elif w >= 1920:
                label = "FHD"
            elif w >= 1280:
                label = "HD"
            info = f"{label}-{fps:.2f} FPS"
            self.channel_video_info[base] = (label, f"{fps:.2f}")
            item.setText(f"{base} ({info})")
            self.current_channel_name = base
            self.current_channel_resolution = info
            self.update_epg_info(base, info)

            # ✅ New: update EPG Grid if open
            if self.epg_grid_window:
                self.epg_grid_window.refresh_channel_fps_info()

    def on_channel_clicked(self, item: QListWidgetItem):
        print(f"[DEBUG] on_channel_clicked called with: {item.text()}")
        url = item.data(Qt.UserRole)
        if not url:
            print("[DEBUG] URL is missing. Cannot play.")
        else:
            print(f"[DEBUG] Playing stream: {url}")

        print(f"[DEBUG] Stream URL: {item.data(Qt.UserRole)}")
        print(f"[DEBUG] Channel ID: {item.data(Qt.UserRole + 1)}")

        url = item.data(Qt.UserRole)
        self.player.play_stream(url)

        # Get channel ID from item data
        channel_id = item.data(Qt.UserRole + 1)
        if channel_id:
            self.current_channel_id = channel_id
            self.update_current_epg()

        base = item.text().split('(')[0].strip()
        item.setText(f"{base} (Loading...)")
        self.current_channel_name = base
        self.update_epg_info(base, "Loading...")

        QTimer.singleShot(4000, lambda: self.detect_resolution(item, base))

    def toggle_play_pause(self):
        mp = self.player.media_player
        if mp.is_playing():
            mp.pause()
        else:
            mp.play()

    def play_next_channel(self):
        idx = self.channel_list.currentRow()
        if idx < self.channel_list.count() - 1:
            self.channel_list.setCurrentRow(idx+1)
            self.on_channel_clicked(self.channel_list.currentItem())

    def play_previous_channel(self):
        idx = self.channel_list.currentRow()
        if idx > 0:
            self.channel_list.setCurrentRow(idx-1)
            self.on_channel_clicked(self.channel_list.currentItem())

    def toggle_fullscreen(self, event=None):
        if not self._is_fullscreen:
            self._normal_geometry = self.geometry()
            self._normal_flags = self.windowFlags()
            self.channel_list.hide()
            self.epg_display.hide()
            self.controls.hide()
            self.setWindowFlags(self._normal_flags | Qt.FramelessWindowHint)
            self.showFullScreen()
        else:
            self.setWindowFlags(self._normal_flags)
            self.showNormal()
            if self._normal_geometry:
                self.setGeometry(self._normal_geometry)
            self.channel_list.show()
            self.epg_display.show()
            self.controls.show()
        self._is_fullscreen = not self._is_fullscreen

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape and self._is_fullscreen:
            self.toggle_fullscreen()
        else:
            super().keyPressEvent(event)

    def download_playlist(self):
        try:
            os.makedirs(self.php_dir, exist_ok=True)
            url = "https://iptv.nywebforum.com/playlist.m3u"
            self.log_debug(f"Downloading playlist from: {url}")
            
            response = requests.get(url, timeout=10)
            content = response.text.replace(
                "https://iptv.nywebforum.com", "http://localhost:8888"
            )
            
            # Extract EPG URL from the first line if present
            first_line = response.text.split('\n')[0]
            if first_line.startswith("#EXTM3U") and "url-tvg=" in first_line:
                self.epg_url = first_line.split('url-tvg="')[1].split('"')[0]
                self.log_debug(f"Found EPG URL: {self.epg_url}")
                QTimer.singleShot(500, self.update_epg_data)  # Delay download to avoid blocking UI
            
            path = os.path.join(self.php_dir, "playlist.m3u")
            with open(path, "w", encoding="utf-8") as f:
                f.write(content)
            self.log_debug(f"Playlist saved to: {path}")
            self.load_playlist(path)
        except Exception as e:
            self.log_debug(f"Failed to download playlist: {e}")

    def load_playlist(self, path):
        try:
            self.log_debug(f"Loading playlist from: {path}")
            lines = open(path, encoding="utf-8").read().splitlines()
            self.channel_list.clear()
            first = None
            name = None
            channel_id = None

            self.channel_order = []  # ✅ Reset and initialize channel order

           

            for i, l in enumerate(lines):
                if l.startswith("#EXTINF:"):
                    name = l.split(',')[-1].strip()
                    if 'tvg-id="' in l:
                        channel_id = l.split('tvg-id="')[1].split('"')[0]
                        self.log_debug(f"Found channel ID: {channel_id} for {name}")
                elif l and not l.startswith("#") and name:
                    it = QListWidgetItem(f"{name} (Loading...)")
                    it.setData(Qt.UserRole, l)
                    if channel_id:
                        it.setData(Qt.UserRole + 1, channel_id)

                    self.channel_list.addItem(it)
                    self.channel_order.append((name, channel_id))  # ✅ Maintain order for EPG

                    if first is None:
                        first = it
                    name = None
                    channel_id = None

            if first:
                self.channel_list.setCurrentItem(first)
                self.log_debug(f"Found {self.channel_list.count()} channels, selecting first one")
                QTimer.singleShot(1000, lambda: self.on_channel_clicked(first))
        
            if self.epg_grid_window is None:
                self.epg_grid_window = EPGGridWindow(self.epg_data, self.channel_order, self.channel_video_info, self)
                self.epg_grid_window.show()
        
        
        except Exception as e:
            self.log_debug(f"Failed to load playlist: {e}")

    def closeEvent(self, event):
        self.log_debug("Application closing - cleaning up resources")
        
        if self.epg_grid_window and self.epg_grid_window.isVisible():
            self.epg_grid_window.close()

        # Stop timers
        self.clock_timer.stop()
        self.epg_update_timer.stop()
        
        # Stop player
        self.player.stop()
        self.player.release()
        
        # Stop any ongoing EPG download
        if hasattr(self, 'epg_downloader') and self.epg_downloader.isRunning():
            self.epg_downloader.quit()
            self.epg_downloader.wait()
        
        super().closeEvent(event)

class EPGGridWindow(QMainWindow):
    
    def __init__(self, epg_data, channel_order, channel_video_info, main_window=None):
        super().__init__()
        self.main_window = main_window
        self.setWindowTitle("Full EPG")
        self.setStyleSheet("""
            background-color: #222; 
            color: white; 
            font-size: 10pt;
            QLabel {
                padding: 5px;
            }
            QScrollArea {
                border: none;
            }
        """)

        self.epg_data = epg_data
        self.channel_order = channel_order
        self.row_height = 100
        self.timeline_slot_width = 150
        self.channel_video_info = channel_video_info

        now = datetime.now(pytz.timezone("Asia/Karachi"))
        aligned_minute = (now.minute // 30) * 30
        aligned_now = now.replace(minute=aligned_minute, second=0, microsecond=0)
        self.timeline_start = aligned_now - timedelta(days=1)
        self.timeline_end = aligned_now + timedelta(days=2)

        self.timeline = []
        t = self.timeline_start
        while t <= self.timeline_end:
            self.timeline.append(t)
            t += timedelta(minutes=30)

        self.main_widget = QWidget()
        self.setCentralWidget(self.main_widget)
        self.main_layout = QGridLayout(self.main_widget)
        self.main_layout.setSpacing(0)
        self.main_layout.setContentsMargins(0, 0, 0, 0)

        self.corner_widget = QWidget()
        self.corner_widget.setFixedSize(300, 40)
        self.corner_widget.setStyleSheet("background-color: #333;")
        self.main_layout.addWidget(self.corner_widget, 0, 0)

        self.timeline_scroll = QScrollArea()
        self.timeline_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.timeline_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.timeline_scroll.setWidgetResizable(True)

        self.timeline_widget = QWidget()
        self.timeline_widget.setStyleSheet("background-color: #333;")
        self.timeline_widget.setContentsMargins(self.timeline_slot_width // 2, 0, 0, 0)
        self.timeline_layout = QGridLayout(self.timeline_widget)
        self.timeline_layout.setSpacing(1)
        self.timeline_layout.setContentsMargins(0, 0, 0, 0)

        for i in range(len(self.timeline) - 1):
            t = self.timeline[i + 1]
            text = f"{t.strftime('%a %b %d')}<br>{t.strftime('%I:%M %p').lstrip('0')}"
            label = QLabel(text)
            label.setTextFormat(Qt.RichText)
            label.setAlignment(Qt.AlignCenter)
            label.setWordWrap(True)
            label.setFixedHeight(60)
            label.setStyleSheet("background-color: #333; font-weight: bold; color: white;")
            self.timeline_layout.addWidget(label, 0, i)

        for col in range(len(self.timeline)):
            self.timeline_layout.setColumnMinimumWidth(col, self.timeline_slot_width)

        self.timeline_scroll.setWidget(self.timeline_widget)
        self.main_layout.addWidget(self.timeline_scroll, 0, 1)

        # ✅ Channel list using QListWidget
        self.channel_list_epg = QListWidget()
        self.channel_list_epg.setVerticalScrollMode(QListWidget.ScrollPerPixel)
        self.channel_list_epg.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.channel_list_epg.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.channel_list_epg.setSpacing(0)
        self.channel_list_epg.setStyleSheet(f"""
            QListWidget {{
                background-color: #222;
                border: none;
                color: white;
                font-size: 10pt;
            }}
            QListWidget::item {{
                margin: 0px;
                padding: 0px 5px;
                border: none;
                border-bottom: 1px solid #444;
                background-color: #222;
                height: {self.row_height - 5}px;
            }}
            QListWidget::item:first {{
                border-top: 1px solid #444;  /* ✅ Draw top divider only on first item */
            }}
            QListWidget::item:hover {{
                background-color: #555;
            }}
            QListWidget::item:selected {{
                background-color: #777;
                font-weight: bold;
            }}
        """)

        self.channel_list_epg.setSpacing(1)  # ✅ Creates 1px visible gap just like EPG grid

        for row, (ch_name, ch_id) in enumerate(self.channel_order):
            res, fps = self.channel_video_info.get(ch_name, ("NA", "NA"))
            item = QListWidgetItem(f"{row + 1}. {ch_name} ({res}, {fps} FPS)")
            item.setData(Qt.UserRole, ch_name)
            item.setSizeHint(QSize(300, self.row_height-1))
            self.channel_list_epg.addItem(item)

        self.channel_list_epg.itemDoubleClicked.connect(self.on_channel_double_click)

        self.channel_container = QWidget()
        self.channel_layout = QVBoxLayout(self.channel_container)
        self.channel_layout.setContentsMargins(0, 0, 0, 0)
        self.channel_layout.setSpacing(0)
        self.channel_layout.addWidget(self.channel_list_epg)

        self.upload_button = QPushButton("⏱️ Jump to Now")
        self.upload_button.setFixedHeight(30)
        self.upload_button.setStyleSheet("""
            background-color: #555;
            color: white;
            font-size: 10px;
        """)
        self.upload_button.clicked.connect(self.scroll_program_to_current_time)
        self.channel_layout.addWidget(self.upload_button)

        self.main_layout.addWidget(self.channel_container, 1, 0)

        # Program grid (right side)
        self.program_scroll = QScrollArea()
        self.program_scroll.setWidgetResizable(True)
        self.program_widget = QWidget()
        self.program_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.epg_layout = QGridLayout(self.program_widget)
        self.epg_layout.setSpacing(1)
        self.epg_layout.setContentsMargins(0, 0, 0, 0)

        for col in range(len(self.timeline)):
            self.epg_layout.setColumnMinimumWidth(col, self.timeline_slot_width)

        for row, (ch_name, ch_id) in enumerate(self.channel_order):
            self.add_channel_epg(row, ch_name, ch_id)

        self.program_scroll.setWidget(self.program_widget)
        self.main_layout.addWidget(self.program_scroll, 1, 1)

        self.program_scroll.horizontalScrollBar().valueChanged.connect(
            self.timeline_scroll.horizontalScrollBar().setValue)
        self.timeline_scroll.horizontalScrollBar().valueChanged.connect(
            self.program_scroll.horizontalScrollBar().setValue)
        self.program_scroll.verticalScrollBar().valueChanged.connect(
            self.channel_list_epg.verticalScrollBar().setValue)
        self.channel_list_epg.verticalScrollBar().valueChanged.connect(
            self.program_scroll.verticalScrollBar().setValue)

        self.close_btn = QPushButton("❌ Close EPG")
        self.close_btn.setStyleSheet("font-size: 14px; background-color: red; color: white; padding: 5px; margin: 5px;")
        self.close_btn.clicked.connect(self.close)
        self.main_layout.addWidget(self.close_btn, 2, 0, 1, 2)

        self.main_layout.setColumnStretch(0, 0)
        self.main_layout.setColumnStretch(1, 1)
        self.main_layout.setRowStretch(0, 0)
        self.main_layout.setRowStretch(1, 1)

        self.clock_label = QLabel()
        self.clock_label.setAlignment(Qt.AlignCenter)
        self.clock_label.setFixedHeight(40)
        self.clock_label.setFixedWidth(300)
        self.clock_label.setStyleSheet("""
            background-color: #333;
            font-weight: bold;
        """)

        # Divider line below clock
        divider = QFrame()
        divider.setFixedHeight(1)
        divider.setStyleSheet("background-color: #444;")

        # Add both to existing layout (don't call setLayout again)
        corner_layout = QVBoxLayout()
        corner_layout.setContentsMargins(0, 0, 0, 0)
        corner_layout.setSpacing(0)
        corner_layout.addWidget(self.clock_label)
        corner_layout.addWidget(divider)
        self.corner_widget.setLayout(corner_layout)
        
        self.clock_label.setAlignment(Qt.AlignCenter)
      
        self.update_clock()
        self.clock_timer = QTimer(self)
        self.clock_timer.timeout.connect(self.update_clock)
        self.clock_timer.start(1000)

        QTimer.singleShot(100, self.scroll_program_to_current_time)

        self.overlay = QWidget(self.program_widget)
        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.overlay.setStyleSheet("background: transparent;")
        self.overlay.raise_()
        self.overlay.resize(self.program_widget.size())
        self.overlay.paintEvent = self.paint_now_line
        self.program_widget.resizeEvent = lambda e: self.overlay.resize(self.program_widget.size())

        self.now_line_timer = QTimer(self)
        self.now_line_timer.timeout.connect(self.overlay.update)
        self.now_line_timer.start(30000)
        QTimer.singleShot(200, self.overlay.update)

        screen = QApplication.primaryScreen().availableGeometry()
        half_width = screen.width() // 2
        full_height = screen.height()
        adjusted_height = full_height - 40  # reduce height by 60px to stay above taskbar

        self.setGeometry(0, 0, half_width, adjusted_height)
        self.resize(half_width, adjusted_height)
        self.move(0, 0)

        self.setWindowFlags(Qt.Window)
        self.show()

        assert main_window is not None, "EPGGridWindow was created without a reference to the main window"

    def on_channel_double_click(self, item):
        print(f"[DEBUG] Channel list double-click detected: {item.text()}")

        if not self.main_window:
            print("[DEBUG] main_window is None — can't proceed.")
            return

        row = self.channel_list_epg.row(item)
        print(f"[DEBUG] Channel list row: {row}")

        if 0 <= row < self.main_window.channel_list.count():
            target_item = self.main_window.channel_list.item(row)
            print(f"[DEBUG] Target main playlist item: {target_item.text()}")
            print(f"[DEBUG] Calling on_channel_clicked...")
            self.main_window.channel_list.setCurrentItem(target_item)
            self.main_window.on_channel_clicked(target_item)
        else:
            print("[DEBUG] Invalid row or target item not found.")

    def refresh_channel_fps_info(self):
        if not hasattr(self, 'channel_list_epg'):
            return

        for row, (ch_name, ch_id) in enumerate(self.channel_order):
            res, fps = self.channel_video_info.get(ch_name, ("NA", "NA"))
            item = self.channel_list_epg.item(row)
            if item:
                item.setText(f"{row + 1}. {ch_name} ({res}, {fps} FPS)")


   
    def scroll_program_to_current_time(self):
        now = datetime.now(pytz.timezone("Asia/Karachi"))
        target_time = now - timedelta(minutes=30)
        start_index = max(0, int((target_time - self.timeline_start).total_seconds() // 1800))
        x_pos = start_index * self.timeline_slot_width
        self.program_scroll.horizontalScrollBar().setValue(x_pos)

    def update_clock(self):
        now = datetime.now(pytz.timezone("Asia/Karachi"))
        self.clock_label.setText(now.strftime("%a %b %d, %I:%M:%S %p"))

    def add_channel_epg(self, row, ch_name, channel_id):
        container = QWidget()
        container.setProperty("channel_name", ch_name)  # Store channel name as property
        container.mouseDoubleClickEvent = self.handle_channel_double_click

        bg = "#333" if row % 2 else "#444"
        local_tz = pytz.timezone("Asia/Karachi")
        now = datetime.now(local_tz)
        programs = [p for p in self.epg_data.findall(".//programme") if p.get("channel") == channel_id]

        # If no EPG at all, fill entire row with repeating "No Program Info"
        if not programs:
            current_col = 0
            while current_col < len(self.timeline):
                block = min(len(self.timeline) - current_col, 4)
                filler = TimelineLabel(
                    text="No Program Information Available",
                    start_offset=current_col,
                    duration=block,
                    parent_grid=self,
                    timeline_slot_width=self.timeline_slot_width
                )
                filler.setFixedHeight(self.row_height)
                self.epg_layout.addWidget(filler, row, current_col, 1, block)
                current_col += block
            return

        # Convert all valid program blocks
        cells = []
        for prog in programs:
            try:
                start = datetime.strptime(prog.get("start")[:14], "%Y%m%d%H%M%S").replace(tzinfo=pytz.utc).astimezone(local_tz)
                stop = datetime.strptime(prog.get("stop")[:14], "%Y%m%d%H%M%S").replace(tzinfo=pytz.utc).astimezone(local_tz)

                if stop <= self.timeline_start or start >= self.timeline_end:
                    continue

                title = prog.findtext("title", "No Title")

                # Clamp within timeline
                start_offset = max(0, int((start - self.timeline_start).total_seconds() // 1800))
                end_offset = min(len(self.timeline), int((stop - self.timeline_start).total_seconds() // 1800))
                duration = max(1, end_offset - start_offset)

                cells.append((start_offset, duration, title))
            except:
                continue

        cells.sort()
        current_col = 0
        for start_offset, duration, title in cells:
            # Fill gap before program with "No Info"
            if start_offset > current_col:
                gap_duration = start_offset - current_col
                while gap_duration > 0:
                    block = min(gap_duration, 4)
                    filler = TimelineLabel(
                        text="No Program Information Available",
                        start_offset=current_col,
                        duration=block,
                        parent_grid=self,
                        timeline_slot_width=self.timeline_slot_width
                    )
                    filler.setFixedHeight(self.row_height)
                    self.epg_layout.addWidget(filler, row, current_col, 1, block)
                    current_col += block
                    gap_duration -= block

            # Actual program cell
            cell = TimelineLabel(
                text=title,
                start_offset=start_offset,
                duration=duration,
                parent_grid=self,
                timeline_slot_width=self.timeline_slot_width
            )
            cell.setFixedHeight(self.row_height)
            self.epg_layout.addWidget(cell, row, start_offset, 1, duration)
            current_col = start_offset + duration

        # Fill remaining timeline with "No Info"
        while current_col < len(self.timeline):
            block = min(len(self.timeline) - current_col, 4)
            filler = TimelineLabel(
                text="No Program Information Available",
                start_offset=current_col,
                duration=block,
                parent_grid=self,
                timeline_slot_width=self.timeline_slot_width
            )
            filler.setFixedHeight(self.row_height)
            self.epg_layout.addWidget(filler, row, current_col, 1, block)
            current_col += block

    def handle_channel_double_click(self, event):
        sender = self.sender()
        if not sender or not self.main_window:
            return

        layout = self.epg_layout
        index = layout.indexOf(sender)
        if index != -1:
            row, _, _, _ = layout.getItemPosition(index)
            if 0 <= row < self.main_window.channel_list.count():
                item = self.main_window.channel_list.item(row)
                self.main_window.channel_list.setCurrentItem(item)
                self.main_window.on_channel_clicked(item)
        event.accept()

    def update_epg(self, new_epg_data, channel_order, channel_video_info):
        self.setUpdatesEnabled(False)

        self.epg_data = new_epg_data
        self.channel_order = channel_order
        self.channel_video_info = channel_video_info

        # --- STEP 1: Build a new widget off-screen ---
        new_program_widget = QWidget()
        new_program_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        new_epg_layout = QGridLayout(new_program_widget)
        new_epg_layout.setSpacing(1)
        new_epg_layout.setContentsMargins(0, 0, 0, 0)

        # Same timeline column widths
        for col in range(len(self.timeline)):
            new_epg_layout.setColumnMinimumWidth(col, self.timeline_slot_width)

        # Add new program cells (this may take time, so nothing visible is affected)
        for row, (ch_name, ch_id) in enumerate(self.channel_order):
            self.add_channel_epg(row, ch_name, ch_id)
            local_tz = pytz.timezone("Asia/Karachi")
            programs = [p for p in self.epg_data.findall(".//programme") if p.get("channel") == ch_id]

            current_col = 0
            if not programs:
                # Fill full row with "No Program" blocks
                while current_col < len(self.timeline):
                    block = min(len(self.timeline) - current_col, 4)
                    filler = TimelineLabel(
                        text="No Program Information Available",
                        start_offset=current_col,
                        duration=block,
                        parent_grid=self,
                        timeline_slot_width=self.timeline_slot_width
                    )
                    filler.setFixedHeight(self.row_height)
                    new_epg_layout.addWidget(filler, row, current_col, 1, block)
                    current_col += block
            else:
                # Build from EPG data
                local_tz = pytz.timezone("Asia/Karachi")
                cells = []
                for prog in programs:
                    try:
                        start = datetime.strptime(prog.get("start")[:14], "%Y%m%d%H%M%S").replace(tzinfo=pytz.utc).astimezone(local_tz)
                        stop = datetime.strptime(prog.get("stop")[:14], "%Y%m%d%H%M%S").replace(tzinfo=pytz.utc).astimezone(local_tz)

                        if stop <= self.timeline_start or start >= self.timeline_end:
                            continue

                        title = prog.findtext("title", "No Title")

                        # Clamp
                        start_offset = max(0, int((start - self.timeline_start).total_seconds() // 1800))
                        end_offset = min(len(self.timeline), int((stop - self.timeline_start).total_seconds() // 1800))
                        duration = max(1, end_offset - start_offset)

                        cells.append((start_offset, duration, title))
                    except:
                        continue

                cells.sort()
                for start_offset, duration, title in cells:
                    if start_offset > current_col:
                        gap = start_offset - current_col
                        while gap > 0:
                            block = min(gap, 4)
                            filler = TimelineLabel(
                                text="No Program Information Available",
                                start_offset=current_col,
                                duration=block,
                                parent_grid=self,
                                timeline_slot_width=self.timeline_slot_width
                            )
                            filler.setFixedHeight(self.row_height)
                            new_epg_layout.addWidget(filler, row, current_col, 1, block)
                            current_col += block
                            gap -= block

                    cell = TimelineLabel(
                        text=title,
                        start_offset=start_offset,
                        duration=duration,
                        parent_grid=self,
                        timeline_slot_width=self.timeline_slot_width
                    )
                    cell.setFixedHeight(self.row_height)
                    new_epg_layout.addWidget(cell, row, start_offset, 1, duration)
                    current_col = start_offset + duration

                # Fill to end
                while current_col < len(self.timeline):
                    block = min(len(self.timeline) - current_col, 4)
                    filler = TimelineLabel(
                        text="No Program Information Available",
                        start_offset=current_col,
                        duration=block,
                        parent_grid=self,
                        timeline_slot_width=self.timeline_slot_width
                    )
                    filler.setFixedHeight(self.row_height)
                    new_epg_layout.addWidget(filler, row, current_col, 1, block)
                    current_col += block

        # --- STEP 2: Replace the widget in one go ---
        self.program_scroll.takeWidget().deleteLater()  # Remove old grid
        self.program_widget = new_program_widget
        self.epg_layout = new_epg_layout
        self.program_scroll.setWidget(self.program_widget)

        # --- STEP 3: Restore overlay on top ---
        self.overlay.setParent(self.program_widget)
        self.overlay.raise_()
        self.overlay.resize(self.program_widget.size())
        self.overlay.update()

        # --- STEP 4: Re-enable UI ---
        self.setUpdatesEnabled(True)
        self.repaint()


    def build_timeline_header(self):
        # Timeline scroll area
        self.timeline_scroll = QScrollArea()
        self.timeline_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.timeline_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.timeline_scroll.setWidgetResizable(True)

        # Timeline widget and layout
        self.timeline_widget = QWidget()
        self.timeline_widget.setStyleSheet("background-color: #333;")
        # ✅ Shift timeline labels left by 15 minutes (half slot width)
        self.timeline_widget.setContentsMargins(self.timeline_slot_width // 2, 0, 0, 0)

        self.timeline_layout = QGridLayout(self.timeline_widget)
        self.timeline_layout.setSpacing(1)
        self.timeline_layout.setContentsMargins(0, 0, 0, 0)

        # Create labels centered across dividing lines
        for i in range(len(self.timeline) - 1):
            t = self.timeline[i + 1]  # Label marks the dividing line
            label = QLabel(f"{t.strftime('%I:%M %p').lstrip('0')}")
            label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom)
            label.setFixedHeight(40)
            label.setStyleSheet("background-color: #333; font-weight: bold;")

            # Add label with column span = 2
            self.timeline_layout.addWidget(label, 0, i, 1, 2)

        # Ensure each column in timeline matches program grid column width
        for col in range(len(self.timeline)):
            self.timeline_layout.setColumnMinimumWidth(col, self.timeline_slot_width)

        self.timeline_scroll.setWidget(self.timeline_widget)
        self.main_layout.addWidget(self.timeline_scroll, 0, 1)


    def paint_now_line(self, event):
        from PyQt5.QtGui import QPainter, QPen

        painter = QPainter(self.overlay)
        pen = QPen(Qt.red, 2)
        painter.setPen(pen)

        now = datetime.now(pytz.timezone("Asia/Karachi"))

        delta = now - self.timeline_start
        total_seconds = delta.total_seconds()

        # Adjust for visual shift (timeline labels are +10 mins visually)
        shift_seconds = 600  # = 10 minutes
        adjusted_seconds = total_seconds + shift_seconds

        # Compute x offset
        block_offset = int(adjusted_seconds // 1800) * self.timeline_slot_width
        offset_within_block = ((adjusted_seconds % 1800) / 1800) * self.timeline_slot_width
        x = block_offset + offset_within_block
        # Draw red vertical line
        painter.drawLine(int(x), 0, int(x), self.overlay.height())

    def upload_playlist_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Select M3U Playlist", "", "M3U Files (*.m3u *.m3u8);;All Files (*)")
        if file_path:
            print(f"[EPGGridWindow] Uploaded playlist file: {file_path}")
            # Optionally: emit a signal or notify the main window
            
    def update_now_line_position(self):
        now = datetime.now(pytz.timezone("Asia/Karachi"))
        offset = int((now - self.timeline_start).total_seconds() // 1800)
        x = offset * self.timeline_slot_width
        self.now_line.move(x, 0)
        self.now_line.setFixedHeight(self.program_widget.height())

class TimelineLabel(QLabel):
    def __init__(self, text, start_offset, duration, parent_grid, timeline_slot_width):
        super().__init__()
        self.parent_grid = parent_grid
        self.full_text = text
        self.start_offset = start_offset
        self.duration = duration
        self.parent_grid = parent_grid
        self.timeline_slot_width = timeline_slot_width
        self.setStyleSheet("""
            border: 1px solid #555;
            color: white;
        """)
        self.setWordWrap(True)                      # Allow multi-line
        self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.setText(self.full_text)               # Let QLabel handle the text

        self.setStyleSheet("""
            border: 1px solid #555;
            color: white;
            font-size: 10pt;
            padding: 2px;  /* reduced padding */
            background-color: #333;
        """)

        self.setStyleSheet("border: 1px solid #444; padding: 2px; background-color: #333;")
        
    def mouseDoubleClickEvent(self, event):
        print("[DEBUG] Program grid double-click detected")

        if self.parent_grid and self.parent_grid.main_window:
            layout = self.parent_grid.epg_layout
            index = layout.indexOf(self)
            if index != -1:
                row, _, _, _ = layout.getItemPosition(index)
                print(f"[DEBUG] EPG row double-clicked: {row}")
                if 0 <= row < self.parent_grid.main_window.channel_list.count():
                    item = self.parent_grid.main_window.channel_list.item(row)
                    print(f"[DEBUG] Attempting to play channel from program grid: {item.text()}")
                    self.parent_grid.main_window.channel_list.setCurrentItem(item)
                    self.parent_grid.main_window.on_channel_clicked(item)
        event.accept()

 
def start_php_server(php_dir, port=8888):
    try:
        os.chdir(php_dir)
        return subprocess.Popen([
            "php", "-S", f"localhost:{port}"],
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
            creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
        )
    except Exception as e:
        print(f"Failed to start PHP server: {e}")
        return None


if __name__ == "__main__":
    php_dir = os.path.join(os.path.dirname(__file__), "php_files")
    os.makedirs(php_dir, exist_ok=True)
    php_proc = start_php_server(php_dir)
    app = QApplication(sys.argv)
    window = IPTVWindow(php_dir)
    window.show()
    exit_code = app.exec_()
    
    # Ensure clean exit
    if php_proc:
        php_proc.terminate()
        php_proc.wait(2)
    sys.exit(exit_code)