# -*- coding: utf-8 -*-
"""
Cache pro výsledky ffprobe (titulky, audio, video) na úrovni jednotlivých streamů (ident).
Soubor: userdata/addon_data/plugin.video.mmirousek_v3/subcaps_cache.json

Schema (per ident):
{
  "iLWMlWQsbs": {
    "cz_subs": true,
    "subs_langs": ["cs","en"],
    "has_cz_audio": true,
    "audio_tracks": [
      {"lang":"cs","codec":"eac3","channels":6,"layout":"5.1(side)","title":"Czech 5.1","atmos":true},
      {"lang":"en","codec":"dts","channels":6,"layout":"5.1","title":"English 5.1","atmos":false}
    ],
    "best_audio": {"lang":"cs","codec":"eac3","channels":6,"layout":"5.1(side)","title":"Czech 5.1","atmos":true},
    "video": {
      "width":1920,"height":1080,"pix_fmt":"yuv420p10le",
      "hdr":false,"dv":false,"resolution_tag":"1080p"
    },
    "probed_at":"2026-02-10T18:45:12",
    "ffprobe_version":"N-110000-gxxxx",
    "series_name": "tmdb_12345"
  },
  ...
}

Pozn. k ``ffprobe_version``: kromě reálné verze ffprobe (legacy) ukládá
``subs_probe`` i značky větví: ``windows-ffprobe`` (exec ffprobe) a
``http-range-v1`` (HTTP range probe). Rozlišení v UI přes :func:`probe_backend`.

Volitelně series_name — storage key seriálu (jako ve správci seriálů), pro selektivní výmaz cache.
"""

import json
import time
import xbmc
import xbmcvfs
import xbmcaddon

from resources.lib.utils.log_utils import get_logger

log = get_logger()
ADDON = xbmcaddon.Addon()
PROFILE = xbmcvfs.translatePath(ADDON.getAddonInfo('profile'))
CACHE_FILE = xbmcvfs.translatePath(PROFILE + 'subcaps_cache.json')

# Jedno načtení na proces — předtím každý get(ident) volal _load() znovu (brzda u stovek streamů).
_cache_data = None

_CZECH_LANG_CODES = ("cs", "cz", "cze", "ces")


def _invalidate_memory_cache():
    global _cache_data
    _cache_data = None


def _load_from_disk() -> dict:
    try:
        if not xbmcvfs.exists(PROFILE):
            xbmcvfs.mkdir(PROFILE)
        if not xbmcvfs.exists(CACHE_FILE):
            return {}
        with xbmcvfs.File(CACHE_FILE, 'r') as fh:
            data = fh.read()
        return json.loads(data) if data else {}
    except Exception as e:
        log.warning(f"load failed: {e}")
        return {}


def _load() -> dict:
    global _cache_data
    if _cache_data is None:
        _cache_data = _load_from_disk()
    return _cache_data


def _save(data: dict) -> None:
    global _cache_data
    try:
        # atomic write
        temp_path = CACHE_FILE + ".tmp"
        with xbmcvfs.File(temp_path, 'w') as fh:
            fh.write(json.dumps(data, ensure_ascii=False, indent=2))
        if xbmcvfs.exists(CACHE_FILE):
            xbmcvfs.delete(CACHE_FILE)
        xbmcvfs.rename(temp_path, CACHE_FILE)
        _cache_data = data
    except Exception as e:
        log.error(f"save failed: {e}")


def upsert_result(ident: str, result: dict, ffprobe_version: str = None, series_name: str = None) -> None:
    """
    Merge (upsert) jednoho záznamu. Přepíše pouze známé klíče.
    series_name: volitelně storage key seriálu (pro mazání podle seriálu v experimentálním menu).
    """
    if not ident:
        return
    data = _load()
    node = dict(data.get(ident, {}))

    # jen whitelisted klíče
    for k in (
        "cz_subs", "subs_langs", "has_cz_audio",
        "audio_tracks", "best_audio", "video"
    ):
        if k in result:
            node[k] = result[k]

    node["probed_at"] = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime())
    if ffprobe_version:
        node["ffprobe_version"] = ffprobe_version
    if series_name is not None:
        sn = (series_name or "").strip()
        if sn:
            node["series_name"] = sn
        else:
            node.pop("series_name", None)

    data[ident] = node
    _save(data)
    log.info(f"upsert {ident} → {list(node.keys())}")


def get(ident: str) -> dict:
    return _load().get(ident, {})


def has_cz_subs(ident: str) -> bool:
    node = get(ident)
    return bool(node.get("cz_subs"))


def is_probed(ident: str) -> bool:
    """
    Vrátí True, pokud byl stream již úspěšně proskenován ffprobe 
    a máme k němu plná metadata (audio + video).
    """
    node = get(ident)
    return bool(node.get("video") and node.get("audio_tracks"))


