import os
import socket
import sys
import time
import traceback
import urllib.error
import urllib.request

import xbmc
import xbmcaddon
import xbmcgui
import xbmcplugin
import xbmcvfs

import gb_common as common  # Gemeinsame Variablen/Funktionen


LOGP = f"[{getattr(common, 'addon_id', 'plugin.video.glotzbox')} settings.py]"
WATCHED_STATUS_MOVIES_FILE = "watched_status_movies.json"
WATCHED_STATUS_SERIES_FILE = "watched_status_series.json"

# Timeout für Verbindungscheck (liest net_timeout aus Settings, fallback 20s)
def _conn_timeout():
    try:
        v = int(common.addon.getSetting("net_timeout") or 0)
        return max(10, v) if v > 0 else getattr(common, "TIMEOUT_DEFAULT", 20)
    except Exception:
        return getattr(common, "TIMEOUT_DEFAULT", 20)


def _log(msg, level=xbmc.LOGINFO):
    try:
        xbmc.log(f"{LOGP} {msg}", level)
    except Exception:
        pass


def _log_debug_exc(context, exc):
    try:
        _log(f"{context}: {exc}", xbmc.LOGDEBUG)
    except Exception:
        pass




def _resolve_addon_context():
    addon_name = getattr(common, "addon_name", "GlotzBox")
    addon_data_dir = getattr(common, "addon_data_dir", "")
    addon_handle = -1

    try:
        h = int(getattr(common, "addon_handle", -1))
        if h >= 0:
            addon_handle = h
    except Exception as e:
        _log_debug_exc("could not parse common.addon_handle", e)

    try:
        addon_obj = xbmcaddon.Addon()
        addon_name = addon_obj.getAddonInfo("name") or addon_name
        profile_dir = xbmcvfs.translatePath(addon_obj.getAddonInfo("profile"))
        if profile_dir:
            addon_data_dir = profile_dir
    except Exception as e:
        _log_debug_exc("could not resolve addon profile from xbmcaddon.Addon()", e)

    try:
        if addon_data_dir and not xbmcvfs.exists(addon_data_dir):
            xbmcvfs.mkdirs(addon_data_dir)
            _log(f"Addon-Datenverzeichnis erstellt: {addon_data_dir}", xbmc.LOGINFO)
    except Exception as e:
        _log_debug_exc(f"could not ensure addon data dir '{addon_data_dir}'", e)

    try:
        if len(sys.argv) >= 2:
            h = int(sys.argv[1])
            if h >= 0:
                addon_handle = h
    except Exception as e:
        _log_debug_exc("could not parse addon handle from sys.argv", e)

    try:
        common.addon_handle = addon_handle
    except Exception as e:
        _log_debug_exc("failed to sync common.addon_handle", e)

    return addon_name, addon_data_dir, addon_handle


