# -*- coding: utf-8 -*-
"""
Series helpers: utility pro parsování názvů, formátování streamů a datumů.
"""
import re
import datetime
import unicodedata

# ---------------------------------------------------------------------------
# Sjednocené jazykové tagy streamů
#
# Cíl: napříč celým addonem používáme **pouze** dva jazykové feature tagy:
#   CZdab = nalezena česká audio stopa
#   CZtit = nalezena česká titulková stopa
#
# Stará data v `series_data` JSON / cache mohou obsahovat: 'CZ', 'czaudio',
# 'CZsub' nebo dokonce kombinované tokeny typu 'CZ-SK' / 'CZ-EN-Atmos-5.1'
# (z původního `extract_features`). Funkce `normalize_language_features` je
# transparentně mapuje na nový tvar **při čtení** – DB se nemění.
# ---------------------------------------------------------------------------
CZ_DAB = "CZdab"
CZ_TIT = "CZtit"

_LEGACY_DAB_TOKENS = {"cz", "czaudio", "czdab"}
_LEGACY_TIT_TOKENS = {"czsub", "cztit"}
_DROP_LANG_TOKENS = {"sk", "en", "ensub", "sub"}
_AUDIO_PARTS = {"atmos", "7.1", "5.1", "2.0"}
_AUDIO_PART_CASE = {"atmos": "Atmos", "7.1": "7.1", "5.1": "5.1", "2.0": "2.0"}

# ---------------------------------------------------------------------------
# Detekce CZ dub / CZ titulků z filename blobu (sdíleno mezi series_curator
# a offline_manager). Cíl: zabránit falešně pozitivnímu CZdab tagu u streamů,
# kde holé `cz`/`cs` v názvu odkazuje pouze na české titulky (např.
# `Movie.2024.1080p.EN.cz.titulky.mkv`).
# ---------------------------------------------------------------------------
_SUBS_TOKEN_RE = re.compile(
    r'\b(sub|subs|subtitle|subtitles|titulky|tit|forced|hc)\b',
    re.IGNORECASE,
)

# Silné CZ-titulkové markery (kombinace cz/cs + tit/sub/forced/hc).
_CZ_TIT_RE = re.compile(
    r'\b(cz|cs|cze|ces|czech)[ ._\-]*'
    r'(tit|titulky|sub|subs|subtitle|subtitles|forced|hc)\b',
    re.IGNORECASE,
)

# Silné CZ-dub markery (cz/cs + dab/dub/audio v libovolném pořadí).
_CZ_DUB_RE = re.compile(
    r'\b(cz|cs|cze|ces|czech)[ ._\-]*(dab|dabing|dub|audio)\b'
    r'|\b(dab|dabing|dub)[ ._\-]*(cz|cs|cze|ces|czech)\b',
    re.IGNORECASE,
)

# Holé CZ slovo / `cs` v blobu (používá se jen v kombinaci s context checkem).
_BARE_CZ_RE = re.compile(r'\b(cz|cze|ces|czech)\b', re.IGNORECASE)
_BARE_CS_RE = re.compile(r'\bcs\b', re.IGNORECASE)

# Word-boundary CZ match v `lang` poli (např. z Webshare).
_LANG_CZ_RE = re.compile(r'\b(cs|cz|cze|ces)\b', re.IGNORECASE)


def detect_cz_tit_from_blob(blob):
    """True pokud blob (filename + quality + ainfo) obsahuje silný marker
    CZ titulků (`cz.titulky`, `cs.sub`, `czech.forced`, …).
    """
    if not blob:
        return False
    return bool(_CZ_TIT_RE.search(str(blob)))


def detect_cz_dub_from_blob(blob, lang=""):
    """True pokud blob/lang signalizuje skutečnou českou audio stopu.

    Pravidla (přísně v tomto pořadí):
      1) `lang` field s word-boundary CZ kódem (cs/cz/cze/ces) → True.
      2) Silný CZ-dub marker v blobu (`cz.dab`, `cs.dub`, `dab.cz`, …) → True.
      3) Holé `cz` / `cs` v blobu → True **jen pokud** v blobu **není**
         přítomen ani silný CZ-tit marker, ani žádný subs token
         (sub/titulky/forced/hc). Jinak je téměř jistě řeč o titulcích,
         ne dubu (např. `EN.dub.cz.titulky.mkv`).
    """
    blob_l = str(blob or "").lower()
    lang_l = str(lang or "").lower()

    if lang_l and _LANG_CZ_RE.search(lang_l):
        return True
    if blob_l and _CZ_DUB_RE.search(blob_l):
        return True
    if blob_l and (_BARE_CZ_RE.search(blob_l) or _BARE_CS_RE.search(blob_l)):
        if _CZ_TIT_RE.search(blob_l):
            return False
        if _SUBS_TOKEN_RE.search(blob_l):
            return False
        return True
    return False


