# -*- coding: utf-8 -*-
"""
Integrace Titulky.com (titulky_light) s nastavením doplňku, RunPlugin a přehráváním.
"""

import hashlib
import json
import os
import re
import time

import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs

from resources.lib.utils.log_utils import get_logger

# Nelze importovat router_state zde — při běhu service.py nemá sys.argv[1] (plugin handle).
ADDON_ID = "plugin.video.mmirousek_v3"

from resources.lib.titulky_light.client import LightTitulkyClient
from resources.lib.titulky_light.utils import extract_subtitles

log = get_logger()

WIN = xbmcgui.Window(10000)
PROP_JOB = "mm_titulky_job"
PROP_JOB_TS = "mm_titulky_job_ts"
PROP_DEDUPE_ID = "mm_titulky_dedupe_id"
PROP_DEDUPE_TS = "mm_titulky_dedupe_ts"

DEDUPE_SEC = 15.0
# Max. stáří jobu od zápisu v PlayHandler (ochrana proti zastaralému mm_titulky_job).
JOB_MAX_AGE_SEC = 60.0


def _titulky_trace(msg):
    """Kodi log vždy (INFO) + logger při zapnutém detailním logování."""
    try:
        xbmc.log("SM2-titulky: %s" % msg, xbmc.LOGINFO)
    except Exception:
        pass
    log.info(msg)


def _addon():
    return xbmcaddon.Addon(ADDON_ID)


def _strip_kodi_label(s):
    """Odstraní [COLOR], [B] a podobné značky z textu položky v seznamu."""
    if not s:
        return ""
    t = re.sub(r"\[/?(?:COLOR|CR|I|B|LIGHT)\b[^\]]*\]", "", str(s), flags=re.IGNORECASE)
    t = re.sub(r"\[[^\]]*\]", "", t)
    return " ".join(t.split()).strip()


def _sanitize_job_strings(job):
    for k in ("title", "fallback_name", "tvshow"):
        if job.get(k):
            job[k] = _strip_kodi_label(job[k])


def _series_tvshow_name_for_titulky(data):
    """
    Název seriálu pro vyhledávání na Titulky.com — přednost angličtině / originálu
    (original_title v TMDb bývá EN; title/name často CZ).
    """
    if not isinstance(data, dict):
        return ""
    tm = data.get("tmdb") or {}
    raw = (
        tm.get("original_title")
        or tm.get("original_name")
        or tm.get("title_en")
        or tm.get("name")
        or tm.get("title")
        or data.get("name")
    )
    return _strip_kodi_label(str(raw).strip()) if raw else ""


def _tvshow_from_metadata_db_show(show_id_int):
    """title_en → title_original → title_cs pro řádek seriálu v metadata_db."""
    try:
        from resources.lib.database.metadata_db import MetadataDB

        row = MetadataDB().get(int(show_id_int))
        if not row:
            return ""
        mt = (row.get("media_type") or "").lower()
        if mt not in ("tv", "tvshow", "series", "show"):
            return ""
        raw = (
            row.get("title_en")
            or row.get("title_original")
            or row.get("title_cs")
        )
        return _strip_kodi_label(str(raw).strip()) if raw else ""
    except Exception:
        return ""


