# resources/lib/tmdb_utils.py
# -*- coding: utf-8 -*-
import re
import os
import json
import requests  # TMDb API volání

try:
    import xbmc
    import xbmcaddon
    import xbmcvfs
except ImportError:
    pass

from resources.lib.tmdb.tmdb_module import API_URL
from resources.lib.utils.log_utils import get_logger

log = get_logger()

ADDON_ID = 'plugin.video.mmirousek_v3'
_addon = xbmcaddon.Addon(id=ADDON_ID)

# --- KONSTANTY ---
# Pokud budeš chtít, můžeš klíč brát ze Settings:
# TMDB_API_KEY = _addon.getSetting('tmdb_api_key') or "..."
TMDB_API_KEY = "f090bb54758cabf231fb605d3e3e0468"

TMDB_CACHE_FILE = os.path.join(
    xbmcvfs.translatePath(xbmcaddon.Addon(ADDON_ID).getAddonInfo('profile')),
    'tmdb_cache.json'
)

# --- Pomocná cache (SQLite - cache.db) -----------------------------------------
try:
    from resources.lib.trakt.trakt_service import FileCache
    tmdb_cache = FileCache(namespace='tmdb')
except Exception as e:
    log.error(f"[TMDB] Nelze inicializovat SQLite FileCache: {e}")
    tmdb_cache = None

# Hodnota TTL (Time To Live) pro TMDb odpovědi, např. 30 dní (v sekundách)
TMDB_TTL = 30 * 24 * 60 * 60

# ============================================================================
# ČIŠTĚNÍ NÁZVU – FILMY (zachováno + oprava regexů)
# ============================================================================
def clean_title(title: str) -> str:
    """
    Vyčistí název pro vyhledávání filmů na TMDb.
    - odstraní velikost (GB/MB) i když je za příponou,
    - odstraní příponu souboru,
    - normalizuje oddělovače (.-_ → mezera),
    - odstraní rok (1990–2030) z textu (rok můžeš mimo tuto funkci z title separátně vytáhnout),
    - odstraní tagy kvality/kodeků/ripu/jazyků (vč. H 264/H 265 s mezerou),
    - umí se „oříznout“ od zdroje/platformy (AMZN/NF/DSNP/…),
    - sundá audio kanály (5.1, 6ch, “channels”, “kanály”),
    - odstraní i „release group“ na úplném konci (ALLCAPS 4–15 znaků),
    - dočistí závorky a přebytečné mezery.
    """
    if not title:
        return ""
    # 1) Získat samotný filename (bez cesty a query parametrů)
    filename = title.split('/')[-1].split('?')[0] or title
    s = filename

    # 2) Odstranit velikost (v závorkách i bez), i když je ZA příponou
    s = re.sub(r'\s*\((?:\d+(?:\.\d+)?)\s*(GB|MB)\)\s*$', '', s, flags=re.IGNORECASE)
    s = re.sub(r'\s*(?:\d+(?:\.\d+)?)\s*(GB|MB)\s*$', '', s, flags=re.IGNORECASE)

    # 3) Odstranit příponu souboru (až po kroku 2)
    s = re.sub(
        r'\.(mkv|mp4|avi|mov|wmv|m4v|mpg|mpeg|ts|m2ts|webm|flv|vob)$',
        '', s, flags=re.IGNORECASE
    )

    # 4) Normalizovat oddělovače (.-_ → mezera)
    s = s.replace('.', ' ').replace('_', ' ').replace('-', ' ')
    s = re.sub(r'\s{2,}', ' ', s).strip()

    # 5) Rok 1990–2030 → odebrat z názvu (rok si případně ulož mimo tuto funkci)
    year_match = re.search(r'\b(19|20)\d{2}\b', s)
    if year_match:
        s = re.sub(rf'\b{year_match.group(0)}\b', '', s)

    # 6) Tokeny zdrojů/platforem – pokud je najdeme, vše vpravo od prvního výskytu odřízneme
    SOURCE_TOKENS = {
        'AMZN', 'NF', 'NETFLIX', 'DSNP', 'DISNEY', 'HMAX', 'HBOMAX', 'MAX',
        'HULU', 'IT', 'ITUNES', 'ATVP', 'APPLETV', 'PARAMOUNT', 'PEACOCK'
    }
    tokens = s.split()
    cut_idx = None
    for i, t in enumerate(tokens):
        t_up = re.sub(r'[^A-Z0-9]+', '', t.upper())
        if t_up in SOURCE_TOKENS:
            cut_idx = i
            break
    if cut_idx is not None:
        tokens = tokens[:cut_idx]
        s = ' '.join(tokens) if tokens else s

    # 7) “Tvrdé” odstranění technických tokenů (kvalita/kodeky/ripy/jazyky)
    NOISE_PATTERN = re.compile(
        r'\b('
        r'480p|720p|1080p|2160p|4k|8k|'
        r'webrip|web[\- ]?dl|b[dr]rip|bluray|remux|hdtv|dvd(?:rip)?|hdrip|'
        r'cam|telesync|telecine|scr|screener|r5|workprint|'
        r'h\.?264|h\.?265|x264|x265|hevc|avc|h\s*264|h\s*265|'
        r'dd(?:p)?|eac3|dts(?:\-hd)?(?:\s*ma)?|truehd|aac|flac|mp3|ac3|'
        r'atmos|multi|subs?|subpack|dubbed|'
        r'cz|cze|czech|sk|slovak|en|eng|english'
        r')\b', re.IGNORECASE
    )
    s = NOISE_PATTERN.sub('', s)

    # 8) Audio kanály a jejich překlepy: "6ch", "5.1 channels", "6 chanels", "kanály"
    s = re.sub(r'\b\d(?:\.\d)?\s*(ch|channels?|chanels|kanaly|kanálů)\b', '', s, flags=re.IGNORECASE)
    s = re.sub(r'\b(stereo|mono)\b', '', s, flags=re.IGNORECASE)

    # 9) Release group na úplném konci: ALLCAPS 4–15 znaků (např. BYNDR, RARBG, SPARKS, YTS...)
    s = re.sub(r'\b[A-Z0-9]{4,15}\b$', '', s)

    # 10) Zbytky závorek a whitespace
    s = re.sub(r'[\[\]\(\)\{\}]', '', s)
    s = re.sub(r'\s{2,}', ' ', s).strip()

    # 11) (Volitelné) speciální opravy známých titulů / lokalizací
    s = re.sub(r'Vykoupeni\s+z\s+veznice\s+Shawshank',
               'Vykoupení z věznice Shawshank', s, flags=re.IGNORECASE)

    log.debug(f'[TMDB] Fallback clean (movie): {filename} → "{s}"')
    return s