def normalize_language_features(feats):
    """Vrátí kopii listu features s jednotnými jazykovými tagy.

    - holé legacy tokeny (`CZ`, `czaudio`) → `CZdab`
    - holé legacy tokeny (`CZsub`) → `CZtit`
    - ne-CZ jazykové tokeny (`SK`, `EN`, `ENsub`, `SUB`) odstraněny
    - kombinované tokeny (`CZ-SK`, `CZ-EN-Atmos-5.1`) rozloženy: pokud
      obsahují CZ část → `CZdab`, audio části (`Atmos`/`5.1`/`7.1`/`2.0`)
      ponechány jako samostatné tagy, ne-CZ jazykové části zahozeny
    - ostatní featury (HDR, DV, 2160p, HEVC, …) zůstávají beze změny
    - dedupe se zachováním pořadí prvního výskytu
    """
    out, seen = [], set()

    def _emit(tok):
        if tok and tok not in seen:
            seen.add(tok)
            out.append(tok)

    for raw in (feats or []):
        s = str(raw).strip()
        if not s:
            continue
        sl = s.lower()
        if sl in _LEGACY_DAB_TOKENS:
            _emit(CZ_DAB)
            continue
        if sl in _LEGACY_TIT_TOKENS:
            _emit(CZ_TIT)
            continue
        if sl in _DROP_LANG_TOKENS:
            continue
        if "-" in sl and any(
            p in _LEGACY_DAB_TOKENS or p in _DROP_LANG_TOKENS or p in _AUDIO_PARTS
            for p in sl.split("-")
        ):
            parts = sl.split("-")
            if any(p in _LEGACY_DAB_TOKENS for p in parts):
                _emit(CZ_DAB)
            for p in parts:
                if p in _AUDIO_PARTS:
                    _emit(_AUDIO_PART_CASE[p])
            continue
        _emit(s)
    return out


def compute_cz_tags_from_cache(ident):
    """Vrátí `(has_dab, has_tit)` pokud je stream skutečně proskenován
    (ffprobe / http_range_probe → cache obsahuje audio_tracks + video).

    Pokud probe neproběhl nebo ident chybí, vrátí `None` — volající má pak
    použít fallback (filename heuristika).

    Tato funkce je centrálním bodem rozhodnutí "je cache autoritativní?".
    Použita ve všech místech, kde se rozhoduje o CZdab/CZtit (UI badges,
    play_handler řazení, offline manager, search engine enrichment).
    """
    if not ident:
        return None
    try:
        from resources.lib.series import subcaps_cache
        if not subcaps_cache.is_probed(ident):
            return None
        return (
            bool(subcaps_cache.has_cz_audio(ident)),
            bool(subcaps_cache.has_cz_subs(ident)),
        )
    except Exception:
        return None


def get_stream_language_tags(st):
    """Vrátí podmnožinu `[CZdab, CZtit]` pro daný stream.

    Priorita:
      1) Pokud byl stream **proskenován** (ffprobe / http_range_probe →
         `subcaps_cache.is_probed(ident)`), je cache **jediným**
         autoritativním zdrojem. Filename-derived CZ tagy v `st['features']`
         se ignorují — probe vidí skutečné audio/sub stopy a je
         spolehlivější než parsing názvu (kde `EN.cz.titulky.mkv` typicky
         vede k FP CZdab).
      2) Jinak (žádná probe data) fallback na CZ tagy uložené v
         `st['features']` (`normalize_language_features`).
    """
    ident = st.get("ident")
    cached = compute_cz_tags_from_cache(ident)
    if cached is not None:
        has_dab, has_tit = cached
    else:
        feats_norm = normalize_language_features(st.get("features") or [])
        has_dab = (CZ_DAB in feats_norm)
        has_tit = (CZ_TIT in feats_norm)
    out = []
    if has_dab:
        out.append(CZ_DAB)
    if has_tit:
        out.append(CZ_TIT)
    return out


_PREFIX_VIDEO_RES = ('2160p', '1080p', '720p')
_PREFIX_HDR = ('HDR10+', 'HDR10', 'HDR')
_PREFIX_AUDIO = ('Atmos',)