def _finalize_episode_tvshow(job, addon=None, profile=None):
    """
    Pro epizodu nahradí úložný klíč / číslo / tmdb_123 lidským názvem seriálu z SeriesManager.
    titulky_parent_tmdb_show_id = TMDb ID seriálu (kontextové menu); u play se doplní z JSON seriálu,
    protože params['tmdb_id'] bývá ID epizody, ne seriálu.
    """
    if (job.get("mediatype") or "").lower() != "episode":
        return
    addon = addon or _addon()
    if profile is None:
        profile = xbmcvfs.translatePath(addon.getAddonInfo("profile"))
    tv = (job.get("tvshow") or "").strip()
    tv_orig = tv
    parent = str(job.get("titulky_parent_tmdb_show_id") or "").strip()

    try:
        from resources.lib.series import series_manager

        sm = series_manager.SeriesManager(addon, profile)

        # Doplnit TMDb ID seriálu z úložiště (play větev nemá tlk_tmdb_id z menu).
        if not parent.isdigit() and tv:
            pdata = sm.load_series_data(tv)
            if pdata:
                sid = (pdata.get("tmdb") or {}).get("id")
                if sid is not None:
                    try:
                        parent = str(int(sid))
                        job["titulky_parent_tmdb_show_id"] = parent
                        _titulky_trace(
                            "episode doplněn parent show_id=%s z klíče %s" % (parent, tv[:50])
                        )
                    except (TypeError, ValueError):
                        pass

        parent = str(job.get("titulky_parent_tmdb_show_id") or "").strip()
        if parent.isdigit():
            nm_db = _tvshow_from_metadata_db_show(int(parent))
            if nm_db:
                job["tvshow"] = nm_db
                _titulky_trace(
                    "episode tvshow z metadata_db (EN/orig): %s" % job["tvshow"][:80]
                )
                return

        keys = []
        if tv:
            keys.append(tv)
            if tv.isdigit():
                keys.append("tmdb_%s" % tv)
        if parent.isdigit():
            keys.append("tmdb_%s" % parent)

        seen = set()
        for k in keys:
            if not k or k in seen:
                continue
            seen.add(k)
            data = sm.load_series_data(k)
            if not data:
                continue
            nm = _series_tvshow_name_for_titulky(data)
            if nm:
                job["tvshow"] = nm
                _titulky_trace(
                    "episode tvshow z series JSON: %s (klíč %s)"
                    % (job["tvshow"][:80], k[:40])
                )
                return
    except Exception as e:
        log.debug("titulky finalize episode tvshow: %s", e)

    parent = str(job.get("titulky_parent_tmdb_show_id") or "").strip()
    if parent.isdigit():
        nm = _tvshow_from_metadata_db_show(int(parent))
        if nm:
            job["tvshow"] = nm
            _titulky_trace(
                "episode tvshow z metadata_db (fallback): %s" % job["tvshow"][:80]
            )
            return

    if tv_orig and (
        re.match(r"^tmdb_\d+$", tv_orig, re.I) or tv_orig.isdigit()
    ):
        _titulky_trace(
            "episode tvshow nelze přeložit z klíče: %s (zkontroluj data seriálu)"
            % tv_orig[:60]
        )


def _apply_db_movie_for_search(job):
    """
    Film: dotaz na Titulky bez roku. Název z DB — přednostně anglický (title_en);
    pokud EN není, doplníme CS/original a nastavíme titulky_has_en_title=False
    (vynutí klávesnici v run_titulky_prompt).
    """
    if (job.get("mediatype") or "").lower() != "movie":
        return
    job["titulky_has_en_title"] = False
    job["year"] = ""
    tid = str(job.get("tmdb_id") or "").strip()
    if not tid.isdigit():
        return
    try:
        from resources.lib.database.metadata_db import MetadataDB

        row = MetadataDB().get(int(tid))
        if not row:
            return
        en = _strip_kodi_label(str(row.get("title_en") or "").strip())
        cs = _strip_kodi_label(
            str(row.get("title_cs") or row.get("title_original") or "").strip()
        )
        if en:
            job["title"] = en
            job["titulky_has_en_title"] = True
        elif cs:
            job["title"] = cs
    except Exception as e:
        log.debug("titulky db movie metadata: %s", e)


def build_job_from_play_params(params, addon, profile):
    """
    Sestaví job dict pro vyhledávání titulků z parametrů play akce.
    """
    mediatype = (params.get("mediatype") or "").lower()
    fallback = params.get("name") or ""
    job = {
        "job_id": "",
        "mediatype": mediatype or "unknown",
        "title": (params.get("title") or params.get("cz_title") or "").strip(),
        "year": str(params.get("year") or "").strip(),
        "tvshow": "",
        "season": str(params.get("season") or "").strip(),
        "episode": str(params.get("episode") or "").strip(),
        "fallback_name": fallback,
        "tmdb_id": str(params.get("tmdb_id") or "").strip(),
        "titulky_parent_tmdb_show_id": "",
    }
    if mediatype == "episode" and params.get("series_name"):
        job["tvshow"] = str(params.get("series_name")).strip()
    if mediatype == "movie" and not job["title"]:
        job["title"] = fallback
    _sanitize_job_strings(job)
    _finalize_episode_tvshow(job, addon, profile)
    _apply_db_movie_for_search(job)
    path_key = "%s|%s|%s|%s|%s|%s" % (
        mediatype,
        job.get("tvshow") or "",
        job.get("season") or "",
        job.get("episode") or "",
        job.get("title") or "",
        job.get("tmdb_id") or "",
    )
    job["job_id"] = hashlib.md5(path_key.encode("utf-8", "replace")).hexdigest()[:20]
    return job