# ============================================================================
# ČIŠTĚNÍ A PARS – SERIÁLY (nové, oddělené od clean_title pro filmy)
# ============================================================================
# Robustní detekce epizod
RE_SxxEyy = re.compile(r'(?i)\bS(\d{1,2})\s*E(\d{1,3})\b')  # S01E02
RE_1x02   = re.compile(r'(?i)\b(\d{1,2})\s*[x×]\s*(\d{1,3})\b')  # 1x02 nebo 1×02

def light_clean_for_episode(name: str) -> str:
    """
    Lehk é očištění, které NEODSTRAŇUJE samotné epizodické označení
    (SxxEyy / 1x02), aby ho šly regexy spolehlivě vytáhnout.
    """
    s = (name or '').replace('.', ' ')
    # vyhoď šum, ale ponech "S..E.." / "1x.."
    s = re.sub(r'\b(480p|720p|1080p|2160p|4k)\b', '', s, flags=re.IGNORECASE)
    s = re.sub(r'\b(webrip|web[\- ]?dl|brrip|bluray|remux)\b', '', s, flags=re.IGNORECASE)
    s = re.sub(r'\b(x264|x265|hevc|hdr10|hdr|dv)\b', '', s, flags=re.IGNORECASE)
    s = re.sub(r'\s{2,}', ' ', s).strip(' -._()[]')
    return s

def parse_episode_title(title: str):
    """
    Vrátí tuple (show_name, season, episode) nebo None.
    """
    if not title:
        return None
    t = light_clean_for_episode(title)
    m = RE_SxxEyy.search(t)
    if m:
        season, episode = int(m.group(1)), int(m.group(2))
        show_name = t[:m.start()].strip(" -._()[]")
        return show_name, season, episode
    m = RE_1x02.search(t)
    if m:
        season, episode = int(m.group(1)), int(m.group(2))
        show_name = t[:m.start()].strip(" -._()[]")
        return show_name, season, episode
    return None

# ============================================================================
# TMDb VYHLEDÁVÁNÍ
# ============================================================================
def get_movie_meta(movie_id: int, language: str = 'cs') -> dict:
    """
    Vrátí metadata filmu z TMDb včetně žánrů, roku a posteru.
    """
    try:
        if not movie_id:
            return None
        lang_map = {'cs': 'cs-CZ', 'en': 'en-US', 'sk': 'sk-SK', 'de': 'de-DE', 'pl': 'pl-PL'}
        tmdb_lang = lang_map.get(language.lower(), f'{language.lower()}-{language.upper()}')
        url = f"https://api.themoviedb.org/3/movie/{int(movie_id)}"
        params = {"api_key": TMDB_API_KEY, "language": tmdb_lang}
        r = requests.get(url, params=params, timeout=8)
        if r.status_code != 200:
            log.warning(f"[TMDB] get_movie_meta HTTP {r.status_code} for movie_id={movie_id}")
            return None
        j = r.json() or {}
        title = j.get('title') or ''
        overview = j.get('overview') or ''
        release_date = j.get('release_date') or ''
        year = None
        if release_date and len(release_date) >= 4:
            try:
                year = int(release_date[:4])
            except:
                year = None
        poster_path = j.get('poster_path')
        genres = [g.get('name') for g in (j.get('genres') or []) if isinstance(g, dict) and g.get('name')]
        IMG_POSTER = "https://image.tmdb.org/t/p/w500"
        poster_url = f"{IMG_POSTER}{poster_path}" if poster_path else None
        result = {
            "movie_id": int(movie_id),
            "title": title,
            "overview": overview,
            "year": year,
            "release_date": release_date,
            "poster_path": poster_path,
            "poster_url": poster_url,
            "genres": genres,
        "backdrop_url": f"https://image.tmdb.org/t/p/original{j.get('backdrop_path')}" if j.get('backdrop_path') else None,
        "vote": float(j.get('vote_average')) if j.get('vote_average') is not None else None
        }
        # fallback EN pro title/overview
        if (not result["title"] or not result["overview"]) and tmdb_lang.lower() != 'en-us':
            try:
                r_en = requests.get(url, params={"api_key": TMDB_API_KEY, "language": "en-US"}, timeout=8)
                if r_en.status_code == 200:
                    j_en = r_en.json() or {}
                    if not result["title"]:
                        result["title"] = j_en.get('title') or result["title"]
                    if not result["overview"]:
                        result["overview"] = j_en.get('overview') or result["overview"]
            except Exception as e_fb:
                log.debug(f"[TMDB] EN fallback failed: {e_fb}")
        return result
    except Exception as e:
        log.error(f"[TMDB] get_movie_meta error: {e}")
        return None

