# wcs/ai/MediaChannelManager.py
"""
MediaChannelManager - Unified manager for MyTV and MyCinema channels.

Supports two channel types:
- 'tv': MyTV channels with series (episodes)
- 'cinema': MyCinema channels with movies

For backward compatibility, MyTVChannelManager is aliased to MediaChannelManager.
"""

import json
import random
import uuid
import xbmc
import xbmcaddon
import os
import xbmcvfs
from wcs import user_data
from wcs.library import LibraryManager


class Channel:
    """
    Represents a media channel (TV or Cinema).
    
    Attributes:
        id: Unique channel identifier
        name: Display name
        icon: Path to channel icon (generated composite)
        media_list: List of media items (series or movies)
        channel_type: 'tv' or 'cinema'
    """
    
    def __init__(self, id=None, name="Můj kanál", icon=None, media_list=None, channel_type='tv'):
        self.id = id or str(uuid.uuid4())
        self.name = name
        self.icon = icon
        self.media_list = media_list or []
        self.channel_type = channel_type
    
    # Legacy property for backward compatibility with MyTV code
    @property
    def series_list(self):
        return self.media_list
    
    @series_list.setter
    def series_list(self, value):
        self.media_list = value

    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'icon': self.icon,
            'media_list': self.media_list,
            # Legacy key for backward compatibility
            'series_list': self.media_list,
            'channel_type': self.channel_type
        }

    @classmethod
    def from_dict(cls, data, default_channel_type='tv'):
        # Support both new 'media_list' and legacy 'series_list'
        media_list = data.get('media_list') or data.get('series_list') or []
        return cls(
            id=data.get('id'),
            name=data.get('name'),
            icon=data.get('icon'),
            media_list=media_list,
            channel_type=data.get('channel_type', default_channel_type)
        )


def _invalidate_skin_widgets(channel_type='tv', contents_changed=True):
    """Publish only dependent rows; keep the last network snapshot during refresh."""
    try:
        from wcs.skin import refresh
        rows = {'widget_mycinema_channels'} if channel_type == 'cinema' else {
            'widget_mytv_channels', 'widget_mytv_next'}
        if channel_type == 'cinema' and contents_changed:
            rows.add('widget_mycinema_recommended')
        refresh.changed(rows)
        if channel_type == 'cinema' and contents_changed:
            refresh.request({'widget_mycinema_recommended'}, force=True)
    except Exception as exc:
        xbmc.log(f'[Milionář Skin] Channel refresh failed: {exc}', xbmc.LOGWARNING)