def apply_titulky_listitem_properties(listitem, params, addon, profile):
    """Nastaví mm_tlk_* vlastnosti pro případný fallback v PlayerMonitor."""
    job = build_job_from_play_params(params, addon, profile)
    listitem.setProperty("mm_tlk_fallback_name", job.get("fallback_name") or "")
    listitem.setProperty("mm_tlk_tvshow", job.get("tvshow") or "")
    listitem.setProperty("mm_tlk_season", job.get("season") or "")
    listitem.setProperty("mm_tlk_episode", job.get("episode") or "")
    listitem.setProperty("mm_tlk_title", job.get("title") or "")
    listitem.setProperty("mm_tlk_year", job.get("year") or "")
    listitem.setProperty("mm_tlk_job_id", job.get("job_id") or "")


def publish_auto_job_if_enabled(params, addon, profile):
    """
    Pokud je zapnuto auto titulky, uloží job do Window pro PlayerMonitor / RunPlugin.
    """
    try:
        if not addon.getSettingBool("titulky_enabled"):
            return
        if not addon.getSettingBool("titulky_auto_on_play"):
            return
    except Exception:
        if addon.getSetting("titulky_enabled") != "true":
            return
        if addon.getSetting("titulky_auto_on_play") != "true":
            return
    job = build_job_from_play_params(params, addon, profile)
    try:
        WIN.setProperty(PROP_JOB, json.dumps(job, ensure_ascii=False))
        WIN.setProperty(PROP_JOB_TS, str(time.time()))
        _titulky_trace(
            "publish: job uložen job_id=%s mediatype=%s tmdb_id=%s"
            % (
                job.get("job_id") or "?",
                job.get("mediatype") or "?",
                job.get("tmdb_id") or "",
            )
        )
    except Exception as e:
        log.error("titulky publish job failed: %s", e, exc_info=True)


def _job_from_window():
    raw = WIN.getProperty(PROP_JOB) or ""
    WIN.clearProperty(PROP_JOB)
    WIN.clearProperty(PROP_JOB_TS)
    if not raw:
        return None
    try:
        return json.loads(raw)
    except Exception:
        return None


def _job_from_listitem_fallback():
    job = {
        "job_id": "",
        "mediatype": (xbmc.getInfoLabel("ListItem.Property(mediatype)") or "").lower(),
        "title": xbmc.getInfoLabel("ListItem.Property(mm_tlk_title)") or "",
        "year": xbmc.getInfoLabel("ListItem.Property(mm_tlk_year)") or "",
        "tvshow": xbmc.getInfoLabel("ListItem.Property(mm_tlk_tvshow)") or "",
        "season": xbmc.getInfoLabel("ListItem.Property(mm_tlk_season)") or "",
        "episode": xbmc.getInfoLabel("ListItem.Property(mm_tlk_episode)") or "",
        "fallback_name": xbmc.getInfoLabel("ListItem.Property(mm_tlk_fallback_name)")
        or "",
        "tmdb_id": xbmc.getInfoLabel("ListItem.Property(tmdb_id)") or "",
    }
    if not any(
        [
            job["title"],
            job["tvshow"],
            job["fallback_name"],
            job["tmdb_id"],
        ]
    ):
        return None
    path = ""
    try:
        path = xbmc.Player().getPlayingFile() or ""
    except Exception:
        path = xbmc.getInfoLabel("Player.Filenameandpath") or ""
    key = "%s|%s" % (path, json.dumps(job, sort_keys=True))
    job["job_id"] = hashlib.md5(key.encode("utf-8", "replace")).hexdigest()[:20]
    job.setdefault("titulky_parent_tmdb_show_id", "")
    _sanitize_job_strings(job)
    _finalize_episode_tvshow(job)
    _apply_db_movie_for_search(job)
    return job


