# -*- coding: utf-8 -*-
"""Sdílené utility pro offline_cache a dfolder – bez závislosti na SeriesManager."""

import json
import re

import xbmcaddon
import xbmcvfs

from resources.lib.database.metadata_db import db as _metadata_db
from resources.lib.utils.log_utils import get_logger
from resources.lib.utils.ui_utils import clean_filename

log = get_logger()

ADDON_ID = 'plugin.video.mmirousek_v3'

_RE_STORAGE_TMDB = re.compile(r"^tmdb_\d+$", re.IGNORECASE)
_SEASON_DIR_RE = re.compile(
    r'(?i)(?:'
    r'(?:Season|Sezona|Sezóna)\s*(\d+)'
    r'|S(\d{1,2})'
    r')'
)
_VIDEO_EXTS = ('.mp4', '.mkv', '.avi', '.ts')


def parse_season_dir_name(dirname: str):
    """Vrátí číslo sezóny z názvu složky (Sezona 4, Season 4, S04, …)."""
    m = _SEASON_DIR_RE.search(dirname or '')
    if not m:
        return None
    raw = m.group(1) or m.group(2)
    try:
        return int(raw)
    except (TypeError, ValueError):
        return None


def _series_root_path(dfolder: str, dirname: str) -> str:
    path = f"{dfolder.rstrip('/')}/{dirname}/"
    return path.replace('\\', '/')


def _looks_like_series_folder(dfolder: str, dirname: str) -> bool:
    """True pokud složka vypadá jako seriál (sezóny nebo SxxExx soubory)."""
    path = _series_root_path(dfolder, dirname)
    if not xbmcvfs.exists(path):
        return False
    try:
        subdirs, files = xbmcvfs.listdir(path)
    except Exception:
        return False
    for sd in subdirs:
        if parse_season_dir_name(sd) is not None:
            return True
    for fname in files:
        low = fname.lower()
        if low.endswith(_VIDEO_EXTS) and re.search(r'(?i)S\d+E\d+', fname):
            return True
    return False


def list_series_root_dirs(dfolder: str = None) -> list:
    """Vrátí názvy podsložek v dfolder, které vypadají jako seriály."""
    dfolder = dfolder or resolve_dfolder_path()
    if not dfolder or not xbmcvfs.exists(dfolder):
        return []
    try:
        dirs, _files = xbmcvfs.listdir(dfolder)
    except Exception:
        return []
    return [d for d in dirs if _looks_like_series_folder(dfolder, d)]


def discover_existing_disk_folder_name(
    series_name: str, series_data: dict = None, dfolder: str = None
) -> str:
    """Najde existující složku seriálu na disku (surový název z listdir)."""
    dfolder = dfolder or resolve_dfolder_path()
    if not dfolder:
        return ''

    storage_key = clean_filename(series_name)
    folder_index = build_disk_folder_to_storage_key_index()
    expected_human = ''
    if isinstance(series_data, dict):
        expected_human = clean_filename(
            resolve_folder_display_name(series_name, series_data)
        )

    ranked = []
    for dirname in list_series_root_dirs(dfolder):
        cd = clean_filename(dirname)
        if expected_human and cd == expected_human and not _RE_STORAGE_TMDB.match(dirname):
            ranked.append((0, dirname))
        elif folder_index.get(cd) == storage_key and not _RE_STORAGE_TMDB.match(dirname):
            ranked.append((1, dirname))
        elif cd == storage_key and _RE_STORAGE_TMDB.match(dirname):
            ranked.append((2, dirname))
        elif folder_index.get(cd) == storage_key:
            ranked.append((3, dirname))

    if not ranked:
        return ''
    ranked.sort(key=lambda x: x[0])
    return ranked[0][1]


def resolve_series_folder_label(
    series_name: str,
    series_data: dict = None,
    dfolder: str = None,
    hint_display_name: str = None,
) -> str:
    """Název složky seriálu pro nové stahování – preferuje existující disk."""
    existing = discover_existing_disk_folder_name(series_name, series_data, dfolder)
    if existing:
        return clean_filename(existing)

    for candidate in (
        hint_display_name,
        resolve_folder_display_name(series_name, series_data or {}) if series_data else '',
        series_name,
    ):
        label = (candidate or '').strip()
        if label and not _RE_STORAGE_TMDB.match(label):
            return clean_filename(label)
    return clean_filename(series_name)