def search_tmdb_movie(title: str):
    """
    Vyhledá film na TMDb podle názvu (s rokem, cache, fallback).
    Vrací: {"tmdb_id": ..., "imdb_id": ..., "title": ...} nebo None
    """
    if not title:
        return None
    filename = title.split('/')[-1].split('?')[0]
    cache_key_file = f"movie:{filename}"

    # 1) cache podle souboru
    if tmdb_cache:
        cached = tmdb_cache.get(cache_key_file)
        if cached and 'data' in cached:
            log.debug(f"[TMDB] Cache HIT (file): {filename}")
            return cached['data']

    # 2) čištění + rok
    clean = clean_title(title)
    year_match = re.search(r'\b(19|20)\d{2}\b', title)
    year = year_match.group(0) if year_match else None
    cache_key_clean = f"movie_clean:{clean}:{year or 'none'}"
    
    if tmdb_cache:
        cached = tmdb_cache.get(cache_key_clean)
        if cached and 'data' in cached:
            log.debug(f"[TMDB] Cache HIT (clean): {cache_key_clean}")
            result = cached['data']
            tmdb_cache.set(cache_key_file, result, None, TMDB_TTL)
            return result

    # 3) TMDb vyhledávání
    try:
        params = {
            'api_key': TMDB_API_KEY,
            'query': clean,
            'language': 'cs-CZ'
        }
        if year:
            params['year'] = year
        log.info(f"[TMDB] Hledám film: '{clean}' (rok: {year or '—'})")
        r = requests.get("https://api.themoviedb.org/3/search/movie", params=params, timeout=10)
        if r.status_code != 200:
            log.warning(f"[TMDB] HTTP chyba search/movie: {r.status_code}")
            return None
        data = r.json()
        if not data.get('results'):
            log.info(f"[TMDB] Žádné výsledky pro: '{clean}'")
            return None
        res = data['results'][0]
        movie_id = res['id']
        movie_title = res['title']

        # 4) IMDb ID
        details = requests.get(
            f"https://api.themoviedb.org/3/movie/{movie_id}",
            params={'api_key': TMDB_API_KEY},
            timeout=10
        )
        imdb_id = details.json().get('imdb_id') if details.status_code == 200 else None

        # 5) Cache (2x)
        result = {"tmdb_id": movie_id, "imdb_id": imdb_id, "title": movie_title, "poster_path": res.get("poster_path"), "overview": res.get("overview")}
        if tmdb_cache:
            tmdb_cache.set(cache_key_file, result, None, TMDB_TTL)
            tmdb_cache.set(cache_key_clean, result, None, TMDB_TTL)
        log.info(f"[TMDB] Nalezen film: '{movie_title}' (TMDb: {movie_id})")
        return result
    except Exception as e:
        log.error(f"[TMDB] Movie error: {e}")
        return None

def search_tmdb_tv(title: str):
    """
    Vyhledá seriál na TMDb podle názvu.
    Opravy:
      - Bezpečný přístp k results (IndexError při prázdném listu)
      - Fallback: pokud CZ vrátí 0 výsledků, zkus raw název (bez clean_title)
      - Ochrana před tv_id=None při volání external_ids
      - Fallback EN dotaz, pokud CZ nevrátí výsledky
    """
    if not title:
        return None
    clean = clean_title(title)
    
    # Cache: zkus oba klíče (clean i raw)
    if tmdb_cache:
        for _ck in (clean, title.strip()):
            cached = tmdb_cache.get(_ck)
            if cached and 'data' in cached:
                log.debug(f"[TMDB] Cache HIT pro '{_ck}'")
                return cached['data']
    try:
        log.debug(f"[TMDB] search_tmdb_tv: '{title}' (clean='{clean}')")
        url = "https://api.themoviedb.org/3/search/tv"

        def _first_result(j: dict) -> dict:
            """Bezpečně vrátí první výsledek nebo prázdný dict."""
            results = j.get('results') or []
            return results[0] if results else {}

        def _search(query: str, lang: str) -> dict:
            """Jední API volání, vrací první výsledek nebo {}."""
            try:
                r = requests.get(url, params={'api_key': TMDB_API_KEY, 'query': query, 'language': lang}, timeout=10)
                return _first_result(r.json() if r.status_code == 200 else {})
            except Exception as _e:
                log.warning(f"[TMDB] search_tmdb_tv _search error: {_e}")
                return {}

        # 1) CZ dotaz (clean název)
        res_cs = _search(clean, 'cs-CZ')

        # 2) Pokud CZ nic nenajde, zkus raw název (pro seriály s diakritikou jako 'Metoda Markovič')
        if not res_cs.get('id') and clean != title.strip():
            log.debug(f"[TMDB] CZ clean nenalezl, zkouším raw: '{title}'")
            res_cs = _search(title.strip(), 'cs-CZ')

        # 3) EN dotaz (vždy, pro original_title)
        res_en = _search(clean, 'en-US')
        if not res_en.get('id') and not res_cs.get('id'):
            res_en = _search(title.strip(), 'en-US')

        # 4) Preferování: CZ má vyšší prioritu, EN jako záloha
        res_primary = res_cs if res_cs.get('id') else res_en
        tv_id = res_primary.get('id')

        if not tv_id:
            log.info(f"[TMDB] search_tmdb_tv: nic nenalezeno pro '{title}'")
            return None

        # 5) External IDs - jen pokud máme platné tv_id
        external = {}
        try:
            ext = requests.get(
                f"https://api.themoviedb.org/3/tv/{int(tv_id)}/external_ids",
                params={'api_key': TMDB_API_KEY}, timeout=10
            )
            external = ext.json() if ext.status_code == 200 else {}
        except Exception as _e:
            log.warning(f"[TMDB] external_ids error pro tv_id={tv_id}: {_e}")

        result = {
            "tmdb_id": tv_id,
            "tvdb_id": external.get('tvdb_id'),
            "imdb_id": external.get('imdb_id'),
            "title": res_cs.get('name') or res_en.get('name') or clean,
            "original_title": res_en.get('original_name') or res_cs.get('original_name') or clean,
            "year": (res_primary.get('first_air_date') or '')[:4] or None,
            "poster_path": res_cs.get('poster_path') or res_en.get('poster_path'),
            "overview": res_cs.get('overview') or res_en.get('overview')
        }
        log.info(f"[TMDB] Nalezen seriál: CZ='{result['title']}', EN='{result['original_title']}', id={tv_id}")
        if tmdb_cache:
            tmdb_cache.set(clean, result, None, TMDB_TTL)
            if clean != title.strip():
                tmdb_cache.set(title.strip(), result, None, TMDB_TTL)  # ulož i raw název
        return result
    except Exception as e:
        log.error(f"[TMDB] search_tmdb_tv error: {e}")
        return None