def verbindungscheck():
    """
    Testet die Remote-Verbindung mit derselben Host-/Pfad-Logik wie die übrigen Module.
    Nutzt bevorzugt HEAD (schnell), bei 405/501 Fallback auf GET.
    """
    try:
        host, base_path, scheme = common.normalize_ftp_host_basepath()
        if not host:
            xbmcgui.Dialog().notification("Fehler", "Host fehlt!", xbmcgui.NOTIFICATION_ERROR)
            common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
            return

        base_path = (base_path or "/user/downloads/kodiAddon/").rstrip("/") + "/"
        probe_files = ("kodiMovie.json", "kodiSeries.json", "seriesFav.json")
        probe_urls = [f"{scheme}://{host}{base_path}{fn}" for fn in probe_files]

        ssl_ctx = None
        try:
            if hasattr(common, "get_ssl_context"):
                ssl_ctx = common.get_ssl_context()
        except Exception as e:
            _log_debug_exc("verbindungscheck: could not get shared ssl context", e)
            ssl_ctx = None

        last_error = None

        for url in probe_urls:
            try:
                req = common.get_authenticated_request(url)
                if not req:
                    continue
                try:
                    req.get_method = lambda: "HEAD"
                except Exception as e:
                    _log_debug_exc("verbindungscheck: could not force HEAD method", e)

                with common.retry_urlopen(req, context=ssl_ctx, timeout=_conn_timeout(), retries=1, retry_delay=2.0) as response:
                    status = int(response.getcode() or 0)
                if 200 <= status < 400:
                    xbmcgui.Dialog().notification("Verbindung OK", "Verbindungscheck erfolgreich", xbmcgui.NOTIFICATION_INFO)
                    common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
                    return
            except urllib.error.HTTPError as e:
                # Manche Server erlauben HEAD nicht -> GET-Fallback.
                if int(getattr(e, "code", 0)) in (405, 501):
                    try:
                        req_get = common.get_authenticated_request(url)
                        if req_get:
                            req_get.add_header("Range", "bytes=0-0")
                            with common.retry_urlopen(req_get, context=ssl_ctx, timeout=15, retries=1, retry_delay=2.0) as response:
                                status = int(response.getcode() or 0)
                            if 200 <= status < 400:
                                xbmcgui.Dialog().notification("Verbindung OK", "Verbindungscheck erfolgreich", xbmcgui.NOTIFICATION_INFO)
                                common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
                                return
                    except Exception as e_get:
                        _log_debug_exc(f"verbindungscheck: GET fallback failed for {common.mask_url(url)}", e_get)
                        last_error = e_get
                        continue
                last_error = e
            except Exception as e:
                last_error = e

        if last_error:
            raise last_error
        raise Exception("Kein Test-Endpunkt erreichbar.")
    except urllib.error.HTTPError as e:
        code = int(getattr(e, "code", 0) or 0)
        if code == 401:
            error_msg = f"HTTP 401: Benutzername oder Passwort falsch."
        elif code == 403:
            error_msg = f"HTTP 403: Zugriff verweigert."
        elif code == 404:
            error_msg = f"HTTP 404: Datei nicht gefunden – Pfad prüfen."
        else:
            error_msg = f"HTTP-Fehler: {e.code} {e.reason}"
        _log(error_msg, xbmc.LOGERROR)
        xbmcgui.Dialog().notification("HTTP-Fehler", error_msg, xbmcgui.NOTIFICATION_ERROR)
        common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
    except socket.timeout:
        error_msg = "Timeout: Server antwortet nicht."
        _log(error_msg, xbmc.LOGERROR)
        xbmcgui.Dialog().notification("Timeout", error_msg, xbmcgui.NOTIFICATION_ERROR)
        common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
    except urllib.error.URLError as e:
        reason = getattr(e, "reason", e)
        if isinstance(reason, socket.timeout):
            error_msg = "Timeout: Server antwortet nicht."
        else:
            error_msg = f"URL-Fehler: {str(reason)}"
        _log(error_msg, xbmc.LOGERROR)
        title = "Timeout" if "Timeout:" in error_msg else "URL-Fehler"
        xbmcgui.Dialog().notification(title, error_msg, xbmcgui.NOTIFICATION_ERROR)
        common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
    except Exception as e:
        error_msg = f"Unbekannter Fehler: {str(e)}"
        _log(error_msg, xbmc.LOGERROR)
        xbmcgui.Dialog().notification("Kritischer Fehler", error_msg, xbmcgui.NOTIFICATION_ERROR)
        common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))