def _job_from_context_params(params):
    mediatype = (params.get("tlk_mediatype") or params.get("mediatype") or "").lower()
    job = {
        "job_id": "ctx_%s" % hashlib.md5(repr(sorted(params.items())).encode()).hexdigest()[:16],
        "mediatype": mediatype,
        "title": (params.get("tlk_title") or "").strip(),
        "year": str(params.get("tlk_year") or "").strip(),
        "tvshow": (params.get("tlk_tvshow") or "").strip(),
        "season": str(params.get("tlk_season") or "").strip(),
        "episode": str(params.get("tlk_episode") or "").strip(),
        "fallback_name": (params.get("tlk_fallback") or "").strip(),
        "tmdb_id": str(params.get("tlk_tmdb_id") or params.get("tmdb_id") or "").strip(),
        "titulky_parent_tmdb_show_id": str(params.get("tlk_tmdb_id") or "").strip(),
    }
    _sanitize_job_strings(job)
    _finalize_episode_tvshow(job)
    _apply_db_movie_for_search(job)
    return job


def _video_metadata_from_job(job):
    md = {}
    mediatype = (job.get("mediatype") or "").lower()
    if mediatype == "episode" and job.get("tvshow"):
        md["tvshow"] = job["tvshow"]
        try:
            md["season"] = int(job.get("season") or 1)
        except Exception:
            md["season"] = 1
        try:
            md["episode"] = int(job.get("episode") or 1)
        except Exception:
            md["episode"] = 1
        return md
    title = job.get("title") or job.get("fallback_name") or ""
    md["title"] = title
    md["tvshow"] = ""
    md["season"] = 1
    md["episode"] = 1
    return md


def _default_fulltext_for_display(vmeta):
    """Stejný řetězec jako na Titulky.com odešle klient (pro náhled / úpravu)."""
    if vmeta.get("tvshow"):
        return "%s S%02dE%02d" % (
            vmeta["tvshow"],
            int(vmeta.get("season", 1)),
            int(vmeta.get("episode", 1)),
        )
    return (vmeta.get("title") or "").strip()


# Podsložka pod titulky_folder — čitelné názvy pro Kodi / průzkumník.
TITULKY_STORAGE_SUBDIR = "Titulky"


def _sanitize_path_segment(s, max_len=120):
    """Název jedné složky bez znaků zakázaných ve Windows; oříznutí délky."""
    if s is None:
        s = ""
    t = str(s)
    forbidden = '<>:"/\\|?*'
    out = []
    for c in t:
        o = ord(c)
        if o < 32:
            out.append("_")
        elif c in forbidden:
            out.append("_")
        else:
            out.append(c)
    t = "".join(out)
    t = re.sub(r"_+", "_", t)
    t = t.strip(" .")
    if len(t) > max_len:
        t = t[:max_len].rstrip(" .")
    return t


def _folder_label_from_job(job):
    """
    Čitelný název podsložky pod …/Titulky/ z jobu (ne z custom_fulltext po klávesnici).
    Film: title + volitelně (rok); epizoda: Show SxxEyy jako u fulltextu.
    """
    md = _video_metadata_from_job(job)
    if md.get("tvshow"):
        label = _default_fulltext_for_display(md)
    else:
        label = (md.get("title") or "").strip()
        yr = str(job.get("year") or "").strip()
        if yr and label:
            label = "%s (%s)" % (label, yr)
        elif yr and not label:
            label = "release_%s" % yr
    label = (label or "").strip()
    if not label:
        jid = "".join(c for c in (job.get("job_id") or "x") if c.isalnum())[:8] or "x"
        label = "Unknown_%s" % jid
    safe = _sanitize_path_segment(label)
    if not safe:
        jid = "".join(c for c in (job.get("job_id") or "x") if c.isalnum())[:8] or "x"
        safe = "Unknown_%s" % jid
    return safe


def _confirm_query_enabled(addon):
    try:
        return addon.getSettingBool("titulky_confirm_query")
    except Exception:
        v = addon.getSetting("titulky_confirm_query")
        return v != "false"