def get_tv_external_ids(tv_id: int) -> dict:
    """
    Vrátí externí ID pro TV seriál z TMDb (imdb_id, tvdb_id, tvrage_id).
    Endpoint: /tv/{tv_id}/external_ids
    """
    try:
        if not tv_id:
            return {}
        url = f"https://api.themoviedb.org/3/tv/{int(tv_id)}/external_ids"
        params = {"api_key": TMDB_API_KEY}
        r = requests.get(url, params=params, timeout=8)
        if r.status_code != 200:
            log.warning(f"[TMDB] get_tv_external_ids HTTP {r.status_code} for tv_id={tv_id}")
            return {}
        j = r.json() or {}
        return j  # obsahuje imdb_id, tvdb_id, tvrage_id
    except Exception as e:
        log.error(f"[TMDB] get_tv_external_ids error: {e}")
        return {}



# ============================================================================
# KNIHOVNA (volitelně) – přidání .strm + .nfo
# ============================================================================
def add_to_library(item: dict, stream_url: str):
    """
    Automaticky přidá film/seriál do Kodi knihovny (vytvoří .strm + .nfo).
    - respektuje nastavení 'library_enabled' a 'library_folder'
    """
    try:
        addon = xbmcaddon.Addon(ADDON_ID)
        if not addon.getSettingBool('library_enabled'):
            return
        base_path = addon.getSetting('library_folder')
        if not base_path:
            base_path = xbmc.translatePath('special://profile/library/')
        addon.setSetting('library_folder', base_path)
        movies_path = os.path.join(base_path, 'Movies')
        tv_path = os.path.join(base_path, 'TV Shows')
        for p in [movies_path, tv_path]:
            if not xbmcvfs.exists(p + '/'):
                xbmcvfs.mkdirs(p)
        title = item.get('name', 'Neznámý')
        tmdb_id = item.get('tmdb_id')
        if not tmdb_id:
            log.warning('[Library] Chybí TMDb ID – přeskočeno')
            return
        # odfiltrovat “nebezpečné” znaky
        safe_title = "".join(c for c in title if c.isalnum() or c in " -_").rstrip()
        if 'movie' in item:
            folder = os.path.join(movies_path, safe_title)
            strm = os.path.join(folder, f"{safe_title}.strm")
            nfo_content = (
                f"<movie><title>{title}</title>"
                f"<uniqueid type='tmdb'>{tmdb_id}</uniqueid></movie>"
            )
        elif 'show' in item and 'episode' in item:
            show = item['show'].get('title') or title
            season = item['episode'].get('season')
            episode = item['episode'].get('number')
            show_safe = "".join(c for c in show if c.isalnum() or c in " -_").rstrip()
            season_path = os.path.join(tv_path, show_safe, f"Season {season}")
            ep_title = f"S{season:02d}E{episode:02d} - {title}"
            folder = season_path
            strm = os.path.join(season_path, f"{ep_title}.strm")
        if not xbmcvfs.exists(folder + '/'):
            xbmcvfs.mkdirs(folder)
        # xbmcvfs.File().write očekává bytes v Py3
        if not xbmcvfs.exists(strm):
            with xbmcvfs.File(strm, 'w') as f:
                f.write(stream_url.encode('utf-8'))
            with xbmcvfs.File(strm.replace('.strm', '.nfo'), 'w') as f:
                f.write(nfo_content.encode('utf-8'))
        xbmc.executebuiltin('UpdateLibrary(video)')
        log.info(f"[Library] Přidáno do knihovny: {title}")
    except Exception as e:
        log.error(f"[Library] Chyba přidávání: {e}")