def clear_cache():
    """
    Löscht die *relevanten* Cache-/Meta-Dateien des Addons.

    WICHTIG: Downloads (downloaded_*.json) und watched_status_* bleiben erhalten.
    """
    try:
        base = getattr(common, "addon_data_dir", "") or xbmcvfs.translatePath(f"special://home/userdata/addon_data/{common.addon_id}/")
        base = xbmcvfs.translatePath(base) if base else ""
        if not base:
            xbmcgui.Dialog().notification("Cache leeren", "Addon-Datenverzeichnis nicht gefunden.", xbmcgui.NOTIFICATION_ERROR)
            return

        # Hinweis: cache_movies_images / cache_series_images / cache_fav_series_images
        # gibt es seit dem Umstieg auf Kodis eigenen Texture-Cache (siehe
        # service._migrate_remove_old_image_caches) nicht mehr. Daher nicht mehr
        # in dieser Liste — wäre toter Code, der nur verwirrt beim Lesen.
        cache_dirs = [
            "cache",
            "cache_series_details_json",
            "cache_series_details_json_meta",
            "fav_details_cache",
            "payload_cache",
            "payload_cache_fav",
        ]

        cache_files = [
            "movies.cache.json",
            "series.cache.json",
            "seriesFav.cache.json",
            "movies_remote_meta.json",
            "series_remote_meta.json",
            "seriesFav.remote_meta.json",
            "kodiMovie.json.meta.json",
            "kodiSeries.json.meta.json",
            "seriesFav.json.meta.json",
            "details_sha1_map.json",
            "series_seen_urls.json",
            "details_cache_schema.txt",
            "series_details_meta.json",
            "series_details_prefetch_state.json",
            "payload_gc_state.json",
            "cache_cleanup_state.json",
            ".image_cache_removed",
        ]

        def _delete_file(fp: str):
            try:
                if fp and xbmcvfs.exists(fp):
                    if not xbmcvfs.delete(fp):
                        _log(f"clear_cache: xbmcvfs.delete returned False for {fp}", xbmc.LOGDEBUG)
                    return
            except Exception as e:
                _log_debug_exc(f"clear_cache: xbmcvfs.delete failed for {fp}", e)
            try:
                if fp and os.path.exists(fp):
                    os.remove(fp)
            except Exception as e:
                _log_debug_exc(f"clear_cache: os.remove failed for {fp}", e)

        def _delete_tree(path: str):
            try:
                if not path:
                    return
                if xbmcvfs.exists(path):
                    dirs, files = xbmcvfs.listdir(path)
                    for f_name in files:
                        _delete_file(os.path.join(path, f_name))
                    for d_name in dirs:
                        sub = os.path.join(path, d_name)
                        _delete_tree(sub)
                        try:
                            if not xbmcvfs.rmdir(sub):
                                _log(f"clear_cache: xbmcvfs.rmdir returned False for {sub}", xbmc.LOGDEBUG)
                        except Exception as e:
                            _log_debug_exc(f"clear_cache: xbmcvfs.rmdir failed for {sub}", e)
                    try:
                        if not xbmcvfs.rmdir(path):
                            _log(f"clear_cache: xbmcvfs.rmdir returned False for {path}", xbmc.LOGDEBUG)
                    except Exception as e:
                        _log_debug_exc(f"clear_cache: xbmcvfs.rmdir failed for {path}", e)
                    return
            except Exception as e:
                _log_debug_exc(f"clear_cache: recursive VFS delete failed for {path}", e)

            try:
                if os.path.isdir(path):
                    import shutil
                    shutil.rmtree(path, ignore_errors=True)
            except Exception as e:
                _log_debug_exc(f"clear_cache: shutil.rmtree failed for {path}", e)

        for d_name in cache_dirs:
            _delete_tree(os.path.join(base, d_name))

        for f_name in cache_files:
            _delete_file(os.path.join(base, f_name))

        # Laufzeit-Zustände/Memory-Caches best-effort invalidieren, damit
        # "Cache leeren" auch ohne Kodi-Neustart sofort Wirkung zeigt.
        try:
            for prop in (
                common.WindowProps.MOVIES_BG_RUNNING,
                common.WindowProps.MOVIES_REFRESH_TS,
                common.WindowProps.MOVIES_IMAGE_PREFETCH_TRIGGER,
                common.WindowProps.MOVIES_REFRESH_PENDING_UNTIL,
                common.WindowProps.MOVIES_LISTING_TS,
                common.WindowProps.MOVIES_REFRESH_REQ_TS,
                common.WindowProps.MOVIES_LIST_BUILT_TS,
                common.WindowProps.MOVIES_WATCHED_UPDATED_TS,
                common.WindowProps.MOVIES_LISTING_DATA_SIG,
                common.WindowProps.SERIES_DETAILS_PREFETCH_RUNNING,
                common.WindowProps.SERIES_DETAILS_PREFETCH_TRIGGER,
                common.WindowProps.PAYLOAD_GC_RUNNING,
                common.WindowProps.CACHE_CLEANUP_RUNNING,
                common.WindowProps.LAST_PLAYED_TVSHOW_TITLE,
                common.WindowProps.LAST_SERIES_CACHE_KEY,
            ):
                common.clear_window_property(prop)
            common.set_window_property(common.WindowProps.SERIES_DETAILS_BUMP, int(time.time()))
        except Exception as e:
            _log_debug_exc("clear_cache: runtime property reset failed", e)

        _log(f"Cache/Meta bereinigt unter: {base}", xbmc.LOGINFO)
        xbmcgui.Dialog().notification("Cache gelöscht", "Cache/Meta wurden erfolgreich gelöscht!", xbmcgui.NOTIFICATION_INFO)
        try:
            if common.addon_handle >= 0:
                xbmcplugin.endOfDirectory(common.addon_handle, succeeded=True, cacheToDisc=False)
        except Exception:
            pass

    except Exception as e:
        _log(f"Fehler beim Cache löschen: {e}", xbmc.LOGERROR)
        xbmcgui.Dialog().notification(
            "Cache leeren",
            "Fehler beim Cache-Loeschen. Details im Kodi-Log.",
            xbmcgui.NOTIFICATION_ERROR,
        )
        try:
            if common.addon_handle >= 0:
                xbmcplugin.endOfDirectory(common.addon_handle, succeeded=False)
        except Exception:
            pass