def _detect_tech_badges_from_blob(blob):
    """Detekuje technické badge (rozlišení / HDR / Atmos) přímo z názvu.

    Slouží jako fallback v :func:`compact_stream_badges`, aby prefix v UI
    listu vždy reflektoval **aktuální** detekci — i když uložené
    ``st['features']`` v ``series_data`` byly zapsány starší verzí
    detekce (např. před přidáním ``HDR10+``).

    Sjednocené regexy s :func:`StreamCurator.extract_features`.
    """
    out = set()
    if not blob:
        return out
    b = blob.lower()
    if re.search(r'\b(2160p|4k|uhd)\b', b):
        out.add('2160p')
    elif re.search(r'\b1080p\b', b):
        out.add('1080p')
    elif re.search(r'\b720p\b', b):
        out.add('720p')
    if re.search(r'\bhdr10\s*\+|\bhdr10\s*plus\b', b):
        out.add('HDR10+')
    elif re.search(r'\bhdr10\b', b):
        out.add('HDR10')
    elif re.search(r'\bhdr\b', b):
        out.add('HDR')
    if re.search(r'\b(dv|dolby\s*vision)\b', b):
        out.add('DV')
    if re.search(r'\batmos\b', b):
        out.add('Atmos')
    return out


def compact_stream_badges(st):
    """Vrátí kompaktní seznam badge pro prefix v UI listu streamů.

    Pevné pořadí: jazyk (CZdab/CZtit) → rozlišení (max. 1) → DV (nezávisle)
    → HDR varianta (max. 1 z HDR10+/HDR10/HDR) → Atmos.

    DV (Dolby Vision) je samostatná HDR technologie — streamy běžně mají
    DV i HDR10+ (DV jako enhancement layer nad HDR10/HDR10+ základem),
    proto se zobrazují **vedle sebe**, ne jako alternativa.

    Jazykové tagy jdou přes :func:`get_stream_language_tags` (autoritativně
    z :mod:`subcaps_cache` u proboaných streamů, jinak fallback na
    ``st['features']``).

    Technické badge se sbírají z **union** ``st['features']`` ∪
    fresh-detekce z ``st['name']/quality/ainfo`` — díky tomu prefix vidí i
    nově přidané badge (např. ``HDR10+``), které ještě nejsou propsané do
    ``series_data`` (re-extract neproběhl).
    """
    out = []
    try:
        for tag in get_stream_language_tags(st):
            if tag not in out:
                out.append(tag)
    except Exception:
        pass

    feats_set = set()
    if isinstance(st, dict):
        feats = st.get('features')
        if isinstance(feats, list):
            for f in feats:
                if isinstance(f, str):
                    feats_set.add(f)
        try:
            blob = ' '.join([
                st.get('name') or '',
                st.get('quality') or '',
                st.get('ainfo') or '',
            ])
            feats_set |= _detect_tech_badges_from_blob(blob)
        except Exception:
            pass

    for res in _PREFIX_VIDEO_RES:
        if res in feats_set:
            out.append(res)
            break

    if 'DV' in feats_set:
        out.append('DV')

    for hdr in _PREFIX_HDR:
        if hdr in feats_set:
            out.append(hdr)
            break

    for au in _PREFIX_AUDIO:
        if au in feats_set and au not in out:
            out.append(au)

    return out


def _default_build_url(**kwargs):
    """Fallback builder plugin URL."""
    try:
        import sys
        from urllib.parse import urlencode
        base = sys.argv[0]
        return "{}?{}".format(base, urlencode(kwargs, encoding="utf-8"))
    except:
        return "plugin://plugin.video.mmirousek_v3/?" + "&".join([f"{k}={v}" for k, v in kwargs.items()])

def _norm(s):
    """Odstraní diakritiku a převede na malé písmenka."""
    if not isinstance(s, str): return ""
    return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn').lower()

def format_date_cz(date_str):
    """Převede YYYY-MM-DD na DD.MM.YYYY."""
    if not date_str or not isinstance(date_str, str): return ""
    try:
        parts = date_str.split('-')
        if len(parts) == 3:
            return f"{parts[2]}.{parts[1]}.{parts[0]}"
    except: pass
    return date_str

def _detect_episode_info(filename):
    """
    Pokusí se z názvu souboru extrahovat SxxExx.
    Vrací (s, e) nebo (None, None).
    """
    if not filename: return None, None
    f = filename.lower()
    m = re.search(r's(\d{1,2})e(\d{1,3})', f)
    if m: return int(m.group(1)), int(m.group(2))
    
    # Alternativa: 1x01
    m = re.search(r'(\d{1,2})x(\d{1,3})', f)
    if m: return int(m.group(1)), int(m.group(2))
    
    return None, None

def _is_likely_episode(filename, s, e):
    """Vrátí True, pokud název souboru napovídá, že jde o danou epizodu."""
    fs, fe = _detect_episode_info(filename)
    if fs is not None and fe is not None:
        return fs == s and fe == e
    return False