# ============================================================================
# TMDb META (show & episode)
# ============================================================================
def get_tv_episode_meta(tv_id: int, season: int, episode: int, language: str = 'cs') -> dict:
    """
    Vrátí metadata epizody z TMDb + plnou URL k 'still' obrázku.
    - Cache klíč: f"ep:{tv_id}:S{season:02d}E{episode:02d}:{lang}"
    - Při chybě vrací None.
    """
    try:
        if not tv_id or season is None or episode is None:
            return None
        # Normalizace jazyka
        lang_map = {
            'cs': 'cs-CZ',
            'en': 'en-US',
            'sk': 'sk-SK',
            'de': 'de-DE',
            'pl': 'pl-PL'
        }
        tmdb_lang = lang_map.get(str(language).lower(), f'{language.lower()}-{language.upper()}')
        cache_key = f"ep:{int(tv_id)}:S{int(season):02d}E{int(episode):02d}:{tmdb_lang}"
        
        if tmdb_cache:
            cached = tmdb_cache.get(cache_key)
            if cached and 'data' in cached:
                log.debug(f"[TMDB] Cache HIT (episode): {cache_key}")
                return cached['data']
        
        if not TMDB_API_KEY:
            log.warning("[TMDB] get_tv_episode_meta: Chybí TMDB_API_KEY")
            return None
        # Volání TMDb
        url = f"https://api.themoviedb.org/3/tv/{int(tv_id)}/season/{int(season)}/episode/{int(episode)}"
        params = {"api_key": TMDB_API_KEY, "language": tmdb_lang}
        log.debug(f"[TMDB] GET {url} lang={tmdb_lang}")
        r = requests.get(url, params=params, timeout=8)
        if r.status_code != 200:
            log.warning(f"[TMDB] get_tv_episode_meta HTTP {r.status_code} for tv_id={tv_id} S{season}E{episode}")
            return None
        j = r.json() or {}
        ep_id = j.get('id')
        title = j.get('name') or ''
        overview = j.get('overview') or ''
        air_date = j.get('air_date') or None
        still_path = j.get('still_path') or None
        vote = None
        try:
            vote = float(j.get('vote_average')) if j.get('vote_average') is not None else None
        except Exception:
            vote = None
        # runtime v minutách (může být None – TMDb ho u epizod někdy nevrací)
        runtime = None
        try:
            rt = j.get('runtime')
            runtime = int(rt) if rt is not None else None
        except Exception:
            runtime = None
        IMG_STILL = "https://image.tmdb.org/t/p/w500"
        still_url = f"{IMG_STILL}{still_path}" if still_path else None
        result = {
            "episode_id": ep_id,
            "title": title,
            "overview": overview,
            "still_path": still_path,
            "still_url": still_url,
            "season": int(season),
            "episode": int(episode),
            "air_date": air_date,
            "vote": vote,
            "runtime": runtime,  # ← přidáno
        }
        # Lehký EN fallback, pokud chybí title i overview
        if (not result["title"] or not result["overview"]) and tmdb_lang.lower() != 'en-us':
            try:
                r_en = requests.get(url, params={"api_key": TMDB_API_KEY, "language": "en-US"}, timeout=8)
                if r_en.status_code == 200:
                    j_en = r_en.json() or {}
                    if not result["title"]:
                        result["title"] = j_en.get('name') or result["title"]
                    if not result["overview"]:
                        result["overview"] = j_en.get('overview') or result["overview"]
            except Exception as e_fallback:
                log.debug(f"[TMDB] Episode EN fallback failed: {e_fallback}")
        # Uložit do cache
        if tmdb_cache:
            tmdb_cache.set(cache_key, result, None, TMDB_TTL)
        return result
    except Exception as e:
        log.error(f"[TMDB] get_tv_episode_meta error: {e}")
        return None



def get_tv_show_meta(tv_id: int, language: str = 'cs') -> dict:
    """
    Vrátí metadata TV seriálu z TMDb včetně počtu sezón (number_of_seasons).
    """
    try:
        if not tv_id:
            return None
        lang_map = {'cs': 'cs-CZ', 'en': 'en-US', 'sk': 'sk-SK', 'de': 'de-DE', 'pl': 'pl-PL'}
        tmdb_lang = lang_map.get(language.lower(), f'{language.lower()}-{language.upper()}')

        cache_key = f"tv:{int(tv_id)}:{tmdb_lang}"
        if tmdb_cache:
            cached = tmdb_cache.get(cache_key)
            if cached and 'data' in cached:
                log.debug(f"[TMDB] Cache HIT (show): {cache_key}")
                return cached['data']

        if not TMDB_API_KEY:
            log.warning("[TMDB] get_tv_show_meta: Chybí TMDB_API_KEY")
            return None

        url = f"https://api.themoviedb.org/3/tv/{int(tv_id)}"
        params = {"api_key": TMDB_API_KEY, "language": tmdb_lang}
        log.debug(f"[TMDB] GET {url} lang={tmdb_lang}")
        r = requests.get(url, params=params, timeout=8)
        if r.status_code != 200:
            log.warning(f"[TMDB] get_tv_show_meta HTTP {r.status_code} for tv_id={tv_id}")
            return None

        j = r.json() or {}
        name = j.get('name') or ''
        original_name = j.get('original_name') or ''
        overview = j.get('overview') or ''
        first_air_date = j.get('first_air_date') or None
        year = None
        if first_air_date and len(first_air_date) >= 4:
            try:
                year = int(first_air_date[:4])
            except Exception:
                year = None

        poster_path = j.get('poster_path')
        backdrop_path = j.get('backdrop_path')
        IMG_POSTER = "https://image.tmdb.org/t/p/w500"
        IMG_FANART = "https://image.tmdb.org/t/p/original"
        poster_url = f"{IMG_POSTER}{poster_path}" if poster_path else None
        backdrop_url = f"{IMG_FANART}{backdrop_path}" if backdrop_path else None

        genres = [g.get('name') for g in (j.get('genres') or []) if isinstance(g, dict) and g.get('name')]
        networks = [n.get('name') for n in (j.get('networks') or []) if isinstance(n, dict) and n.get('name')]

        episode_run_time = None
        try:
            rts = j.get('episode_run_time') or []
            if isinstance(rts, list) and rts:
                episode_run_time = int(rts[0])
        except Exception:
            episode_run_time = None

        number_of_seasons = j.get('number_of_seasons')
        try:
            number_of_seasons = int(number_of_seasons) if number_of_seasons is not None else None
        except Exception:
            number_of_seasons = None

        result = {
            "tv_id": int(tv_id),
            "title": name or original_name,
            "original_title": original_name or name,
            "overview": overview or "",
            "year": year,
            "poster_path": poster_path,
            "backdrop_path": backdrop_path,
            "poster_url": poster_url,
            "backdrop_url": backdrop_url,
            "genres": genres,
            "networks": networks,
            "first_air_date": first_air_date,
            "status": j.get('status'),
            "episode_run_time": episode_run_time,
            "number_of_seasons": number_of_seasons,
            "vote": float(j.get('vote_average')) if j.get('vote_average') is not None else None,
        }

        if tmdb_cache:
            tmdb_cache.set(cache_key, result, None, TMDB_TTL)
        return result
    except Exception as e:
        log.error(f"[TMDB] get_tv_show_meta error: {e}")
        return None
    