def merge_offline_scan_items(existing: list, new_items: list) -> list:
    """Sloučí položky offline scanu podle (season, episode)."""
    merged = {}
    for it in (existing or []) + (new_items or []):
        try:
            sig = (int(it.get('season', -1)), int(it.get('episode', -1)))
        except (TypeError, ValueError):
            continue
        merged[sig] = it
    return list(merged.values())


def resolve_folder_display_name(series_name: str, data: dict) -> str:
    """
    Název složky na disku: preferuj lidský titul z DB/TMDb/search query,
    storage key `tmdb_<id>` používej jen jako poslední fallback.
    """
    if not isinstance(data, dict):
        data = {}

    def _usable_title(value) -> str:
        value = (value or "").strip()
        return value if value and not _RE_STORAGE_TMDB.match(value) else ""

    title = _usable_title(data.get("name"))
    if title:
        return title

    tmdb = data.get("tmdb") or {}
    if isinstance(tmdb, dict):
        for key in ("title", "name", "original_title", "original_name"):
            title = _usable_title(tmdb.get(key))
            if title:
                return title

    title = _usable_title(data.get("search_query_name"))
    if title:
        return title

    aliases = data.get("aliases") or []
    if isinstance(aliases, list):
        for alias in aliases:
            title = _usable_title(alias)
            if title:
                return title

    fallback = (series_name or "").strip()
    if _RE_STORAGE_TMDB.match(fallback):
        log.debug(f"[offline] Používám storage key jako fallback názvu složky: {fallback}")
    return fallback


def sync_active_dfolder_setting(addon=None):
    """Zkopíruje aktivní slot (dfolder_1/2/3) do nastavení dfolder."""
    try:
        addon = addon or xbmcaddon.Addon(ADDON_ID)
        raw_slot = addon.getSetting('dfolder_slot') or '0'
        slot_idx = 0
        if raw_slot == 'Složka 1':
            slot_idx = 0
        elif raw_slot == 'Složka 2':
            slot_idx = 1
        elif raw_slot == 'Složka 3':
            slot_idx = 2
        else:
            try:
                slot_idx = int(raw_slot)
            except ValueError:
                slot_idx = 0
        slot_path = addon.getSetting(f'dfolder_{slot_idx + 1}') or ''
        if slot_path and slot_path != (addon.getSetting('dfolder') or ''):
            addon.setSetting('dfolder', slot_path)
    except Exception as e:
        log.debug(f"[offline] sync_active_dfolder_setting: {e}")


def resolve_dfolder_path(addon=None):
    """Vrátí přeloženou VFS cestu aktivní offline složky (s trailing /)."""
    addon = addon or xbmcaddon.Addon(ADDON_ID)
    sync_active_dfolder_setting(addon)
    raw = (addon.getSetting('dfolder') or '').strip()
    if not raw:
        return ''
    try:
        real = xbmcvfs.translatePath(raw) or raw
    except Exception:
        real = raw
    if not real:
        return ''
    if not xbmcvfs.exists(real) and raw != real and xbmcvfs.exists(raw):
        real = raw
    real = real.replace('\\', '/')
    if not real.endswith('/'):
        real += '/'
    return real


def resolve_offline_cache_key_for_disk_folder(folder_name: str, folder_index: dict = None) -> str:
    """Mapuje složku na disku na storage klíč seriálu (tmdb_*), pokud je známý z DB."""
    folder_key = clean_filename(folder_name)
    if not folder_key:
        return ''
    if _RE_STORAGE_TMDB.match(folder_name):
        return folder_key
    if folder_index is None:
        folder_index = build_disk_folder_to_storage_key_index()
    return folder_index.get(folder_key, folder_key)


def offline_cache_key_for_disk_folder(disk_folder_name: str) -> str:
    """Klíč pro uložení do offline_cache = skutečný název složky na disku."""
    return clean_filename(disk_folder_name)