def _pick_subtitle_file(paths):
    if not paths:
        return None
    pref = [p for p in paths if p.lower().endswith(".srt")]
    return pref[0] if pref else paths[0]


def run_titulky_prompt(params):
    addon = _addon()
    name = addon.getAddonInfo("name")
    src = (params.get("source") or "").lower() or "?"
    _titulky_trace("run_titulky_prompt: start source=%s" % src)
    try:
        enabled = addon.getSettingBool("titulky_enabled")
    except Exception:
        enabled = addon.getSetting("titulky_enabled") == "true"
    if not enabled:
        _titulky_trace("run_titulky_prompt: titulky vypnuty, konec")
        return

    folder = addon.getSetting("titulky_folder") or ""
    folder = xbmcvfs.translatePath(folder) if folder else ""
    if not folder or not folder.strip():
        xbmcgui.Dialog().notification(
            name, "Nastavte složku pro titulky v nastavení doplňku.", xbmcgui.NOTIFICATION_WARNING, 4000
        )
        return

    source = (params.get("source") or "").lower()
    if source == "context":
        job = _job_from_context_params(params)
    else:
        job = _job_from_window()
        if not job:
            job = _job_from_listitem_fallback()
    if not job:
        _titulky_trace("run_titulky_prompt: žádný job (okno/kontext/fallback prázdný)")
        return

    job.setdefault("titulky_parent_tmdb_show_id", "")
    _finalize_episode_tvshow(job)

    vmeta = _video_metadata_from_job(job)
    if not vmeta.get("tvshow") and not (vmeta.get("title") or "").strip():
        xbmcgui.Dialog().notification(
            name, "Nelze vyhledat titulky — chybí název videa.", xbmcgui.NOTIFICATION_WARNING, 4000
        )
        return

    default_q = _default_fulltext_for_display(vmeta)
    is_movie = (job.get("mediatype") or "").lower() == "movie"
    has_en = bool(job.get("titulky_has_en_title"))
    force_keyboard = is_movie and not has_en
    show_keyboard = force_keyboard or _confirm_query_enabled(addon)

    if show_keyboard:
        hint = (
            "Titulky.com — upravte dotaz (bez anglického názvu v databázi)"
            if force_keyboard
            else "Titulky.com — upravte vyhledávací text"
        )
        kb = xbmc.Keyboard(default_q, hint)
        kb.doModal()
        if not kb.isConfirmed():
            return
        custom = (kb.getText() or "").strip()
        if not custom:
            xbmcgui.Dialog().notification(
                name, "Prázdný dotaz — zrušeno.", xbmcgui.NOTIFICATION_INFO, 2500
            )
            return
        vmeta = dict(vmeta)
        vmeta["custom_fulltext"] = custom
        log.info('titulky search query (uživatelský): "%s"', custom)
    else:
        log.info('titulky search query (automaticky, EN z DB): "%s"', default_q)

    user = addon.getSetting("titulky_username") or ""
    password = addon.getSetting("titulky_password") or ""
    client = LightTitulkyClient(username=user or None, password=password or None)
    if user:
        if not client.login():
            xbmcgui.Dialog().notification(
                name,
                "Přihlášení k Titulky.com selhalo — pokračuji jako host.",
                xbmcgui.NOTIFICATION_INFO,
                3500,
            )

    try:
        results = client.search(vmeta) or []
    except Exception as e:
        log.error("titulky search error: %s", e, exc_info=True)
        xbmcgui.Dialog().notification(name, "Chyba vyhledávání titulků.", xbmcgui.NOTIFICATION_ERROR, 4000)
        return

    if not results:
        xbmcgui.Dialog().notification(
            name, "Na Titulky.com nebyly nalezeny titulky.", xbmcgui.NOTIFICATION_INFO, 3500
        )
        return

    labels = []
    for r in results:
        ver = (r.get("version") or "")[:72]
        labels.append("%s  [%s]  %s" % (r.get("title") or "?", r.get("lang") or "?", ver))
    idx = xbmcgui.Dialog().select("Titulky.com — vyberte titulky", labels)
    if idx < 0:
        return

    sel = results[idx]
    storage_name = _folder_label_from_job(job)
    dest_dir = os.path.join(folder, TITULKY_STORAGE_SUBDIR, storage_name)
    _titulky_trace("run_titulky_prompt: dest_dir=%s" % dest_dir[:160])
    try:
        xbmcvfs.mkdirs(dest_dir)
    except Exception:
        pass
    try:
        if not xbmcvfs.exists(dest_dir):
            os.makedirs(dest_dir, exist_ok=True)
    except Exception as e:
        log.error("titulky mkdir: %s", e)
        xbmcgui.Dialog().notification(name, "Nelze vytvořit cílovou složku.", xbmcgui.NOTIFICATION_ERROR, 4000)
        return

    zip_path = client.download(sel["id"], sel["link_file"], dest_dir)
    if not zip_path or not xbmcvfs.exists(zip_path):
        xbmcgui.Dialog().notification(name, "Stažení titulků selhalo.", xbmcgui.NOTIFICATION_ERROR, 4000)
        return

    subs = extract_subtitles(zip_path) or []
    sub_path = _pick_subtitle_file(subs)
    if not sub_path:
        xbmcgui.Dialog().notification(
            name, "V archivu nebyly nalezeny podporované titulky.", xbmcgui.NOTIFICATION_WARNING, 4000
        )
        return

    try:
        xbmc.Player().setSubtitles(sub_path)
        xbmcgui.Dialog().notification(name, "Titulky načteny.", xbmcgui.NOTIFICATION_INFO, 2500)
    except Exception as e:
        log.error("setSubtitles: %s", e)
        xbmcgui.Dialog().notification(name, "Titulky staženy, ale nepodařilo je zapnout.", xbmcgui.NOTIFICATION_WARNING, 4000)