def get_tv_season_meta(tv_id: int, season_num: int, language: str = 'cs') -> dict:
    """
    Vrátí metadata sezóny a seznam epizod z TMDb.

    Výstup:
    {
        "tv_id": int,
        "season_number": int,
        "episode_count": int,
        "episodes": [
            {"episode_number": int, "name": str|None, "air_date": "YYYY-MM-DD"|None}
            ...
        ],
        "poster_path": str|None,
        "poster_url": str|None
    }
    """
    try:
        if not tv_id or season_num is None:
            return None
        lang_map = {'cs': 'cs-CZ', 'en': 'en-US', 'sk': 'sk-SK', 'de': 'de-DE', 'pl': 'pl-PL'}
        tmdb_lang = lang_map.get(str(language).lower(), f'{language.lower()}-{language.upper()}')

        cache_key = f"season:{int(tv_id)}:S{int(season_num):02d}:{tmdb_lang}"
        if tmdb_cache:
            cached = tmdb_cache.get(cache_key)
            if cached and 'data' in cached:
                log.debug(f"[TMDB] Cache HIT (season): {cache_key}")
                return cached['data']

        if not TMDB_API_KEY:
            log.warning("[TMDB] get_tv_season_meta: Chybí TMDB_API_KEY")
            return None

        url = f"https://api.themoviedb.org/3/tv/{int(tv_id)}/season/{int(season_num)}"
        params = {"api_key": TMDB_API_KEY, "language": tmdb_lang}
        log.debug(f"[TMDB] GET {url} lang={tmdb_lang}")
        r = requests.get(url, params=params, timeout=8)
        if r.status_code != 200:
            log.warning(f"[TMDB] get_tv_season_meta HTTP {r.status_code} for tv_id={tv_id} S{season_num}")
            return None

        j = r.json() or {}
        episodes_json = j.get('episodes') or []
        episodes = []
        for ep in episodes_json:
            episodes.append({
                "episode_number": int(ep.get('episode_number')) if ep.get('episode_number') is not None else None,
                "name": ep.get('name') or None,
                "air_date": ep.get('air_date') or None
            })

        poster_path = j.get('poster_path')
        IMG_POSTER = "https://image.tmdb.org/t/p/w500"
        poster_url = f"{IMG_POSTER}{poster_path}" if poster_path else None

        result = {
            "tv_id": int(tv_id),
            "season_number": int(season_num),
            "episode_count": len([e for e in episodes if isinstance(e.get('episode_number'), int)]),
            "episodes": episodes,
            "poster_path": poster_path,
            "poster_url": poster_url,
        }

        if tmdb_cache:
            tmdb_cache.set(cache_key, result, None, TMDB_TTL)
        return result
    except Exception as e:
        log.error(f"[TMDB] get_tv_season_meta error: {e}")
        return None



def filter_streams_by_tmdb(tv_id: int, season: int, episode: int, streams: list) -> list:
    """
    Odfiltruje streamy, které neodpovídají epizodě podle TMDb:
    1) Sezóna musí být <= number_of_seasons (TMDb show meta)
    2) Epizoda musí existovat v sezóně (TMDb season meta)
    3) Název streamu musí obsahovat SxxExx nebo 1x02 odpovídající dané epizodě
    4) Název streamu musí obsahovat slug názvu seriálu (TMDb title)
    5) Pokud TMDb sezóna má např. 10 epizod, zahodíme epizody > 10
    6) Pokud v názvu streamu je rok a liší se od TMDb roku, zahodíme
    Vrací: vyfiltrovaný seznam streamů (původní pořadí zachováno).
    """
    try:
        from resources.lib.tmdb.tmdb_utils import RE_SxxEyy, RE_1x02, light_clean_for_episode
        from resources.lib.tmdb.tmdb_utils import get_tv_show_meta, get_tv_season_meta
    except ImportError:
        log.error("[TMDB Filter] Modul tmdb_utils nelze importovat")
        return streams or []

    try:
        # 1) Načti metadata seriálu
        show_meta = get_tv_show_meta(tv_id, language='cs') or {}
        num_seasons = show_meta.get('number_of_seasons')
        title = show_meta.get('title') or ''
        slug_title = re.sub(r'[^a-z0-9]', '', title.lower()) if title else ''
        tmdb_year = show_meta.get('year')  # z first_air_date

        if not isinstance(num_seasons, int) or num_seasons <= 0:
            log.warning(f"[TMDB Filter] Chybí validní počet sezón pro TMDb ID={tv_id}")
            return streams or []

        if int(season) > num_seasons:
            log.warning(f"[TMDB Filter] Sezóna {season} mimo rozsah TMDb ({num_seasons}) → žádné streamy")
            return []

        # 2) Načti metadata sezóny
        season_meta = get_tv_season_meta(tv_id, int(season), language='cs') or {}
        episodes_meta = season_meta.get('episodes') or []
        ep_numbers = {ep.get('episode_number') for ep in episodes_meta if isinstance(ep.get('episode_number'), int)}
        max_ep = max(ep_numbers) if ep_numbers else None

        if int(episode) not in ep_numbers:
            log.warning(f"[TMDB Filter] Epizoda S{season:02d}E{episode:02d} v TMDb sezóně neexistuje → žádné streamy")
            return []

        # 3) Filtrování streamů
        valid = []
        for st in streams or []:
            name = st.get('name') or ''
            cleaned = light_clean_for_episode(name)
            m = RE_SxxEyy.search(cleaned) or RE_1x02.search(cleaned)
            if not m:
                log.debug(f"[TMDB Filter] Stream vyřazen (bez SxxExx/1x02): {name}")
                continue

            s_match = int(m.group(1))
            e_match = int(m.group(2))

            # Shoda sezóny/epizody
            if s_match != int(season) or e_match != int(episode):
                log.debug(f"[TMDB Filter] Stream vyřazen (S/E mismatch {s_match}/{e_match} vs {season}/{episode}): {name}")
                continue

            # Slug kontrola názvu seriálu
            name_slug = re.sub(r'[^a-z0-9]', '', name.lower())
            if slug_title and slug_title not in name_slug:
                log.debug(f"[TMDB Filter] Stream vyřazen (název neobsahuje slug '{slug_title}'): {name}")
                continue

            # Max počet epizod v sezóně
            if max_ep and e_match > max_ep:
                log.debug(f"[TMDB Filter] Stream vyřazen (epizoda {e_match} > max {max_ep}): {name}")
                continue

            # Validace roku
            year_match = re.search(r'\b(19|20)\d{2}\b', name)
            if year_match and tmdb_year and int(year_match.group(0)) != int(tmdb_year):
                log.debug(f"[TMDB Filter] Stream vyřazen (rok {year_match.group(0)} != TMDb {tmdb_year}): {name}")
                continue

            valid.append(st)

        log.info(f"[TMDB Filter] Validní streamy: {len(valid)} z původních {len(streams)}")
        return valid

    except Exception as e:
        log.error(f"[TMDB Filter] Selhalo: {e}")


