
# -*- coding: utf-8 -*-
# Module: download_preview
# Author: mmirousek
# License: AGPL v3
#
# Interaktivní preview editor pro hromadné zařazení epizod sezóny / celého
# seriálu do fronty stahování. Návazné moduly:
#   - offline_manager.save_season / save_whole_show (volá build_queue_plan + show_interactive_plan)
#   - offline_manager.save_episode(..., selected_stream=...) (finální zařazení)

import os
from typing import List, Optional

import xbmcgui

from resources.lib.utils.log_utils import get_logger
from resources.lib.utils.ui_utils import clean_filename
from resources.lib.down.offline_manager import (
    _pick_stream_by_quality,
    _stream_has_cz_audio,
    _stream_has_czsub,
    _stream_has_quality,
    _stream_hdr_rank,
    _resolve_folder_display_name,
    _get_target_folder,
    _merge_season_files,
    _episode_in_offline_cache,
)

log = get_logger()


_SIZE_UNITS = ('B', 'KB', 'MB', 'GB', 'TB')


def _format_size(size_bytes) -> str:
    """Lidsky čitelná velikost; None/0/invalid → prázdný řetězec."""
    try:
        n = float(size_bytes or 0)
    except (TypeError, ValueError):
        return ''
    if n <= 0:
        return ''
    idx = 0
    while n >= 1024.0 and idx < len(_SIZE_UNITS) - 1:
        n /= 1024.0
        idx += 1
    if idx == 0:
        return f"{int(n)} {_SIZE_UNITS[idx]}"
    return f"{n:.1f} {_SIZE_UNITS[idx]}"


def _stream_badge_label(st: dict) -> str:
    """Kompaktní prefix [CZdab][1080p]... z compact_stream_badges."""
    try:
        from resources.lib.series.series_helpers import compact_stream_badges
        badges = compact_stream_badges(st) or []
    except Exception:
        badges = []
    return ''.join(f"[{b}]" for b in badges)


def _quality_rank(st: dict) -> int:
    """Numerický rank pro řazení (vyšší = lepší). 2160→4, 1080→3, 720→2, jinak 1."""
    if _stream_has_quality(st, '2160'):
        return 4
    if _stream_has_quality(st, '1080'):
        return 3
    if _stream_has_quality(st, '720'):
        return 2
    return 1


def _lang_group_rank(st: dict) -> int:
    """0 = CZdab, 1 = CZtit, 2 = ostatní (pro řazení v pool listu)."""
    if _stream_has_cz_audio(st):
        return 0
    if _stream_has_czsub(st):
        return 1
    return 2


def _sort_streams_for_display(streams: list) -> list:
    """Setřídí streamy: CZdab → CZtit → rest, uvnitř podle kvality sestupně,
    při shodné kvalitě HDR > SDR (DV > HDR10+ > HDR10 > HDR > nic), při shodě
    podle velikosti sestupně. Pořadí odpovídá auto-výběru v
    offline_manager._best_in_quality_chain.
    """
    if not streams:
        return []
    return sorted(
        streams,
        key=lambda s: (
            _lang_group_rank(s),
            -_quality_rank(s),
            -_stream_hdr_rank(s),
            -(int(s.get('size') or 0)),
        ),
    )


class PlanItem:
    """Jeden řádek plánu — jedna epizoda se zvoleným streamem a všemi alternativami."""

    __slots__ = (
        'season', 'episode', 'ep_title',
        'all_streams', 'chosen_idx', 'skip_reason',
    )

    def __init__(self, season: str, episode: str, ep_title: str,
                 all_streams: list, chosen_idx: int, skip_reason):
        self.season = str(season)
        self.episode = str(episode)
        self.ep_title = ep_title or ''
        self.all_streams = all_streams or []
        self.chosen_idx = chosen_idx if chosen_idx is not None else -1
        self.skip_reason = skip_reason  # None | 'offline' | 'in_queue' | 'no_stream'

    @property
    def chosen_stream(self):
        if 0 <= self.chosen_idx < len(self.all_streams):
            return self.all_streams[self.chosen_idx]
        return None

    @property
    def se_label(self) -> str:
        try:
            return f"S{int(self.season):02d}E{int(self.episode):02d}"
        except (TypeError, ValueError):
            return f"S{self.season}E{self.episode}"


def _find_chosen_idx(sorted_streams: list, best_stream) -> int:
    """Najde index 'best_stream' v setříděném listu (fallback 0)."""
    if not best_stream or not sorted_streams:
        return 0 if sorted_streams else -1
    best_ident = best_stream.get('ident')
    if best_ident:
        for i, st in enumerate(sorted_streams):
            if st.get('ident') == best_ident:
                return i
    # fallback identity
    for i, st in enumerate(sorted_streams):
        if st is best_stream:
            return i
    return 0