def probe_backend(ident: str):
    """Určí, kterou větví vznikl probovaný záznam v cache.

    :returns:
        ``None`` — chybí ``ident`` nebo stream není probovaný (:func:`is_probed`).
        ``"ffprobe"`` — exec ffprobe (Windows), nebo legacy zápis s verzí ffprobe.
        ``"http_range"`` — HTTP range probe (Android apod.), viz ``http-range-*``.

    Pole ``ffprobe_version`` historicky nese buď reálnou verzi buildu ffprobe
    (``N-…-g…``), nebo značky ``windows-ffprobe`` / ``http-range-v1`` z
    :mod:`resources.lib.series.subs_probe`.
    """
    if not ident:
        return None
    if not is_probed(ident):
        return None
    ver = (get(ident).get("ffprobe_version") or "").strip().lower()
    if not ver:
        return "ffprobe"
    if "http-range" in ver or "http_range" in ver:
        return "http_range"
    if ver == "windows-ffprobe" or "ffprobe" in ver:
        return "ffprobe"
    if ver.startswith("n-") or "-g" in ver:
        return "ffprobe"
    return "ffprobe"


def has_cz_audio(ident: str) -> bool:
    node = get(ident)
    if node.get("has_cz_audio"):
        log.debug(f"has_cz_audio ident={ident} -> True (flat flag)")
        return True
    # fallback: z audio_tracks
    langs = [
        (tr.get("lang") or "").lower()
        for tr in (node.get("audio_tracks", []) or [])
    ]
    for lang in langs:
        if lang in _CZECH_LANG_CODES:
            log.debug(f"has_cz_audio ident={ident} -> True (audio_tracks lang={lang})")
            return True
    if langs:
        log.debug(f"has_cz_audio ident={ident} -> False (audio_tracks={langs})")
    else:
        log.debug(f"has_cz_audio ident={ident} -> False (no audio_tracks)")
    return False


def audio_badges(ident: str) -> list:
    """
    Vrátí list značek pro audio: např. ["CZdab","5.1","Atmos"] podle best_audio.

    Jazykový tag je sjednocen na `CZdab` (česká audio stopa).
    """
    node = get(ident)
    out = []
    ba = node.get("best_audio") or {}
    lang = (ba.get("lang") or "").lower()
    if lang in _CZECH_LANG_CODES:
        out.append("CZdab")
    # kanály
    ch = ba.get("channels")
    if isinstance(ch, int):
        if ch >= 8:
            out.append("7.1")
        elif ch >= 6:
            out.append("5.1")
        elif ch >= 2:
            out.append("2.0")
    # atmos
    if ba.get("atmos"):
        out.append("Atmos")
    log.debug(
        f"audio_badges ident={ident} lang={lang or 'und'} channels={ba.get('channels')} "
        f"atmos={bool(ba.get('atmos'))} out={out}"
    )
    return out


def video_badges(ident: str) -> list:
    """
    Vrátí list značek pro video: např. ["2160p","HDR10"].
    """
    node = get(ident)
    v = node.get("video") or {}
    out = []
    tag = v.get("resolution_tag")
    if tag:
        out.append(tag)
    if v.get("hdr"):
        out.append("HDR")
    if v.get("dv"):
        out.append("DV")
    return out


def _node_series_key(node: dict) -> str:
    """Normalizovaný series_name z uzlu nebo ''."""
    if not isinstance(node, dict):
        return ""
    return (node.get("series_name") or "").strip()


def clear_all() -> None:
    """Vymaže celou subcaps cache (všechny identy)."""
    _save({})
    _invalidate_memory_cache()
    log.info("subcaps_cache clear_all")


def list_series_in_cache():
    """
    Vrátí (seřazené unikátní series_name, počet legacy záznamů bez series_name).
    """
    data = _load()
    seen = set()
    legacy = 0
    for _ident, node in (data or {}).items():
        if not isinstance(node, dict):
            continue
        sk = _node_series_key(node)
        if sk:
            seen.add(sk)
        else:
            legacy += 1
    return sorted(seen), legacy


def remove_entries_for_series(series_name: str) -> int:
    """Smaže všechny záznamy s daným series_name. Vrátí počet smazaných klíčů."""
    target = (series_name or "").strip()
    if not target:
        return 0
    data = _load()
    to_del = [
        ident for ident, node in (data or {}).items()
        if isinstance(node, dict) and _node_series_key(node) == target
    ]
    for ident in to_del:
        data.pop(ident, None)
    if to_del:
        _save(data)
    log.info(f"remove_entries_for_series {target!r} → {len(to_del)}")
    return len(to_del)


def remove_entries_without_series() -> int:
    """Smaže záznamy bez přiřazeného series_name (staré probnutí)."""
    data = _load()
    to_del = [
        ident for ident, node in (data or {}).items()
        if isinstance(node, dict) and not _node_series_key(node)
    ]
    for ident in to_del:
        data.pop(ident, None)
    if to_del:
        _save(data)
    log.info(f"remove_entries_without_series → {len(to_del)}")
    return len(to_del)