def maybe_schedule_auto_titulky_from_player():
    """
    Voláno ze service PlayerMonitor: deduplikace a RunPlugin(titulky_prompt).
    mm_titulky_job musí zůstat do doby, než ho run_titulky_prompt přečte.
    """
    addon = _addon()
    try:
        if not addon.getSettingBool("titulky_enabled"):
            return
        if not addon.getSettingBool("titulky_auto_on_play"):
            return
    except Exception:
        if addon.getSetting("titulky_enabled") != "true":
            return
        if addon.getSetting("titulky_auto_on_play") != "true":
            return

    raw = WIN.getProperty(PROP_JOB)
    if not raw:
        return

    ts_raw = WIN.getProperty(PROP_JOB_TS) or ""
    try:
        job_ts = float(ts_raw) if ts_raw else 0.0
    except Exception:
        job_ts = 0.0
    now = time.time()
    if not job_ts or (now - job_ts) > JOB_MAX_AGE_SEC:
        WIN.clearProperty(PROP_JOB)
        WIN.clearProperty(PROP_JOB_TS)
        _titulky_trace(
            "maybe_schedule: zahozen job (chybí/neplatné ts nebo starší než %ss)"
            % int(JOB_MAX_AGE_SEC)
        )
        return

    try:
        job = json.loads(raw)
    except Exception as e:
        WIN.clearProperty(PROP_JOB)
        WIN.clearProperty(PROP_JOB_TS)
        log.error("maybe_schedule: JSON job chyba: %s", e, exc_info=True)
        return
    jid = job.get("job_id") or ""
    try:
        last_id = WIN.getProperty(PROP_DEDUPE_ID) or ""
        last_ts = float(WIN.getProperty(PROP_DEDUPE_TS) or 0)
        if jid and last_id == jid and (time.time() - last_ts) < DEDUPE_SEC:
            WIN.clearProperty(PROP_JOB)
            WIN.clearProperty(PROP_JOB_TS)
            _titulky_trace("maybe_schedule: dedupe skip job_id=%s" % jid)
            return
    except Exception:
        pass

    WIN.setProperty(PROP_DEDUPE_ID, jid)
    WIN.setProperty(PROP_DEDUPE_TS, str(time.time()))

    url = "plugin://%s/?action=titulky_prompt&source=auto" % ADDON_ID
    _titulky_trace("maybe_schedule: RunPlugin titulky_prompt job_id=%s" % (jid or "?"))
    xbmc.executebuiltin("RunPlugin(%s)" % url)