# ============================================================================
# EXTERNAL ID REGEX PATTERNS + HELPERY (NOVÉ)
# ============================================================================
# IMDb: 'tt' + 7–9 číslic
RE_IMDB_ID_URL   = re.compile(r'(?:imdb\.com/title/)(tt\d{7,9})', re.IGNORECASE)
RE_IMDB_ID_PARAM = re.compile(r'(?:[?&]imdb[_\-]?id=)(tt\d{7,9})', re.IGNORECASE)
RE_IMDB_ID_RAW   = re.compile(r'\b(tt\d{7,9})\b', re.IGNORECASE)

# TMDb: movie/tv stránky, query params i „volné“ tagy
RE_TMDB_MOVIE_URL   = re.compile(r'(?:themoviedb\.org/movie/)(\d+)', re.IGNORECASE)
RE_TMDB_TV_URL      = re.compile(r'(?:themoviedb\.org/tv/)(\d+)', re.IGNORECASE)
RE_TMDB_ID_PARAM    = re.compile(r'(?:[?&]tmdb[_\-]?id=)(\d+)', re.IGNORECASE)  # obecný tmdb_id
RE_TMDB_MOVIE_PARAM = re.compile(r'(?:[?&]tmdb[_\-]?movie[_\-]?id=)(\d+)', re.IGNORECASE)
RE_TMDB_TV_PARAM    = re.compile(r'(?:[?&]tmdb[_\-]?tv[_\-]?id=)(\d+)', re.IGNORECASE)
RE_TMDB_ID_RAW      = re.compile(r'\b(?:tmdb[_\-]?id|tmdb[_\-]?movie[_\-]?id|tmdb[_\-]?tv[_\-]?id)\s*[:=]\s*(\d+)\b', re.IGNORECASE)

# TVDB: různé cesty + query param + „volný“ tag
RE_TVDB_URL_2   = re.compile(r'(?:thetvdb\.com/(?:series/.+?/|dereferrer/series/))(\d+)', re.IGNORECASE)
RE_TVDB_ID_PARAM= re.compile(r'(?:[?&]tvdb[_\-]?id=)(\d+)', re.IGNORECASE)
RE_TVDB_ID_RAW  = re.compile(r'\b(?:tvdb[_\-]?id)\s*[:=]\s*(\d+)\b', re.IGNORECASE)

# Webshare ident (typické ?id=<digits/alpha>)
RE_WEBSHARE_IDENT= re.compile(r'(?:webshare\.cz/.*?[?&]id=)([A-Za-z0-9]+)', re.IGNORECASE)

# Volitelné „obaly“ v názvu souboru: [tmdb:12345], (imdb:tt1234567), {tvdb:98765}
RE_TAGGED_IDS = re.compile(
    r'[\[\(\{]\s*(imdb|tmdb|tvdb)\s*[:=]\s*(tt\d{7,9}|\d+)\s*[\]\)\}]',
    re.IGNORECASE
)

def extract_external_ids(text: str) -> dict:
    """
    Z textu (URL, filename, metadata) vytáhne externí ID:
      - imdb: 'tt1234567' (string)
      - tmdb_movie: int
      - tmdb_tv: int
      - tmdb: int (obecné, pokud nevíme typ)
      - tvdb: int
      - webshare_ident: str
    Preferuje explicitní typy; 'tmdb' doplní jen když neexistuje movie/tv varianta.
    Vrací dict jen s nalezenými klíči.
    """
    ids = {}
    if not text:
        return ids

    # IMDb
    for rex in (RE_IMDB_ID_URL, RE_IMDB_ID_PARAM, RE_IMDB_ID_RAW):
        m = rex.search(text)
        if m:
            ids['imdb'] = m.group(1)
            break

    # TMDb - movie / tv / generic
    m = RE_TMDB_MOVIE_URL.search(text) or RE_TMDB_MOVIE_PARAM.search(text)
    if m:
        try: ids['tmdb_movie'] = int(m.group(1))
        except: pass

    m = RE_TMDB_TV_URL.search(text) or RE_TMDB_TV_PARAM.search(text)
    if m:
        try: ids['tmdb_tv'] = int(m.group(1))
        except: pass

    m = RE_TMDB_ID_PARAM.search(text) or RE_TMDB_ID_RAW.search(text)
    if m and 'tmdb_movie' not in ids and 'tmdb_tv' not in ids:
        try: ids['tmdb'] = int(m.group(1))
        except: pass

    # TVDB
    m = RE_TVDB_URL_2.search(text) or RE_TVDB_ID_PARAM.search(text) or RE_TVDB_ID_RAW.search(text)
    if m:
        try: ids['tvdb'] = int(m.group(1))
        except: pass

    # Webshare
    m = RE_WEBSHARE_IDENT.search(text)
    if m:
        ids['webshare_ident'] = m.group(1)

    # Tagged ids like [tmdb:12345] ...
    for tag in RE_TAGGED_IDS.findall(text):
        label, value = tag[0], tag[1]
        if label.lower() == 'imdb' and 'imdb' not in ids and value.lower().startswith('tt'):
            ids['imdb'] = value
        elif label.lower() == 'tmdb':
            try:
                v = int(value)
                # pokud už máme tmdb_movie/tv, nechte je; jinak generický
                if 'tmdb_movie' not in ids and 'tmdb_tv' not in ids and 'tmdb' not in ids:
                    ids['tmdb'] = v
            except:
                pass
        elif label.lower() == 'tvdb':
            try:
                v = int(value)
                if 'tvdb' not in ids:
                    ids['tvdb'] = v
            except:
                pass

    return ids