def _reset_watched_status_file(watched_filename: str):
    addon_name, addon_data_dir, addon_handle = _resolve_addon_context()

    if not addon_data_dir:
        xbmcgui.Dialog().notification(
            "Fehler",
            "Addon-Pfad nicht gefunden.",
            xbmcgui.NOTIFICATION_ERROR,
            5000,
        )
        common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))
        return

    watched_status_file = os.path.join(addon_data_dir, watched_filename)
    _log(f"[{addon_name}] Prüfe/Lösche Gesehen-Status Datei: {watched_status_file}", xbmc.LOGINFO)

    try:
        exists = False
        try:
            exists = bool(xbmcvfs.exists(watched_status_file))
        except Exception as e:
            _log_debug_exc(f"[{addon_name}] xbmcvfs.exists failed for {watched_status_file}", e)
            exists = bool(os.path.exists(watched_status_file))

        if exists:
            deleted = False
            try:
                deleted = bool(xbmcvfs.delete(watched_status_file))
            except Exception as e:
                _log_debug_exc(f"[{addon_name}] xbmcvfs.delete failed for {watched_status_file}", e)

            if not deleted:
                try:
                    if os.path.exists(watched_status_file):
                        os.remove(watched_status_file)
                        deleted = True
                except Exception as e:
                    _log_debug_exc(f"[{addon_name}] os.remove failed for {watched_status_file}", e)

            if deleted:
                _log(f"[{addon_name}] Gesehen-Status-Datei erfolgreich gelöscht.", xbmc.LOGINFO)
                xbmcgui.Dialog().notification(
                    addon_name,
                    "Gesehen-Status wurde zurückgesetzt.",
                    xbmcgui.NOTIFICATION_INFO,
                    3000,
                )
            else:
                _log(f"[{addon_name}] Fehler: Konnte existierende Datei nicht löschen: {watched_status_file}", xbmc.LOGERROR)
                xbmcgui.Dialog().notification(
                    f"{addon_name} - Fehler",
                    "Datei gefunden, aber Löschen fehlgeschlagen.",
                    xbmcgui.NOTIFICATION_ERROR,
                    5000,
                )
        else:
            _log(f"[{addon_name}] Gesehen-Status-Datei nicht gefunden (Status war bereits zurückgesetzt).", xbmc.LOGINFO)
            xbmcgui.Dialog().notification(
                addon_name,
                "Nichts zu löschen!",
                xbmcgui.NOTIFICATION_INFO,
                3000,
            )

    except Exception as e:
        _log(f"[{addon_name}] Fehler beim Prüfen/Löschen der Gesehen-Status-Datei: {e}", xbmc.LOGERROR)
        _log(traceback.format_exc(), xbmc.LOGERROR)
        xbmcgui.Dialog().notification(
            f"{addon_name} - Fehler",
            "Fehler beim Zuruecksetzen. Details im Kodi-Log.",
            xbmcgui.NOTIFICATION_ERROR,
            5000,
        )

    if addon_handle != -1:
        _log(f"[{addon_name}] Aufruf von endOfDirectory mit Handle: {addon_handle}", xbmc.LOGDEBUG)
    else:
        _log(f"[{addon_name}] Kein gültiges Addon-Handle für endOfDirectory.", xbmc.LOGWARNING)
    common.safe_end_of_directory(handle=getattr(common, "addon_handle", -1))


def reset_watched_status_movie():
    _reset_watched_status_file(WATCHED_STATUS_MOVIES_FILE)


def reset_watched_status_series():
    _reset_watched_status_file(WATCHED_STATUS_SERIES_FILE)