class MediaChannelManager:
    """
    Manages channels for both MyTV (series) and MyCinema (movies).
    
    Args:
        addon: Kodi addon instance
        channel_type: 'tv' for MyTV, 'cinema' for MyCinema
    """
    
    def __init__(self, addon, channel_type='tv'):
        self.addon = addon
        self.channel_type = channel_type
        self.channels = []
        self._load_channels()

    def _get_storage_key(self):
        """Returns the storage key for this channel type."""
        if self.channel_type == 'cinema':
            return 'my_cinema_channels'
        return 'my_tv_channels'

    def _load_channels(self):
        try:
            if self.channel_type == 'cinema':
                raw_list = user_data.load_mycinema_channels(self.addon)
            else:
                raw_list = user_data.load_mytv_channels(self.addon)
            self.channels = [Channel.from_dict(item, self.channel_type) for item in raw_list]
        except Exception as e:
            log_prefix = '[MyCinema]' if self.channel_type == 'cinema' else '[MyTV]'
            xbmc.log(f"[plugin.video.milionar] {log_prefix} Error loading channels: {e}", level=xbmc.LOGERROR)
            self.channels = []

    def _save_channels(self):
        try:
            raw_list = [ch.to_dict() for ch in self.channels]
            if self.channel_type == 'cinema':
                load, store = user_data.load_mycinema_channels, user_data.store_mycinema_channels
            else:
                load, store = user_data.load_mytv_channels, user_data.store_mytv_channels
            before = [Channel.from_dict(ch, self.channel_type).to_dict() for ch in load(self.addon)]
            if before == raw_list:
                return
            if store(self.addon, raw_list) is False:
                return
            # Older writers/stubs return None; also verify the actual persisted value.
            after = [Channel.from_dict(ch, self.channel_type).to_dict() for ch in load(self.addon)]
            if after == raw_list and after != before:
                contents_changed = False
                if self.channel_type == 'cinema':
                    before_movies = sorted(str(item.get('id')) for ch in before for item in ch['media_list'])
                    after_movies = sorted(str(item.get('id')) for ch in after for item in ch['media_list'])
                    contents_changed = before_movies != after_movies
                _invalidate_skin_widgets(self.channel_type, contents_changed)
        except Exception as e:
            log_prefix = '[MyCinema]' if self.channel_type == 'cinema' else '[MyTV]'
            xbmc.log(f"[plugin.video.milionar] {log_prefix} Error saving channels: {e}", level=xbmc.LOGERROR)

    def get_all_channels(self):
        return self.channels

    def get_channel_by_id(self, channel_id):
        return next((ch for ch in self.channels if ch.id == channel_id), None)

    def create_channel(self, name):
        new_channel = Channel(name=name, channel_type=self.channel_type)
        self.channels.insert(0, new_channel)
        self._save_channels()
        return new_channel

    def rename_channel(self, channel_id, new_name):
        """Rename a channel."""
        channel = self.get_channel_by_id(channel_id)
        if channel:
            channel.name = new_name
            self._save_channels()
            return True
        return False

    def delete_channel(self, channel_id):
        """Delete a channel by ID."""
        original_count = len(self.channels)
        self.channels = [ch for ch in self.channels if ch.id != channel_id]
        if len(self.channels) < original_count:
            self._save_channels()
            return True
        return False

    def move_channel(self, channel_id, new_index):
        """Move a channel to a new position in the list.
        
        Args:
            channel_id: ID of the channel to move
            new_index: Target position (0-based, clamped to valid range)
        
        Returns:
            True if moved successfully, False otherwise
        """
        old_index = None
        for i, ch in enumerate(self.channels):
            if ch.id == channel_id:
                old_index = i
                break
        if old_index is None:
            return False
        
        new_index = max(0, min(new_index, len(self.channels) - 1))
        if old_index == new_index:
            return False
        
        channel = self.channels.pop(old_index)
        self.channels.insert(new_index, channel)
        self._save_channels()
        return True

    def toggle_media_in_channel(self, channel_id, media_data):
        """
        Adds or removes a media item (series or movie) from the channel.
        media_data should contain 'id', 'name'/'title', 'poster_path'/'poster'.
        """
        channel = self.get_channel_by_id(channel_id)
        if not channel:
            return False

        # Normalize ID to str for comparison
        target_id = str(media_data.get('id'))
        
        # Check if exists
        exists_idx = -1
        for idx, item in enumerate(channel.media_list):
            if str(item.get('id')) == target_id:
                exists_idx = idx
                break
        
        if exists_idx >= 0:
            # Remove
            channel.media_list.pop(exists_idx)
            action = "removed"
        else:
            # Add
            # Common fields for both series and movies
            clean_data = {
                'id': media_data.get('id'),
                'name': media_data.get('name') or media_data.get('title'),
                'poster_path': media_data.get('poster_path') or media_data.get('poster'),
                'genre_ids': media_data.get('genre_ids') or [],
                'genres': media_data.get('genres') or []
            }
            
            # Type-specific fields
            if self.channel_type == 'cinema':
                # Movie-specific
                clean_data['release_date'] = media_data.get('release_date', '')
                clean_data['year'] = media_data.get('year') or (media_data.get('release_date', '')[:4] if media_data.get('release_date') else '')
                clean_data['runtime'] = media_data.get('runtime', 0)
                clean_data['overview'] = media_data.get('overview', '')
                clean_data['backdrop_path'] = media_data.get('backdrop_path', '')
                clean_data['tagline'] = media_data.get('tagline', '')  # Movie tagline
            else:
                # Series-specific
                clean_data['first_air_date'] = media_data.get('first_air_date', '')
                clean_data['year'] = media_data.get('year') or (media_data.get('first_air_date', '')[:4] if media_data.get('first_air_date') else '')
            
            channel.media_list.insert(0, clean_data)
            action = "added"
        
        self._save_channels()
        
        # Regenerate channel icon
        self.generate_channel_composite(channel.id)
        
        return action == "added"
    
    # Legacy method name for backward compatibility
    def toggle_series_in_channel(self, channel_id, series_data):
        """Legacy method - calls toggle_media_in_channel."""
        return self.toggle_media_in_channel(channel_id, series_data)

    def generate_channel_composite(self, channel_id):
        """
        Generates a composite icon for the channel based on its media.
        Uses PIL to create a 2x2 grid of posters.
        """
        try:
            from PIL import Image
            import io
        except ImportError as e:
            xbmc.log(f"[plugin.video.milionar] PIL import failed: {e}", level=xbmc.LOGERROR)
            import traceback
            traceback.print_exc()
            return

        # Robust selection of resampling filter for Android/Old PIL compatibility
        resample_filter = None
        filter_name = "Unknown"
        try:
            if hasattr(Image, 'Resampling'):
                resample_filter = Image.Resampling.LANCZOS
                filter_name = "Image.Resampling.LANCZOS"
            elif hasattr(Image, 'LANCZOS'):
                resample_filter = Image.LANCZOS
                filter_name = "Image.LANCZOS"
            elif hasattr(Image, 'ANTIALIAS'):
                resample_filter = Image.ANTIALIAS
                filter_name = "Image.ANTIALIAS"
            else:
                resample_filter = Image.BICUBIC
                filter_name = "Image.BICUBIC (Fallback)"
        except Exception:
            resample_filter = Image.BICUBIC
            filter_name = "Image.BICUBIC (Exception Fallback)"
            
        log_prefix = '[MyCinema]' if self.channel_type == 'cinema' else '[MyTV]'
        xbmc.log(f"[plugin.video.milionar] {log_prefix} PIL loaded. Using resampling filter: {filter_name}", level=xbmc.LOGINFO)

        channel = self.get_channel_by_id(channel_id)
        if not channel: return

        # Constants
        CANVAS_WIDTH = 500
        CANVAS_HEIGHT = 750
        
        # Prepare icons directory (separate for cinema)
        profile_path = xbmcvfs.translatePath(self.addon.getAddonInfo('profile'))
        if self.channel_type == 'cinema':
            icons_dir = os.path.join(profile_path, 'cinema_icons')
        else:
            icons_dir = os.path.join(profile_path, 'channel_icons')
        if not os.path.exists(icons_dir):
            xbmcvfs.mkdir(icons_dir)
        
        # Delete old icon files for this channel (cache busting)
        import glob
        old_icons = glob.glob(os.path.join(icons_dir, f"{channel_id}_*.jpg"))
        for old_icon in old_icons:
            try:
                os.remove(old_icon)
            except:
                pass
            
        # Create new icon with timestamp to bust Kodi cache
        import time
        timestamp = int(time.time())
        target_file = os.path.join(icons_dir, f"{channel_id}_{timestamp}.jpg")
        
        # Get media posters (max 4)
        posters = []
        for item in channel.media_list[:4]:
            p_path = item.get('poster_path') or item.get('poster')
            if p_path:
                local_path = self._get_cached_poster(p_path)
                if local_path:
                    posters.append(local_path)
        
        if not posters:
            channel.icon = None
            self._save_channels()
            return

        # Create canvas
        canvas = Image.new('RGB', (CANVAS_WIDTH, CANVAS_HEIGHT), color=(20, 20, 30))
        
        try:
            if len(posters) == 1:
                img = self._open_image_safely(posters[0])
                if img:
                    img = img.resize((CANVAS_WIDTH, CANVAS_HEIGHT), resample_filter)
                    canvas.paste(img, (0, 0))
                
            elif len(posters) >= 2:
                # 2-4 posters: 2x2 grid
                cell_w = CANVAS_WIDTH // 2
                cell_h = CANVAS_HEIGHT // 2
                
                positions = [
                    (0, 0), (cell_w, 0),
                    (0, cell_h), (cell_w, cell_h)
                ]
                
                for i, p_path in enumerate(posters):
                    if i >= 4: break
                    img = self._open_image_safely(p_path)
                    if img:
                        img = img.resize((cell_w, cell_h), resample_filter)
                        canvas.paste(img, positions[i])
            
            # Save using xbmcvfs to ensure Android write permission compatibility
            buffer = io.BytesIO()
            canvas.save(buffer, 'JPEG', quality=90)
            buffer.seek(0)
            
            f = xbmcvfs.File(target_file, 'w')
            f.write(buffer.read())
            f.close()
            
            # Update channel
            channel.icon = target_file
            self._save_channels()
            xbmc.log(f"[plugin.video.milionar] {log_prefix} Generated channel icon: {target_file}", level=xbmc.LOGINFO)
            
        except Exception as e:
            xbmc.log(f"[plugin.video.milionar] {log_prefix} Error generating composite: {e}", level=xbmc.LOGERROR)
            import traceback
            traceback.print_exc()

    def _open_image_safely(self, path):
        """Helper to open image compatible with Android xbmcvfs"""
        from PIL import Image
        import io
        try:
            # Try direct open first (works on most systems)
            return Image.open(path)
        except Exception:
            try:
                # Fallback: read bytes via xbmcvfs
                f = xbmcvfs.File(path, 'rb')
                data = f.read()
                f.close()
                return Image.open(io.BytesIO(data))
            except Exception as e:
                xbmc.log(f"[plugin.video.milionar] Failed to open image safely {path}: {e}", level=xbmc.LOGERROR)
                return None

    def _get_cached_poster(self, poster_path):
        """
        Downloads poster if needed and returns local path.
        poster_path can be full URL or TMDB partial path.
        """
        if not poster_path: return None
        
        # Construct URL
        if poster_path.startswith('http'):
            url = poster_path
        else:
            url = f"https://image.tmdb.org/t/p/w500{poster_path}"
            
        # Define cache path
        import hashlib
        name_hash = hashlib.md5(url.encode('utf-8')).hexdigest()
        profile_path = xbmcvfs.translatePath(self.addon.getAddonInfo('profile'))
        cache_dir = os.path.join(profile_path, 'img_cache')
        if not os.path.exists(cache_dir):
            xbmcvfs.mkdir(cache_dir)
            
        local_file = os.path.join(cache_dir, f"{name_hash}.jpg")
        
        if xbmcvfs.exists(local_file):
            return local_file
            
        # Download
        try:
            import urllib.request
            req = urllib.request.Request(url, headers={'User-Agent': 'Kodi-Milionar/1.0'})
            with urllib.request.urlopen(req) as response:
                data = response.read()
                f = xbmcvfs.File(local_file, 'w')
                f.write(data)
                f.close()
            return local_file
        except Exception as e:
            xbmc.log(f"[plugin.video.milionar] Download error for {url}: {e}", level=xbmc.LOGERROR)
            return None

    def get_random_episode_for_channel(self, channel_id):
        """
        Returns info about a random 'Next Up' episode from the channel.
        Only applicable for TV channels (series).
        Returns dict or None.
        """
        if self.channel_type == 'cinema':
            # For cinema, return a random unwatched movie
            return self.get_random_movie_for_channel(channel_id)
        
        channel = self.get_channel_by_id(channel_id)
        if not channel or not channel.media_list:
            return None

        # Shuffle series to try random ones
        series_candidates = list(channel.media_list)
        random.shuffle(series_candidates)

        recently_played = user_data.load_recently_played(self.addon)

        for series in series_candidates:
            series_name = series.get('name')
            series_id = str(series.get('id'))
            
            # Find progress
            last_season = 1
            last_episode = 0
            
            for item in recently_played:
                rp_id = str(item.get('tmdb_id', ''))
                if rp_id == series_id or item.get('title') == series_name:
                    if 'season' in item and 'episode' in item:
                        last_season = int(item['season'])
                        last_episode = int(item['episode'])
                        break
            
            # Get next episode
            next_metadata = LibraryManager.get_next_episode_metadata_from_library(
                series_name, last_season, last_episode
            )

            if next_metadata:
                next_metadata['tmdb_id'] = series_id
                return next_metadata

        return None
    
    def get_random_movie_for_channel(self, channel_id):
        """
        Returns info about a random unwatched movie from the cinema channel.
        Returns dict or None.
        """
        from wcs.ai import ChannelHistory
        
        channel = self.get_channel_by_id(channel_id)
        if not channel or not channel.media_list:
            return None

        # Shuffle movies
        movie_candidates = list(channel.media_list)
        random.shuffle(movie_candidates)

        # Find first unwatched movie
        for movie in movie_candidates:
            movie_id = movie.get('id')
            if not ChannelHistory.is_movie_watched(channel_id, movie_id):
                return movie

        # All watched - return random one anyway
        if movie_candidates:
            return random.choice(movie_candidates)
        
        return None


# Backward compatibility alias
MyTVChannelManager = MediaChannelManager