def merge_ids(target: dict, extracted: dict, media_type: str = None) -> dict:
    """
    Doplní ID z 'extracted' do 'target' (bez přepsání existujících klíčů).
    - Pokud media_type == 'movie' → preferuje tmdb_movie.
      Pokud 'episode' → preferuje tmdb_tv.
    - Vždy doplní 'imdb', 'tvdb', 'webshare_ident', pokud chybí.
    """
    out = dict(target or {})
    mt = media_type or out.get('media_type')

    # TMDb
    if not out.get('tmdb_id'):
        if mt == 'movie' and extracted.get('tmdb_movie'):
            out['tmdb_id'] = str(extracted['tmdb_movie'])
        elif mt == 'episode' and extracted.get('tmdb_tv'):
            out['tmdb_id'] = str(extracted['tmdb_tv'])
        elif extracted.get('tmdb'):
            out['tmdb_id'] = str(extracted['tmdb'])

    # IMDb
    if extracted.get('imdb') and not out.get('imdb_id'):
        out['imdb_id'] = extracted['imdb']

    # TVDB → epizody (Trakt payload / show.ids.tvdb si nastavíš mimo)
    if extracted.get('tvdb') and not out.get('tvdb_id'):
        out['tvdb_id'] = str(extracted['tvdb'])

    # Webshare ident
    if extracted.get('webshare_ident') and not out.get('webshare_ident'):
        out['webshare_ident'] = extracted['webshare_ident']

    return out


def tmdb_api_keys_try_order():
    """
    Pořadí klíčů pro TMDb: nejdřív tmdb_api_key z nastavení, pak vestavěný TMDB_API_KEY.
    Stejné pořadí jako u „fungujícího“ EN dotazu po doplnění fallbacku — špatný neprázdný
    klíč v nastavení sám o sobě nesmí zablokovat cs-CZ (dříve EN šel přes konstantu, cs jen přes settings).
    """
    keys = []
    try:
        s = (_addon.getSetting("tmdb_api_key") or "").strip()
    except Exception:
        s = ""
    if s:
        keys.append(s)
    if TMDB_API_KEY and TMDB_API_KEY not in keys:
        keys.append(TMDB_API_KEY)
    return keys


def get_english_title(media_type: str, tmdb_id: int) -> str:
    """
    Stáhne anglický název pro daný film/seriál, použije se jen,
    pokud original_language není 'en' nebo 'cs'.
    """
    if not tmdb_id:
        return None

    path = f"/{media_type}/{tmdb_id}"
    last_err = None
    for api_key in tmdb_api_keys_try_order():
        if not api_key:
            continue
        params = {"api_key": api_key, "language": "en-US"}
        try:
            r = requests.get(API_URL + path, params=params, timeout=5)
            if r.status_code == 401:
                last_err = "401"
                continue
            r.raise_for_status()
            data = r.json()
            return data.get("title") or data.get("name")
        except Exception as e:
            last_err = e
            continue
    log.warning(f"[TMDB_UTILS] Failed to fetch EN title for {tmdb_id}: {last_err}")
    return None


def get_tmdb_title_for_language_diag(
    media_type: str, tmdb_id: int, language: str = "cs-CZ", api_keys=None
):
    """
    Jako get_tmdb_title_for_language, ale vrací (název_nebo_None, důvod_prázdného).
    Důvod je \"\" při úspěchu (vhodné pro logování ve VOD SA batch).
    """
    if not tmdb_id:
        return None, "no_tmdb_id"
    keys = api_keys if api_keys is not None else tmdb_api_keys_try_order()
    if not keys:
        return None, "no_api_keys"
    path = "/{}/{}".format(media_type, int(tmdb_id))
    last_err = None
    last_status = None
    for api_key in keys:
        if not api_key:
            continue
        params = {"api_key": api_key, "language": language}
        try:
            r = requests.get(API_URL + path, params=params, timeout=6)
            last_status = r.status_code
            if r.status_code == 401:
                last_err = "401"
                continue
            if r.status_code == 404:
                return None, "http_404_wrong_type_or_id"
            r.raise_for_status()
            data = r.json()
            title = data.get("title") or data.get("name")
            if title:
                s = str(title).strip()
                if s:
                    return s, ""
            return None, "empty_title_in_json"
        except Exception as e:
            last_err = str(e)
            continue
    tail = "status=%s err=%s" % (last_status, last_err)
    return None, "keys_exhausted:" + tail


def get_tmdb_title_for_language(
    media_type: str, tmdb_id: int, language: str = "cs-CZ", api_keys=None
):
    """
    Vrátí lokalizovaný název filmu/seriálu z TMDb (title / name).
    media_type: 'movie' nebo 'tv'.
    api_keys: volitelný seznam klíčů (např. z hlavního vlákna před ThreadPool v Kodi);
              None = tmdb_api_keys_try_order() uvnitř volání.
    """
    title, reason = get_tmdb_title_for_language_diag(
        media_type, tmdb_id, language=language, api_keys=api_keys
    )
    if reason:
        log.debug(
            f"[TMDB_UTILS] title lang={language} {media_type}/{tmdb_id} fail={reason}"
        )
    return title