def _features_to_stream_info(features, runtime_min=None):
    """
    Konverze seznamu features (2160p, 5.1, HDR...) na slovníky pro Kodi li.addStreamInfo.
    """
    vinfo = {}
    ainfo = {}
    if runtime_min:
        vinfo['duration'] = runtime_min * 60
    
    if not features:
        return vinfo, ainfo

    fs = [f.lower() for f in features]
    # Video
    if '2160p' in fs or '4k' in fs: vinfo['width'], vinfo['height'] = 3840, 2160
    elif '1080p' in fs:             vinfo['width'], vinfo['height'] = 1920, 1080
    elif '720p' in fs:              vinfo['width'], vinfo['height'] = 1280, 720
    
    if 'hdr' in fs:   vinfo['hdr'] = 'hdr10'
    if 'dv' in fs:    vinfo['hdr'] = 'dolbyvision'
    
    # Audio
    if '7.1' in fs:   ainfo['channels'] = 8
    elif '5.1' in fs: ainfo['channels'] = 6
    else:            ainfo['channels'] = 2
    
    if 'atmos' in fs: ainfo['codec'] = 'truehd'
    elif 'dts' in fs:  ainfo['codec'] = 'dts'

    return vinfo, ainfo

def _derive_latest_episode_timestamp(ep_dict):
    """
    Vrátí timestamp poslední aktivity v epizodě.
    Priorita: last_watched_at > last_refresh_at.
    """
    if not isinstance(ep_dict, dict): return 0
    ts_str = ep_dict.get('last_watched_at') or ep_dict.get('last_refresh_at')
    if not ts_str: return 0
    try:
        # ISO format: 2024-02-15T14:30:00
        return datetime.datetime.fromisoformat(ts_str.replace('Z', '')).timestamp()
    except:
        return 0

def _iter_saved_streams(series_data):
    """Generátor: vrací (s_num, e_num, episode_dict, stream_dict) pro všechny uložené streamy
    seřazené od nejmenší série a epizody."""
    if not isinstance(series_data, dict): return
    seasons = series_data.get('seasons') or {}
    for s_key in sorted(seasons.keys(), key=lambda k: int(k) if str(k).isdigit() else 999):
        episodes = seasons[s_key]
        if not isinstance(episodes, dict): continue
        for e_key in sorted(episodes.keys(), key=lambda k: int(k) if str(k).isdigit() else 999):
            ep = episodes[e_key]
            for st in (ep.get('streams') or []):
                yield s_key, e_key, ep, st

def get_stream_languages(st):
    """Kompatibilní obálka — vrací sjednocené tagy `[CZdab, CZtit]`.

    Dříve vracela `[CZ, CZsub]`. Volání je zachováno (play_handler,
    series_ui_builder), ale výstup používá nové tagy.
    """
    return get_stream_language_tags(st)


def episode_probe_marker_suffix(ep):
    """Suffix pro menu epizod: `` [ff]`` / `` [rg]`` / `` [ff][rg]``.

    ``[ff]`` = aspoň jeden stream má úspěšný probe z exec ffprobe větve;
    ``[rg]`` = aspoň jeden z HTTP range větve (:func:`subcaps_cache.probe_backend`).
    """
    from resources.lib.series import subcaps_cache

    has_ff = False
    has_rg = False
    for st in (ep or {}).get('streams') or []:
        if not isinstance(st, dict):
            continue
        b = subcaps_cache.probe_backend(st.get('ident'))
        if b == 'ffprobe':
            has_ff = True
        elif b == 'http_range':
            has_rg = True
    parts = []
    if has_ff:
        parts.append('[ff]')
    if has_rg:
        parts.append('[rg]')
    return (' ' + ''.join(parts)) if parts else ''


def season_probe_marker_suffix(season_dict):
    """Suffix pro menu sezóny — OR přes všechny epizody (stejný formát jako epizoda)."""
    from resources.lib.series import subcaps_cache

    has_ff = False
    has_rg = False
    for ep in (season_dict or {}).values():
        if not isinstance(ep, dict):
            continue
        for st in ep.get('streams') or []:
            if not isinstance(st, dict):
                continue
            b = subcaps_cache.probe_backend(st.get('ident'))
            if b == 'ffprobe':
                has_ff = True
            elif b == 'http_range':
                has_rg = True
    parts = []
    if has_ff:
        parts.append('[ff]')
    if has_rg:
        parts.append('[rg]')
    return (' ' + ''.join(parts)) if parts else ''


def get_episode_streams(series_manager, series_name, season, episode):
    """Vrátí seznam streamů pro danou epizodu."""
    data = series_manager.load_series_data(series_name)
    if not data: return []
    ep = data.get('seasons', {}).get(str(season), {}).get(str(episode))
    return ep.get('streams', []) if ep else []