def offline_cache_lookup_keys(
    series_name: str,
    series_data: dict = None,
    cache_all: dict = None,
    folder_index: dict = None,
) -> list:
    """Všechny možné klíče offline_cache pro seriál (storage key + název složky na disku).

    Čistě DB/paměťová operace (žádné xbmcvfs I/O) — bezpečné volat opakovaně
    v cyklu přes všechny seriály v menu. Pro hromadné volání (menu s N seriály)
    předej ``cache_all``/``folder_index`` předpočítané jednou mimo cyklus,
    jinak se dopočítají lokálně (o něco dražší, ale stále jen SQL SELECT, ne FS scan).
    """
    keys = []

    def _add(value):
        key = clean_filename(value) if value else ''
        if key and key not in keys:
            keys.append(key)

    _add(series_name)
    if isinstance(series_data, dict):
        _add(resolve_folder_display_name(series_name, series_data))
        _add(series_data.get('name'))

    # Reverzní párování: cache uložená pod lidským názvem složky na disku.
    # Pracuje čistě s daty už načtenými z DB (cesty v offline_cache, series_data) —
    # bez dalšího přístupu na disk (žádné xbmcvfs.listdir).
    sk = clean_filename(series_name)
    if sk:
        try:
            _folder_index = folder_index
            if _folder_index is None:
                _folder_index = build_disk_folder_to_storage_key_index()
            _cache_all = cache_all
            if _cache_all is None:
                _cache_all = _metadata_db.get_offline_cache_all() or {}
            for cache_key, items in _cache_all.items():
                if cache_key in keys:
                    continue
                ck = clean_filename(cache_key)
                if ck == sk or _folder_index.get(ck) == sk:
                    _add(cache_key)
                    continue
                disk = disk_folder_name_from_cache_items(cache_key, items)
                if not disk:
                    continue
                dk = clean_filename(disk)
                if dk == sk or _folder_index.get(dk) == sk:
                    _add(cache_key)
        except Exception as e:
            log.debug(f"[offline] reverse cache lookup failed: {e}")

    return keys


def build_disk_folder_to_storage_key_index() -> dict:
    """Mapa clean_filename(název_složky_na_disku) -> clean_filename(storage_key)."""
    index = {}
    if not _metadata_db.conn:
        return index
    try:
        cur = _metadata_db.conn.execute("SELECT series_name, json_data FROM series_data")
        for storage_key, json_blob in cur.fetchall():
            try:
                data = json.loads(json_blob or '{}')
            except Exception:
                data = {}
            sk = clean_filename(storage_key)
            if sk:
                index[sk] = sk
            for candidate in (
                resolve_folder_display_name(storage_key, data),
                (data.get('name') or ''),
            ):
                folder_key = clean_filename(candidate)
                if folder_key:
                    index[folder_key] = sk or folder_key
    except Exception as e:
        log.warning(f"[offline] Nelze sestavit index složek seriálů: {e}")
    return index


def get_merged_offline_cache(
    series_name: str,
    series_data: dict = None,
    cache_all: dict = None,
    folder_index: dict = None,
) -> list:
    """Sloučí offline_cache záznamy ze všech aliasů seriálu (bez duplicit S/E)."""
    merged = []
    seen = set()
    for key in offline_cache_lookup_keys(series_name, series_data, cache_all=cache_all, folder_index=folder_index):
        items_source = cache_all.get(key) if cache_all is not None else (_metadata_db.get_offline_cache_for_series(key) or [])
        for item in items_source or []:
            try:
                sig = (int(item.get('season', -1)), int(item.get('episode', -1)))
            except (TypeError, ValueError):
                continue
            if sig in seen:
                continue
            seen.add(sig)
            merged.append(item)
    return merged


def _dfolder_path_prefixes(dfolder: str = None) -> list:
    """Možné prefixy cesty dfolder (přeložené + raw z nastavení)."""
    prefixes = []

    def _add(path: str):
        p = (path or '').replace('\\', '/').strip()
        if not p:
            return
        if not p.endswith('/'):
            p += '/'
        if p not in prefixes:
            prefixes.append(p)

    _add(dfolder or resolve_dfolder_path())
    try:
        addon = xbmcaddon.Addon(ADDON_ID)
        for key in ('dfolder', 'dfolder_1', 'dfolder_2', 'dfolder_3'):
            raw = addon.getSetting(key) or ''
            if not raw:
                continue
            try:
                _add(xbmcvfs.translatePath(raw) or raw)
            except Exception:
                _add(raw)
            _add(raw)
    except Exception:
        pass
    return prefixes