def build_queue_plan(series_name: str, data: dict, seasons: list,
                     prefer_quality: str) -> List[PlanItem]:
    """Sestaví seznam PlanItem pro zadané sezóny s vypočítaným skip_reason.

    - `seasons`: list of season keys (str), pořadí = pořadí v preview.
    - `prefer_quality`: '2160' | '1080' | '720' | '' (prázdné = bez preference).
    """
    result: List[PlanItem] = []
    if not isinstance(data, dict):
        return result

    all_seasons = data.get('seasons') or {}
    display_name = _resolve_folder_display_name(series_name, data)

    # Queue snapshot (1x) pro in_queue check
    try:
        from resources.lib.down.download_queue import load_queue
        q = load_queue() or []
    except Exception:
        q = []

    for s_key in seasons:
        season = all_seasons.get(str(s_key)) or {}
        if not season:
            continue

        # Fyzické soubory v cílové složce (primary + legacy) — jednou per sezóna
        try:
            season_primary = _get_target_folder(
                series_name, str(s_key), display_name=display_name
            )
            season_legacy = _get_target_folder(
                series_name, str(s_key), display_name=series_name
            )
            files = _merge_season_files(season_primary or '', season_legacy or '')
        except Exception:
            files = []

        ep_keys = sorted(season.keys(), key=lambda k: int(k) if str(k).isdigit() else 0)
        for ep_num in ep_keys:
            ep = season.get(ep_num) or {}
            ep_title = ep.get('name') or ep.get('title') or ep.get('source_name') or ''
            streams = ep.get('streams') or []

            sorted_streams = _sort_streams_for_display(streams)

            # Skip reason detection
            skip_reason = None
            if not streams:
                skip_reason = 'no_stream'
            else:
                # offline file check
                expected_bases = []
                if ep_title:
                    expected_bases.append(clean_filename(ep_title).lower())
                try:
                    expected_bases.append(
                        f"S{int(s_key):02d}E{int(ep_num):02d}".lower()
                    )
                except (TypeError, ValueError):
                    pass
                on_disk = False
                for fname in files:
                    try:
                        base_noext = os.path.splitext(fname)[0].lower()
                        if any(base_noext.startswith(b) or base_noext == b for b in expected_bases if b):
                            on_disk = True
                            break
                    except Exception:
                        continue
                if on_disk:
                    skip_reason = 'offline'
                elif _episode_in_offline_cache(series_name, display_name, s_key, ep_num):
                    skip_reason = 'offline'
                else:
                    in_q = any(
                        i.get('series_name') == series_name
                        and str(i.get('season')) == str(s_key)
                        and str(i.get('episode')) == str(ep_num)
                        for i in q
                    )
                    if in_q:
                        skip_reason = 'in_queue'

            # Výběr streamu (i pro skip epizody ať uživatel vidí, co by se jinak vybralo)
            if streams:
                best = _pick_stream_by_quality(streams, prefer_quality or '')
                chosen_idx = _find_chosen_idx(sorted_streams, best)
            else:
                chosen_idx = -1

            result.append(PlanItem(
                season=str(s_key),
                episode=str(ep_num),
                ep_title=ep_title,
                all_streams=sorted_streams,
                chosen_idx=chosen_idx,
                skip_reason=skip_reason,
            ))

    return result


def _stream_line(st: dict) -> str:
    """Textový label pro jeden stream v sub-dialogu i hlavním listu."""
    prefix = _stream_badge_label(st) or '[?]'
    size = _format_size(st.get('size'))
    if size:
        return f"{prefix} {size}"
    return prefix


def _plan_row_label(item: PlanItem) -> str:
    """Label pro řádek v hlavním select dialogu."""
    title = item.ep_title or ''
    left = f"{item.se_label}"
    if title:
        left += f"  {title}"

    if item.skip_reason == 'offline':
        return f"[COLOR gray]{left}  [skip: už stažena][/COLOR]"
    if item.skip_reason == 'in_queue':
        return f"[COLOR gray]{left}  [skip: ve frontě][/COLOR]"
    if item.skip_reason == 'no_stream':
        return f"[COLOR gray]{left}  [bez streamu][/COLOR]"

    chosen = item.chosen_stream
    stream_lbl = _stream_line(chosen) if chosen else '[---]'
    return f"{left}  {stream_lbl}"


