import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
from datetime import datetime, timedelta
import json
import urllib.parse
from typing import Dict, List, Any

try:
    from themes import apply_theme_to_window
except ImportError:
    def apply_theme_to_window(window):
        pass  # No theming available

class SearchWindow(xbmcgui.WindowXML):
    def __init__(self, xml_file: str, addon_path: str, channels: List[Dict[str, Any]] = None, programs: List[Dict[str, Any]] = None, return_selection: bool = False):
        super().__init__(xml_file, addon_path, "default")
        self.channels = channels or []
        self.programs = programs or []
        self.list_control = None
        self.search_term = ""
        self.current_time = datetime.now().astimezone()
        self.return_selection = return_selection  # If True, return selected stream instead of playing
        self.selected_stream = None  # To store selected stream when return_selection is True
        
        # Get addon for settings
        self.addon = xbmcaddon.Addon()
        
        # Paths for saved searches
        self.profile_path = xbmcvfs.translatePath(self.addon.getAddonInfo('profile'))
        self.saved_searches_path = self.profile_path + "saved_searches.json"
        self.search_history_path = self.profile_path + "search_history.json"
        self.max_history = 10  # Keep last 10 searches
        
        # Get unique categories from channels and programs
        self.categories = self._get_categories()
    
    def _get_categories(self) -> List[str]:
        """Extract unique categories from channels and programs"""
        categories = set()
        for channel in self.channels:
            group = channel.get('group', '')
            if group:
                categories.add(group)
        for program in self.programs:
            cat = program.get('category', '')
            if cat:
                if isinstance(cat, list):
                    for c in cat:
                        categories.add(c)
                else:
                    categories.add(cat)
        return sorted(list(categories))
    
    def _get_saved_searches(self) -> List[Dict]:
        """Load saved searches from file"""
        try:
            if xbmcvfs.exists(self.saved_searches_path):
                with xbmcvfs.File(self.saved_searches_path, 'r') as f:
                    return json.loads(f.read())
        except Exception:
            pass
        return []
    
    def _save_saved_searches(self, searches: List[Dict]) -> bool:
        """Save searches to file"""
        try:
            with xbmcvfs.File(self.saved_searches_path, 'w') as f:
                f.write(json.dumps(searches, indent=2))
            return True
        except Exception:
            return False
    
    def _save_current_search(self):
        """Save the current search term and filters"""
        dialog = xbmcgui.Dialog()
        
        # Ask for a name for this saved search
        name = dialog.input("Enter a name for this saved search", defaultt=self.search_term)
        if not name:
            return
        
        saved_searches = self._get_saved_searches()
        
        # Check for duplicate name
        for s in saved_searches:
            if s.get('name') == name:
                dialog.ok("Error", "A saved search with this name already exists.")
                return
        
        # Get current filter selections
        time_filter = self.getProperty('time_filter') if hasattr(self, 'getProperty') else 'all'
        category_filter = self.getProperty('category_filter') if hasattr(self, 'getProperty') else 'all'
        
        saved_search = {
            'name': name,
            'search_term': self.search_term,
            'time_filter': time_filter,
            'category_filter': category_filter
        }
        
        saved_searches.append(saved_search)
        
        if self._save_saved_searches(saved_searches):
            dialog.notification("Saved", f"Search '{name}' saved", xbmcgui.NOTIFICATION_INFO, 2000)
        else:
            dialog.ok("Error", "Failed to save search.")

    def _get_search_history(self) -> List[str]:
        """Load search history from file"""
        try:
            if xbmcvfs.exists(self.search_history_path):
                with xbmcvfs.File(self.search_history_path, 'r') as f:
                    return json.loads(f.read())
        except Exception:
            pass
        return []

    def _add_to_history(self, search_term: str):
        """Add search term to history"""
        if not search_term:
            return
        
        history = self._get_search_history()
        
        # Remove if already exists (to move to top)
        if search_term in history:
            history.remove(search_term)
        
        # Add to beginning
        history.insert(0, search_term)
        
        # Keep only last N searches
        history = history[:self.max_history]
        
        try:
            with xbmcvfs.File(self.search_history_path, 'w') as f:
                f.write(json.dumps(history))
        except Exception:
            pass

    def _clear_history(self):
        """Clear search history"""
        dialog = xbmcgui.Dialog()
        if dialog.yesno("Clear History", "Are you sure you want to clear search history?"):
            try:
                if xbmcvfs.exists(self.search_history_path):
                    xbmcvfs.delete(self.search_history_path)
                dialog.notification("Cleared", "Search history cleared", xbmcgui.NOTIFICATION_INFO, 2000)
            except Exception:
                pass
    
    def _show_history_menu(self):
        """Show full search history with options to use or clear"""
        dialog = xbmcgui.Dialog()
        history = self._get_search_history()
        
        if not history:
            dialog.ok("Search History", "No search history yet.")
            return
        
        while True:
            options = [h for h in history]
            options.append("[Clear All History]")
            options.append("[Done]")
            
            choice = dialog.select("Search History", options)
            
            if choice == -1 or choice == len(options) - 1:
                break
            
            if choice == len(options) - 2:
                # Clear all history
                self._clear_history()
                break
            
            # Use this search term
            self.search_term = history[choice]
            self.setProperty('time_filter', 'all')
            self.setProperty('category_filter', 'all')
            self._do_search()
            break
    
    def _show_saved_searches_menu(self):
        """Show menu to load or manage saved searches"""
        dialog = xbmcgui.Dialog()
        saved_searches = self._get_saved_searches()
        
        if not saved_searches:
            dialog.ok("Saved Searches", "No saved searches yet.")
            return
        
        while True:
            options = [s['name'] for s in saved_searches]
            options.append("[Delete a Saved Search]")
            options.append("[Done]")
            
            choice = dialog.select("Saved Searches", options)
            
            if choice == -1 or choice == len(options) - 1:
                break
            
            if choice == len(options) - 2:
                # Delete a saved search
                self._delete_saved_search(saved_searches)
                saved_searches = self._get_saved_searches()
                continue
            
            # Load the saved search
            saved = saved_searches[choice]
            self.search_term = saved.get('search_term', '')
            self.setProperty('time_filter', saved.get('time_filter', 'all'))
            self.setProperty('category_filter', saved.get('category_filter', 'all'))
            self._do_search()
            break
    
    def _delete_saved_search(self, saved_searches: List[Dict]):
        """Delete a saved search"""
        dialog = xbmcgui.Dialog()
        options = [s['name'] for s in saved_searches]
        options.append("Cancel")
        
        choice = dialog.select("Delete Saved Search", options)
        
        if choice == -1 or choice == len(options) - 1:
            return
        
        # Remove the search
        deleted_name = saved_searches.pop(choice)['name']
        
        if self._save_saved_searches(saved_searches):
            dialog.notification("Deleted", f"Search '{deleted_name}' deleted", xbmcgui.NOTIFICATION_INFO, 2000)
    
    def _get_time_filter_options(self) -> List[str]:
        """Get time filter options"""
        return ['all', 'now', 'later_today', 'tomorrow', 'this_week']
    
    def _get_time_filter_display(self, filter_key: str) -> str:
        """Get display name for time filter"""
        display = {
            'all': 'All Times',
            'now': 'Now Playing',
            'later_today': 'Later Today',
            'tomorrow': 'Tomorrow',
            'this_week': 'This Week'
        }
        return display.get(filter_key, filter_key)
    
    def _get_category_filter_options(self) -> List[str]:
        """Get category filter options"""
        return ['all'] + self.categories
    
    def _filter_by_time(self, program: Dict, time_filter: str) -> bool:
        """Check if program matches time filter"""
        if time_filter == 'all':
            return True
        
        start = program.get('start')
        if not start:
            return True  # Include if no start time
        
        if time_filter == 'now':
            # Currently playing (between start and stop)
            return start <= self.current_time <= program.get('stop', start + timedelta(hours=1))
        
        elif time_filter == 'later_today':
            # Later today (after now but before midnight)
            end_today = self.current_time.replace(hour=23, minute=59, second=59)
            return start > self.current_time and start <= end_today
        
        elif time_filter == 'tomorrow':
            # Tomorrow
            tomorrow = (self.current_time + timedelta(days=1)).replace(hour=0, minute=0, second=0)
            tomorrow_end = (tomorrow + timedelta(days=1))
            return start >= tomorrow and start < tomorrow_end
        
        elif time_filter == 'this_week':
            # This week (next 7 days)
            week_end = self.current_time + timedelta(days=7)
            return start >= self.current_time and start <= week_end
        
        return True
    
    def _filter_by_category(self, program: Dict, channel: Dict, category_filter: str) -> bool:
        """Check if program/channel matches category filter"""
        if category_filter == 'all':
            return True
        
        # Check program category
        program_cat = program.get('category', '')
        if program_cat:
            if isinstance(program_cat, list):
                if category_filter in program_cat:
                    return True
            elif program_cat.lower() == category_filter.lower():
                return True
        
        # Check channel group
        channel_group = channel.get('group', '')
        if channel_group and channel_group.lower() == category_filter.lower():
            return True
        
        return category_filter == 'all'
    
    def _show_filter_menu(self):
        """Show filter selection menu"""
        dialog = xbmcgui.Dialog()
        
        # Time filter
        time_options = [
            "All Times",
            "Now Playing", 
            "Later Today",
            "Tomorrow",
            "This Week"
        ]
        
        current_time_filter = self.getProperty('time_filter') if hasattr(self, 'getProperty') else 'all'
        time_choice = dialog.select("Filter by Time", time_options)
        
        if time_choice == -1:
            return
        
        time_filters = ['all', 'now', 'later_today', 'tomorrow', 'this_week']
        time_filter = time_filters[time_choice]
        self.setProperty('time_filter', time_filter)
        
        # Category filter
        cat_options = ["All Categories"] + self.categories
        current_cat_filter = self.getProperty('category_filter') if hasattr(self, 'getProperty') else 'all'
        
        cat_choice = dialog.select("Filter by Category", cat_options)
        
        if cat_choice == -1:
            return
        
        cat_filters = ['all'] + self.categories
        category_filter = cat_filters[cat_choice]
        self.setProperty('category_filter', category_filter)
        
        # Re-run search with filters
        self._do_search()
    
    def _do_search(self):
        """Perform the search with current filters"""
        # Add to search history
        if self.search_term:
            self._add_to_history(self.search_term)
        
        self.list_control.reset()
        
        search_term = self.search_term.lower().strip()
        time_filter = self.getProperty('time_filter') if hasattr(self, 'getProperty') else 'all'
        category_filter = self.getProperty('category_filter') if hasattr(self, 'getProperty') else 'all'
        
        # Build filter info string
        filters_applied = []
        if time_filter != 'all':
            filters_applied.append(f"Time: {self._get_time_filter_display(time_filter)}")
        if category_filter != 'all':
            filters_applied.append(f"Category: {category_filter}")
        
        filter_info = f" [COLOR grey]({', '.join(filters_applied)})[/COLOR]" if filters_applied else ""
        
        # Header with search term and filters
        header = xbmcgui.ListItem(f'[COLOR gold]Search: "{self.search_term}"{filter_info}[/COLOR]')
        self.list_control.addItem(header)
        
        found_channels = []
        found_programs = []
        
        # Search channels
        for channel in self.channels:
            if search_term and search_term in channel['name'].lower():
                found_channels.append(channel)
        
        # Search programs with filters
        for program in self.programs:
            # Apply time filter
            if not self._filter_by_time(program, time_filter):
                continue
            
            # Check if program matches search term
            if search_term and search_term not in program.get('title', '').lower():
                continue
            
            # Find matching channel
            channel = next((c for c in self.channels if c.get('tvg_id') == program.get('channel')), None)
            if not channel:
                continue
            
            # Apply category filter
            if not self._filter_by_category(program, channel, category_filter):
                continue
            
            found_programs.append({
                'program': program,
                'channel': channel
            })
        
        # Sort programs by start time
        found_programs.sort(key=lambda x: x['program'].get('start', datetime.now()))
        
        # Add results
        if found_channels:
            section = xbmcgui.ListItem(f'[COLOR yellow]Channels ({len(found_channels)})[/COLOR]')
            self.list_control.addItem(section)
            
            for channel in found_channels:
                name = f"[COLOR yellow]► {channel['name']}[/COLOR]"
                item = xbmcgui.ListItem(name)
                item.setArt({'icon': channel.get('logo', ''), 'thumb': channel.get('logo', '')})
                item.setProperty('type', 'channel')
                item.setProperty('channel_id', channel.get('tvg_id', ''))
                self.list_control.addItem(item)
        
        if found_programs:
            section = xbmcgui.ListItem(f'[COLOR lime]Programs ({len(found_programs)})[/COLOR]')
            self.list_control.addItem(section)
            
            for result in found_programs:
                program = result['program']
                channel = result['channel']
                start_time = program.get('start')
                time_str = start_time.strftime('%I:%M %p') if start_time else 'Unknown'
                day_str = ''
                if start_time:
                    if start_time.date() > self.current_time.date():
                        day_str = start_time.strftime('%a ')
                    elif start_time.date() == (self.current_time + timedelta(days=1)).date():
                        day_str = 'Tomorrow '
                
                name = f"[COLOR lime]{program.get('title', 'Unknown')}[/COLOR] on {channel.get('name', 'Unknown')} at {day_str}{time_str}"
                
                item = xbmcgui.ListItem(name)
                item.setArt({'icon': channel.get('logo', ''), 'thumb': channel.get('logo', '')})
                item.setProperty('type', 'program')
                item.setProperty('channel_id', channel.get('tvg_id', ''))
                self.list_control.addItem(item)
        
        if not found_channels and not found_programs:
            no_results = xbmcgui.ListItem('[COLOR red]No matches found[/COLOR]')
            self.list_control.addItem(no_results)
        
        self.setFocusId(100)
    
    def onInit(self):
        try:
            # Apply theme colors
            apply_theme_to_window(self)
            
            # Get list control
            self.list_control = self.getControl(100)
            
            # Show main search menu
            self._show_search_menu()
            
        except Exception as e:
            xbmcgui.Dialog().ok("Error", f"Failed to initialize search: {str(e)}")
            self.close()
    
    def _show_search_menu(self):
        """Show the search options menu"""
        dialog = xbmcgui.Dialog()
        
        # Check for search history
        history = self._get_search_history()
        
        # Build options list
        options = []
        
        # Show recent searches if any
        if history:
            options.append("-- Recent --")
            for h in history[:5]:
                options.append(f"  {h}")
            options.append("")
        
        options.append("New Search...")
        options.append("Full History")
        options.append("Saved Searches")
        options.append("Cancel")
        
        choice = dialog.select("Search", options)
        
        if choice == -1:
            self.close()
            return
        
        # Calculate indices properly
        num_history = len(history[:5]) if history else 0
        new_search_idx = num_history + 2 if history else 1
        
        # Handle selection
        if history and choice > 0 and choice <= num_history:
            # Selected a recent search
            idx = choice - 1
            if 0 <= idx < num_history:
                self.search_term = history[:5][idx]
                self.setProperty('time_filter', 'all')
                self.setProperty('category_filter', 'all')
                self._do_search()
                return
        elif choice == new_search_idx:
            # New Search
            self._do_new_search()
            return
        elif choice == new_search_idx + 1:
            # Full history
            self._show_history_menu()
            return
        elif choice == new_search_idx + 2:
            # Saved searches
            self._show_saved_searches_menu()
            return
        
        # Default: close
        self.close()

    def _do_new_search(self):
        """Handle new search input"""
        dialog = xbmcgui.Dialog()
        
        keyboard = xbmc.Keyboard('', 'Search Channels and Programs')
        keyboard.doModal()
        
        if not keyboard.isConfirmed():
            # Go back to search menu
            self._show_search_menu()
            return
        
        self.search_term = keyboard.getText().strip()
        
        if not self.search_term:
            # Go back to search menu
            self._show_search_menu()
            return
        
        # Ask if user wants to set filters
        filter_choice = dialog.yesno("Search", "Would you like to set time/category filters?", "Yes", "No - Search All")
        
        if filter_choice:
            self._show_filter_menu()
        else:
            self.setProperty('time_filter', 'all')
            self.setProperty('category_filter', 'all')
            self._do_search()
    
    def play_channel(self, channel_id: str):
        """Play a channel directly or return selection if return_selection is True"""
        try:
            # Find the channel
            channel = next((c for c in self.channels if c.get('tvg_id') == channel_id), None)
            if not channel:
                return
            
            # If return_selection mode, just store the channel and close
            if self.return_selection:
                self.selected_stream = {
                    'link': channel.get('url', '') or channel.get('stream_url', ''),
                    'title': channel.get('name', 'Unknown'),
                    'headers': {}
                }
                self.close()
                return
            
            # Create list item
            list_item = xbmcgui.ListItem(channel.get('name', 'Unknown'))
            list_item.setArt({'icon': channel.get('logo', '')})
            
            # Get stream URL
            url = channel.get('url', '') or channel.get('stream_url', '')
            if '|' in url:
                url = url.split('|')[0].strip()
            
            if not url:
                xbmcgui.Dialog().ok("Error", "No stream URL found for this channel.")
                return
            
            # Close dialog and play
            xbmc.executebuiltin('Dialog.Close(busydialog)')
            self.close()
            
            # Cancel any running YouTube sequential playback before playing new content
            try:
                from youtube_guide import cancel_youtube_playback
                cancel_youtube_playback()
            except ImportError:
                pass
            
            xbmc.Player().play(url, list_item)
            
        except Exception as e:
            xbmcgui.Dialog().ok("Error", f"Failed to play channel: {str(e)}")
    
    def onClick(self, control_id):
        try:
            if control_id == 100:
                pos = self.list_control.getSelectedPosition()
                item = self.list_control.getSelectedItem()
                
                item_type = item.getProperty('type')
                if item_type in ['channel', 'program']:
                    channel_id = item.getProperty('channel_id')
                    self.play_channel(channel_id)
            
            # Filter button (if exists in skin)
            elif control_id == 101:
                self._show_filter_menu()
            
            # Save search button (if exists in skin)
            elif control_id == 102:
                if self.search_term:
                    self._save_current_search()
            
            # New search button (if exists in skin)
            elif control_id == 103:
                self._show_search_menu()
            
            # Filter button
            elif control_id == 104:
                self._show_filter_menu()
            
            # Saved searches button
            elif control_id == 105:
                self._show_saved_searches_menu()
                
            elif control_id == 106:
                self._show_history_menu()
        
        except Exception as e:
            xbmcgui.Dialog().ok("Error", f"Error handling click: {str(e)}")
    
    def onAction(self, action):
        try:
            action_id = action.getId()
            
            # Close on back/menu buttons
            if action_id in [xbmcgui.ACTION_PREVIOUS_MENU, xbmcgui.ACTION_NAV_BACK, 92]:
                self.close()
            
            # Context menu for additional options
            elif action_id == 117:  # Context menu
                dialog = xbmcgui.Dialog()
                options = ["New Search", "Saved Searches", "Set Filters", "Save Current Search"]
                choice = dialog.select("Options", options)
                
                if choice == 0:
                    self._show_search_menu()
                elif choice == 1:
                    self._show_saved_searches_menu()
                elif choice == 2:
                    self._show_filter_menu()
                elif choice == 3:
                    if self.search_term:
                        self._save_current_search()
        
        except Exception as e:
            xbmcgui.Dialog().ok("Error", f"Error handling action: {str(e)}")