def series_folder_from_episode_path(ep_path: str, dfolder: str = None) -> str:
    """Název složky seriálu z plné cesty epizody (…/Seriál/Sezona N/SxxExx.mkv)."""
    norm = (ep_path or '').replace('\\', '/')
    if not norm:
        return ''
    for prefix in _dfolder_path_prefixes(dfolder):
        if norm.startswith(prefix):
            rel = norm[len(prefix):].lstrip('/')
            if rel:
                return rel.split('/')[0]
    parts = [p for p in norm.split('/') if p]
    if len(parts) >= 3:
        return parts[-3]
    return ''


def disk_folder_name_from_cache_items(cache_key: str, cache_items: list = None) -> str:
    """Nejčastější název složky seriálu podle cest v offline_cache (spolehlivé i bez DB)."""
    items = cache_items
    if items is None:
        items = _metadata_db.get_offline_cache_for_series(cache_key) or []
    if not items:
        return ''

    counts = {}
    for item in items:
        folder = series_folder_from_episode_path(item.get('path', ''))
        if folder:
            counts[folder] = counts.get(folder, 0) + 1
    if not counts:
        return ''

    ranked = sorted(
        counts.items(),
        key=lambda x: (
            1 if _RE_STORAGE_TMDB.match(x[0]) else 0,
            -x[1],
            x[0].lower(),
        ),
    )
    return ranked[0][0]


def _list_dfolder_subdirs(dfolder: str = None) -> list:
    """Všechny podsložky v dfolder (ne jen ty s detekovanou sezónní strukturou)."""
    dfolder = dfolder or resolve_dfolder_path()
    if not dfolder or not xbmcvfs.exists(dfolder):
        return []
    try:
        dirs, _files = xbmcvfs.listdir(dfolder)
    except Exception:
        return []
    series_like = [d for d in dirs if _looks_like_series_folder(dfolder, d)]
    return series_like or list(dirs)


def disk_folder_names_for_storage_key(cache_key: str, dfolder: str = None) -> list:
    """Vrátí názvy složek na disku patřící ke storage klíči (lidské před tmdb_*)."""
    dfolder = dfolder or resolve_dfolder_path()
    sk = clean_filename(cache_key)
    if not dfolder or not sk:
        return []

    folder_index = build_disk_folder_to_storage_key_index()
    found = []
    seen = set()
    for dirname in _list_dfolder_subdirs(dfolder):
        cd = clean_filename(dirname)
        matched = cd == sk or folder_index.get(cd) == sk
        if matched and dirname not in seen:
            seen.add(dirname)
            found.append(dirname)

    found.sort(key=lambda n: (1 if _RE_STORAGE_TMDB.match(n) else 0, n.lower()))
    return found


def resolve_offline_cache_display_label(cache_key: str, cache_items: list = None) -> str:
    """Lidský název seriálu pro UI (Manager stažených)."""
    key = (cache_key or '').strip()
    if not key:
        return ''
    if not _RE_STORAGE_TMDB.match(key):
        return key

    items = cache_items
    if items is None:
        items = _metadata_db.get_offline_cache_for_series(key) or []

    # 1) Skutečná složka z cest v offline_cache (funguje pro všechna existující stažení)
    from_paths = disk_folder_name_from_cache_items(key, items)
    if from_paths and not _RE_STORAGE_TMDB.match(from_paths):
        return from_paths

    series_data = _metadata_db.get_series_data(key) or {}
    human = resolve_folder_display_name(key, series_data)
    if human and not _RE_STORAGE_TMDB.match(human):
        return human

    if from_paths:
        return from_paths

    for dirname in disk_folder_names_for_storage_key(key):
        if not _RE_STORAGE_TMDB.match(dirname):
            return dirname

    return key