def _summary_counts(plan: List[PlanItem]):
    """Vrátí (enqueue_count, skip_offline, skip_in_queue, skip_no_stream, total_bytes)."""
    enq = 0
    skip_off = 0
    skip_q = 0
    skip_ns = 0
    total_bytes = 0
    for it in plan:
        if it.skip_reason == 'offline':
            skip_off += 1
            continue
        if it.skip_reason == 'in_queue':
            skip_q += 1
            continue
        if it.skip_reason == 'no_stream':
            skip_ns += 1
            continue
        enq += 1
        st = it.chosen_stream
        try:
            total_bytes += int((st or {}).get('size') or 0)
        except (TypeError, ValueError):
            pass
    return enq, skip_off, skip_q, skip_ns, total_bytes


def _pick_alt_stream(item: PlanItem) -> Optional[int]:
    """Sub-dialog výběru alternativního streamu pro jednu epizodu.
    Vrací nový chosen_idx (může být stejný) nebo None při zrušení."""
    if not item.all_streams:
        xbmcgui.Dialog().notification('Offline', 'Epizoda nemá žádné streamy.', xbmcgui.NOTIFICATION_WARNING, 2500)
        return None

    options = []
    for i, st in enumerate(item.all_streams):
        marker = '▶ ' if i == item.chosen_idx else '   '
        options.append(f"{marker}{_stream_line(st)}")

    title = f"{item.se_label} — vyber stream"
    preselect = max(0, item.chosen_idx)
    try:
        idx = xbmcgui.Dialog().select(title, options, preselect=preselect)
    except Exception:
        idx = xbmcgui.Dialog().select(title, options)
    if idx is None or idx < 0:
        return None
    return idx


def show_interactive_plan(plan: List[PlanItem], title: str) -> Optional[List[PlanItem]]:
    """Interaktivní editor. Vrací upravený plán (in-place) nebo None při zrušení.

    Hlavní položky:
      0) POKRAČOVAT (zařadit X epizod, ~Y)
      1) ZRUŠIT
      2+) jednotlivé epizody (řazené podle plan list pořadí)
    """
    if not plan:
        xbmcgui.Dialog().notification('Offline', 'Nic k zařazení.', xbmcgui.NOTIFICATION_INFO, 2500)
        return None

    dlg = xbmcgui.Dialog()
    while True:
        enq, skip_off, skip_q, skip_ns, total_bytes = _summary_counts(plan)
        size_str = _format_size(total_bytes) or '?'

        continue_label = f"[COLOR lime]POKRAČOVAT — zařadit {enq} epizod (~{size_str})[/COLOR]"
        if enq == 0:
            continue_label = "[COLOR gray]POKRAČOVAT — nic k zařazení[/COLOR]"
        cancel_label = "[COLOR red]ZRUŠIT[/COLOR]"

        options = [continue_label, cancel_label]
        for it in plan:
            options.append(_plan_row_label(it))

        # Info řádek v titulku
        info_parts = []
        if skip_off:
            info_parts.append(f"offline:{skip_off}")
        if skip_q:
            info_parts.append(f"ve frontě:{skip_q}")
        if skip_ns:
            info_parts.append(f"bez streamu:{skip_ns}")
        info = (' · ' + ', '.join(info_parts)) if info_parts else ''
        full_title = f"{title}{info}"

        try:
            sel = dlg.select(full_title, options)
        except Exception as e:
            log.error(f"show_interactive_plan select error: {e}")
            return None

        if sel is None or sel < 0:
            return None
        if sel == 0:
            if enq == 0:
                xbmcgui.Dialog().notification('Offline', 'Nic k zařazení.', xbmcgui.NOTIFICATION_INFO, 2500)
                continue
            return plan
        if sel == 1:
            return None

        # Epizoda (index posunutý o 2 hlavní položky)
        ep_idx = sel - 2
        if ep_idx < 0 or ep_idx >= len(plan):
            continue

        item = plan[ep_idx]
        if item.skip_reason == 'offline':
            xbmcgui.Dialog().notification('Offline', 'Epizoda je už stažená.', xbmcgui.NOTIFICATION_INFO, 2500)
            continue
        if item.skip_reason == 'in_queue':
            xbmcgui.Dialog().notification('Offline', 'Epizoda je už ve frontě.', xbmcgui.NOTIFICATION_INFO, 2500)
            continue
        if item.skip_reason == 'no_stream':
            xbmcgui.Dialog().notification('Offline', 'Epizoda nemá žádné streamy.', xbmcgui.NOTIFICATION_WARNING, 2500)
            continue

        new_idx = _pick_alt_stream(item)
        if new_idx is not None:
            item.chosen_idx = new_idx
