"""TheTVDB API v4 Integration fuer Serien-Metadaten"""
import logging
import os
from typing import Optional, TYPE_CHECKING

import aiohttp
import aiomysql

from app.config import Config

if TYPE_CHECKING:
    from app.services.library import LibraryService

# tvdb-v4-official ist optional - funktioniert auch ohne
try:
    import tvdb_v4_official
    TVDB_AVAILABLE = True
except ImportError:
    TVDB_AVAILABLE = False
    logging.warning("tvdb-v4-official nicht installiert - TVDB deaktiviert")

# Artwork-Typ-IDs -> Namen
ARTWORK_TYPE_MAP = {
    1: "banner",
    2: "poster",
    3: "fanart",
    5: "icon",
    6: "season_poster",
    7: "season_banner",
    22: "clearlogo",
    23: "clearart",
}


class TVDBService:
    """TVDB API v4 Client fuer Serien- und Film-Metadaten"""

    def __init__(self, config: Config):
        self.config = config
        self._client = None
        self._client_api_key = ""  # Key mit dem der Client erstellt wurde
        self._db_pool: Optional[aiomysql.Pool] = None

    @property
    def _api_key(self) -> str:
        return self.config.settings.get("library", {}).get("tvdb_api_key", "")

    @property
    def _pin(self) -> str:
        return self.config.settings.get("library", {}).get("tvdb_pin", "")

    @property
    def _language(self) -> str:
        """Konfigurierte TVDB-Sprache (Standard: deu)"""
        return self.config.settings.get("library", {}).get(
            "tvdb_language", "deu"
        )

    @property
    def is_configured(self) -> bool:
        return TVDB_AVAILABLE and bool(self._api_key)

    def set_db_pool(self, pool: aiomysql.Pool) -> None:
        """Setzt den DB-Pool (geteilt mit LibraryService)"""
        self._db_pool = pool

    def _get_client(self):
        """Erstellt oder gibt TVDB-Client zurueck.
        Erstellt neuen Client wenn API Key geaendert wurde."""
        if not TVDB_AVAILABLE:
            return None
        if not self._api_key:
            return None

        # Client neu erstellen wenn API Key geaendert wurde
        if self._client and self._client_api_key != self._api_key:
            logging.info("TVDB API Key geaendert - Client wird neu erstellt")
            self._client = None

        if self._client is None:
            try:
                if self._pin:
                    self._client = tvdb_v4_official.TVDB(
                        self._api_key, pin=self._pin
                    )
                else:
                    self._client = tvdb_v4_official.TVDB(self._api_key)
                self._client_api_key = self._api_key
                logging.info("TVDB Client verbunden")
            except Exception as e:
                logging.error(f"TVDB Verbindung fehlgeschlagen: {e}")
                return None

        return self._client

    # === DB-Tabellen ===

    async def init_db(self) -> None:
        """Erstellt TVDB-Cache-Tabellen"""
        if not self._db_pool:
            return
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        CREATE TABLE IF NOT EXISTS tvdb_cast_cache (
                            id INT AUTO_INCREMENT PRIMARY KEY,
                            series_tvdb_id INT NOT NULL,
                            person_name VARCHAR(256) NOT NULL,
                            character_name VARCHAR(256),
                            sort_order INT DEFAULT 0,
                            image_url VARCHAR(512),
                            person_image_url VARCHAR(512),
                            cached_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                            INDEX idx_series (series_tvdb_id)
                        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
                    """)

                    await cur.execute("""
                        CREATE TABLE IF NOT EXISTS tvdb_artwork_cache (
                            id INT AUTO_INCREMENT PRIMARY KEY,
                            series_tvdb_id INT NOT NULL,
                            artwork_type VARCHAR(32) NOT NULL,
                            image_url VARCHAR(512) NOT NULL,
                            thumbnail_url VARCHAR(512),
                            width INT DEFAULT 0,
                            height INT DEFAULT 0,
                            is_primary TINYINT DEFAULT 0,
                            local_path VARCHAR(1024) NULL,
                            cached_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                            INDEX idx_series (series_tvdb_id),
                            INDEX idx_type (artwork_type)
                        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
                    """)

                    # Neue Spalten in library_series (falls noch nicht vorhanden)
                    try:
                        await cur.execute(
                            "ALTER TABLE library_series "
                            "ADD COLUMN metadata_path VARCHAR(1024) NULL"
                        )
                    except Exception:
                        pass  # Spalte existiert bereits
                    try:
                        await cur.execute(
                            "ALTER TABLE library_series "
                            "ADD COLUMN genres VARCHAR(512) NULL"
                        )
                    except Exception:
                        pass

            logging.info("TVDB-Cache-Tabellen initialisiert")
        except Exception as e:
            logging.error(f"TVDB-Tabellen erstellen fehlgeschlagen: {e}")

    @staticmethod
    def _serialize_row(row: dict) -> dict:
        """DB-Row JSON-kompatibel machen (datetime -> str)"""
        result = {}
        for k, v in row.items():
            if hasattr(v, "isoformat"):
                result[k] = str(v)
            else:
                result[k] = v
        return result

    # === Suche ===

    def _localize_search_result(self, item: dict) -> tuple[str, str]:
        """Lokalisierten Namen + Overview aus Suchergebnis extrahieren.
        Nutzt konfigurierte Sprache, Fallback auf Englisch."""
        lang = self._language
        name = item.get("name", "")
        overview = item.get("overview", "")

        # translations = {"deu": "Deutscher Titel", "eng": "English", ...}
        trans = item.get("translations") or {}
        if isinstance(trans, dict):
            name = trans.get(lang) or name

        # overviews = {"deu": "Deutsche Beschreibung", "eng": "English", ...}
        overviews = item.get("overviews") or {}
        if isinstance(overviews, dict):
            overview = (overviews.get(lang)
                        or overviews.get("eng")
                        or overview)

        return name, overview

    async def search_series(self, query: str,
                            language: Optional[str] = None) -> list[dict]:
        """Sucht Serien auf TVDB.

        Args:
            query: Suchbegriff
            language: Sprache fuer Ergebnisse (z.B. 'deu', 'eng').
                      None = konfigurierte Sprache verwenden.
        """
        client = self._get_client()
        if not client:
            return []

        # Sprache fuer Lokalisierung
        display_lang = language or self._language

        try:
            results = client.search(query, type="series")
            if not results:
                return []

            series_list = []
            for item in results[:20]:  # 20 statt 10 Ergebnisse
                # Lokalisierung mit gewaehlter Sprache
                name = item.get("name", "")
                overview = item.get("overview", "")

                trans = item.get("translations") or {}
                if isinstance(trans, dict):
                    # Gewaehlte Sprache oder Original
                    name = trans.get(display_lang) or name

                overviews = item.get("overviews") or {}
                if isinstance(overviews, dict):
                    overview = (overviews.get(display_lang)
                                or overviews.get("eng")
                                or overview)

                # Original-Name fuer Anzeige wenn anders
                original_name = item.get("name", "")

                series_list.append({
                    "tvdb_id": item.get("tvdb_id") or item.get("objectID"),
                    "name": name,
                    "original_name": original_name if original_name != name else "",
                    "overview": overview,
                    "first_air_date": item.get("first_air_time")
                                      or item.get("firstAirDate", ""),
                    "year": item.get("year", ""),
                    "status": item.get("status", ""),
                    "poster": item.get("thumbnail")
                              or item.get("image_url", ""),
                })
            return series_list
        except Exception as e:
            logging.error(f"TVDB Suche fehlgeschlagen: {e}")
            return []

    async def search_movies(self, query: str) -> list[dict]:
        """Sucht Filme auf TVDB"""
        client = self._get_client()
        if not client:
            return []

        try:
            results = client.search(query, type="movie")
            if not results:
                return []

            movie_list = []
            for item in results[:10]:
                name, overview = self._localize_search_result(item)
                movie_list.append({
                    "tvdb_id": item.get("tvdb_id") or item.get("objectID"),
                    "name": name,
                    "overview": overview,
                    "year": item.get("year", ""),
                    "poster": item.get("thumbnail")
                              or item.get("image_url", ""),
                })
            return movie_list
        except Exception as e:
            logging.error(f"TVDB Film-Suche fehlgeschlagen: {e}")
            return []

    # === Serien-Info ===

    async def get_series_info(self, tvdb_id: int) -> Optional[dict]:
        """Holt Serien-Details von TVDB (Dict-basiert)"""
        client = self._get_client()
        if not client:
            return None

        try:
            series = client.get_series_extended(tvdb_id)
            if not series:
                return None

            # API gibt Dict zurueck
            poster_url = series.get("image", "")
            name = series.get("name", "")
            overview = series.get("overview", "")

            # Lokalisierte Uebersetzung holen (Fallback Englisch)
            pref_lang = self._language
            for lang in (pref_lang, "eng"):
                try:
                    trans = client.get_series_translation(tvdb_id, lang)
                    if trans:
                        if not overview and trans.get("overview"):
                            overview = trans["overview"]
                        if lang == pref_lang:
                            if trans.get("overview"):
                                overview = trans["overview"]
                            if trans.get("name"):
                                name = trans["name"]
                        if overview:
                            break
                except Exception:
                    pass

            artworks = series.get("artworks") or []
            for art in artworks:
                art_type = art.get("type", {})
                type_id = art_type.get("id", 0) if isinstance(art_type, dict) else art_type
                if type_id == 2:
                    poster_url = art.get("image", poster_url)
                    break

            # Staffeln zaehlen
            seasons = []
            for s in (series.get("seasons") or []):
                s_type = s.get("type", {})
                type_id = s_type.get("id", 0) if isinstance(s_type, dict) else 0
                # Typ 1 = official
                if type_id == 1 or not s_type:
                    s_num = s.get("number", 0)
                    if s_num and s_num > 0:
                        seasons.append(s_num)

            # Genres
            genres = []
            for g in (series.get("genres") or []):
                gname = g.get("name", "")
                if gname:
                    genres.append(gname)

            # Status
            status_obj = series.get("status", {})
            status_name = ""
            if isinstance(status_obj, dict):
                status_name = status_obj.get("name", "")
            elif isinstance(status_obj, str):
                status_name = status_obj

            return {
                "tvdb_id": tvdb_id,
                "name": name,
                "overview": overview,
                "first_aired": series.get("firstAired", ""),
                "status": status_name,
                "poster_url": poster_url,
                "total_seasons": len(seasons),
                "genres": ", ".join(genres),
            }
        except Exception as e:
            logging.error(f"TVDB Serien-Info fehlgeschlagen (ID {tvdb_id}): {e}")
            return None

    # === Film-Info ===

    async def get_movie_info(self, tvdb_id: int) -> Optional[dict]:
        """Holt Film-Details von TVDB"""
        client = self._get_client()
        if not client:
            return None

        try:
            movie = client.get_movie_extended(tvdb_id)
            if not movie:
                return None

            poster_url = movie.get("image", "")
            name = movie.get("name", "")
            overview = movie.get("overview", "")

            # Lokalisierte Uebersetzung holen (Fallback Englisch)
            pref_lang = self._language
            for lang in (pref_lang, "eng"):
                try:
                    trans = client.get_movie_translation(tvdb_id, lang)
                    if trans:
                        if not overview and trans.get("overview"):
                            overview = trans["overview"]
                        if lang == pref_lang and trans.get("name"):
                            name = trans["name"]
                        if overview:
                            break
                except Exception:
                    pass

            # Genres
            genres = []
            for g in (movie.get("genres") or []):
                gname = g.get("name", "")
                if gname:
                    genres.append(gname)

            # Status
            status_obj = movie.get("status", {})
            status_name = ""
            if isinstance(status_obj, dict):
                status_name = status_obj.get("name", "")
            elif isinstance(status_obj, str):
                status_name = status_obj

            # Jahr
            year = movie.get("year")
            if not year:
                first_release = movie.get("first_release", {})
                if isinstance(first_release, dict):
                    date_str = first_release.get("date", "")
                    if date_str and len(date_str) >= 4:
                        try:
                            year = int(date_str[:4])
                        except ValueError:
                            pass

            return {
                "tvdb_id": tvdb_id,
                "name": name,
                "overview": overview,
                "year": year,
                "poster_url": poster_url,
                "genres": ", ".join(genres),
                "runtime": movie.get("runtime"),
                "status": status_name,
            }
        except Exception as e:
            logging.error(
                f"TVDB Film-Info fehlgeschlagen (ID {tvdb_id}): {e}"
            )
            return None

    async def match_and_update_movie(self, movie_id: int,
                                     tvdb_id: int,
                                     library_service: 'LibraryService'
                                     ) -> dict:
        """TVDB-ID einem Film zuordnen und Infos aktualisieren"""
        info = await self.get_movie_info(tvdb_id)
        if not info:
            return {"error": "TVDB Film-Info nicht gefunden"}

        pool = self._db_pool
        if not pool:
            return {"error": "Keine DB-Verbindung"}

        try:
            async with pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        UPDATE library_movies SET
                            tvdb_id = %s,
                            title = %s,
                            overview = %s,
                            year = %s,
                            poster_url = %s,
                            genres = %s,
                            runtime = %s,
                            status = %s,
                            last_updated = NOW()
                        WHERE id = %s
                    """, (
                        tvdb_id,
                        info["name"],
                        info.get("overview", ""),
                        info.get("year"),
                        info.get("poster_url", ""),
                        info.get("genres", ""),
                        info.get("runtime"),
                        info.get("status", ""),
                        movie_id,
                    ))

            return {
                "success": True,
                "name": info["name"],
                "year": info.get("year"),
            }
        except Exception as e:
            logging.error(f"TVDB Film-Match fehlgeschlagen: {e}")
            return {"error": str(e)}

    # === Cast / Characters ===

    async def get_series_characters(self, tvdb_id: int) -> list[dict]:
        """Holt Cast/Darsteller von TVDB.
        Prueft zuerst DB-Cache, dann TVDB-API."""
        # Cache pruefen
        cached = await self._get_cached_cast(tvdb_id)
        if cached:
            return cached

        client = self._get_client()
        if not client:
            return []

        try:
            series = client.get_series_extended(tvdb_id)
            if not series:
                return []

            characters = []
            for c in (series.get("characters") or []):
                # type=3 ist Actor
                if c.get("type") != 3:
                    continue
                characters.append({
                    "person_name": c.get("personName", ""),
                    "character_name": c.get("name", ""),
                    "sort_order": c.get("sort", 0),
                    "image_url": c.get("image", ""),
                    "person_image_url": c.get("personImgURL", ""),
                })

            # Sortieren
            characters.sort(key=lambda x: x.get("sort_order", 999))

            # In DB cachen
            await self._cache_cast(tvdb_id, characters)

            return characters
        except Exception as e:
            logging.error(f"TVDB Cast laden fehlgeschlagen (ID {tvdb_id}): {e}")
            return []

    async def _get_cached_cast(self, tvdb_id: int) -> Optional[list[dict]]:
        """Cast aus DB-Cache laden"""
        if not self._db_pool:
            return None
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor(aiomysql.DictCursor) as cur:
                    await cur.execute(
                        "SELECT * FROM tvdb_cast_cache "
                        "WHERE series_tvdb_id = %s ORDER BY sort_order",
                        (tvdb_id,)
                    )
                    rows = await cur.fetchall()
                    if rows:
                        return [self._serialize_row(r) for r in rows]
        except Exception:
            pass
        return None

    async def _cache_cast(self, tvdb_id: int,
                          characters: list[dict]) -> None:
        """Darsteller in DB cachen"""
        if not self._db_pool:
            return
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute(
                        "DELETE FROM tvdb_cast_cache "
                        "WHERE series_tvdb_id = %s", (tvdb_id,)
                    )
                    for c in characters:
                        await cur.execute(
                            "INSERT INTO tvdb_cast_cache "
                            "(series_tvdb_id, person_name, character_name, "
                            "sort_order, image_url, person_image_url) "
                            "VALUES (%s, %s, %s, %s, %s, %s)",
                            (tvdb_id, c["person_name"],
                             c["character_name"], c["sort_order"],
                             c["image_url"], c["person_image_url"])
                        )
        except Exception as e:
            logging.error(f"TVDB Cast cachen fehlgeschlagen: {e}")

    # === Artworks ===

    async def get_series_artworks(self, tvdb_id: int) -> list[dict]:
        """Holt Artworks einer Serie.
        Prueft zuerst DB-Cache, dann TVDB-API."""
        cached = await self._get_cached_artworks(tvdb_id)
        if cached:
            return cached

        client = self._get_client()
        if not client:
            return []

        try:
            series = client.get_series_extended(tvdb_id)
            if not series:
                return []

            artworks = []
            for a in (series.get("artworks") or []):
                art_type_obj = a.get("type", {})
                type_id = art_type_obj.get("id", 0) if isinstance(
                    art_type_obj, dict
                ) else art_type_obj
                type_name = ARTWORK_TYPE_MAP.get(type_id)
                if not type_name:
                    continue

                artworks.append({
                    "artwork_type": type_name,
                    "image_url": a.get("image", ""),
                    "thumbnail_url": a.get("thumbnail", ""),
                    "width": a.get("width", 0),
                    "height": a.get("height", 0),
                })

            # Cachen
            await self._cache_artworks(tvdb_id, artworks)

            return artworks
        except Exception as e:
            logging.error(f"TVDB Artworks laden fehlgeschlagen (ID {tvdb_id}): {e}")
            return []

    async def _get_cached_artworks(self, tvdb_id: int) -> Optional[list[dict]]:
        """Artworks aus DB-Cache laden"""
        if not self._db_pool:
            return None
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor(aiomysql.DictCursor) as cur:
                    await cur.execute(
                        "SELECT * FROM tvdb_artwork_cache "
                        "WHERE series_tvdb_id = %s "
                        "ORDER BY artwork_type, is_primary DESC",
                        (tvdb_id,)
                    )
                    rows = await cur.fetchall()
                    if rows:
                        return [self._serialize_row(r) for r in rows]
        except Exception:
            pass
        return None

    async def _cache_artworks(self, tvdb_id: int,
                              artworks: list[dict]) -> None:
        """Artworks in DB cachen"""
        if not self._db_pool:
            return
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute(
                        "DELETE FROM tvdb_artwork_cache "
                        "WHERE series_tvdb_id = %s", (tvdb_id,)
                    )
                    seen_primary = set()
                    for a in artworks:
                        art_type = a["artwork_type"]
                        is_primary = 1 if art_type not in seen_primary else 0
                        seen_primary.add(art_type)
                        await cur.execute(
                            "INSERT INTO tvdb_artwork_cache "
                            "(series_tvdb_id, artwork_type, image_url, "
                            "thumbnail_url, width, height, is_primary) "
                            "VALUES (%s, %s, %s, %s, %s, %s, %s)",
                            (tvdb_id, art_type, a["image_url"],
                             a["thumbnail_url"], a["width"],
                             a["height"], is_primary)
                        )
        except Exception as e:
            logging.error(f"TVDB Artworks cachen fehlgeschlagen: {e}")

    # === Metadaten herunterladen ===

    async def download_metadata(self, series_id: int, tvdb_id: int,
                                 series_folder: str) -> dict:
        """Laedt Poster, Fanart, Cast-Bilder in .metadata/ Ordner"""
        if not series_folder or not os.path.isdir(series_folder):
            return {"error": "Serien-Ordner nicht gefunden"}

        meta_dir = os.path.join(series_folder, ".metadata")
        os.makedirs(meta_dir, exist_ok=True)
        cast_dir = os.path.join(meta_dir, "cast")
        os.makedirs(cast_dir, exist_ok=True)

        downloaded = 0
        errors = 0

        # Artworks holen (ggf. von API)
        artworks = await self.get_series_artworks(tvdb_id)
        # Cast holen (ggf. von API)
        cast = await self.get_series_characters(tvdb_id)

        async with aiohttp.ClientSession() as session:
            # Primaere Bilder herunterladen (Poster, Fanart, Banner)
            seen_types = set()
            for art in artworks:
                art_type = art["artwork_type"]
                if art_type in seen_types:
                    continue
                if art_type not in ("poster", "fanart", "banner"):
                    continue
                seen_types.add(art_type)

                url = art["image_url"]
                if not url:
                    continue
                ext = os.path.splitext(url)[1] or ".jpg"
                target = os.path.join(meta_dir, f"{art_type}{ext}")

                ok = await self._download_file(session, url, target)
                if ok:
                    downloaded += 1
                else:
                    errors += 1

            # Cast-Bilder herunterladen
            for c in cast:
                url = c.get("person_image_url") or c.get("image_url", "")
                name = c.get("person_name", "")
                if not url or not name:
                    continue
                # Sicherer Dateiname
                safe_name = "".join(
                    ch if ch.isalnum() or ch in " _-" else "_"
                    for ch in name
                ).strip()
                ext = os.path.splitext(url)[1] or ".jpg"
                target = os.path.join(cast_dir, f"{safe_name}{ext}")

                ok = await self._download_file(session, url, target)
                if ok:
                    downloaded += 1
                else:
                    errors += 1

        # metadata_path in DB setzen
        if self._db_pool:
            try:
                async with self._db_pool.acquire() as conn:
                    async with conn.cursor() as cur:
                        await cur.execute(
                            "UPDATE library_series SET metadata_path = %s "
                            "WHERE id = %s",
                            (meta_dir, series_id)
                        )
            except Exception:
                pass

        logging.info(
            f"TVDB Metadaten heruntergeladen fuer Serie {series_id}: "
            f"{downloaded} Dateien, {errors} Fehler"
        )
        return {
            "success": True,
            "downloaded": downloaded,
            "errors": errors,
            "metadata_path": meta_dir,
        }

    @staticmethod
    async def _download_file(session: aiohttp.ClientSession,
                              url: str, target: str) -> bool:
        """Einzelne Datei herunterladen"""
        try:
            async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as resp:
                if resp.status != 200:
                    return False
                data = await resp.read()
                with open(target, "wb") as f:
                    f.write(data)
                return True
        except Exception as e:
            logging.warning(f"Download fehlgeschlagen: {url}: {e}")
            return False

    # === Episoden ===

    async def fetch_episodes(self, tvdb_id: int) -> list[dict]:
        """Holt alle Episoden einer Serie von TVDB und cached sie in DB.
        Nutzt konfigurierte Sprache fuer Episodennamen."""
        client = self._get_client()
        if not client:
            return []

        pref_lang = self._language

        try:
            episodes = []
            page = 0
            while True:
                # Mit Sprach-Parameter abrufen fuer lokalisierte Namen
                result = client.get_series_episodes(
                    tvdb_id, season_type="official", page=page,
                    lang=pref_lang,
                )
                if not result:
                    break
                # API gibt Dict zurueck, kein Objekt
                if isinstance(result, dict):
                    eps = result.get("episodes", [])
                elif hasattr(result, "episodes"):
                    eps = result.episodes
                else:
                    break
                if not eps:
                    break
                for ep in eps:
                    if isinstance(ep, dict):
                        s_num = ep.get("seasonNumber", 0)
                        e_num = ep.get("number", 0)
                        ep_name = ep.get("name", "")
                        ep_aired = ep.get("aired")
                        ep_runtime = ep.get("runtime")
                    else:
                        s_num = getattr(ep, "seasonNumber", 0)
                        e_num = getattr(ep, "number", 0)
                        ep_name = getattr(ep, "name", "")
                        ep_aired = getattr(ep, "aired", None)
                        ep_runtime = getattr(ep, "runtime", None)
                    if s_num and s_num > 0 and e_num and e_num > 0:
                        # Beschreibung und Bild-URL
                        if isinstance(ep, dict):
                            ep_overview = ep.get("overview", "")
                            ep_image = ep.get("image", "")
                        else:
                            ep_overview = getattr(ep, "overview", "")
                            ep_image = getattr(ep, "image", "")
                        episodes.append({
                            "season_number": s_num,
                            "episode_number": e_num,
                            "episode_name": ep_name or "",
                            "aired": ep_aired,
                            "runtime": ep_runtime,
                            "overview": ep_overview or "",
                            "image_url": ep_image or "",
                        })
                page += 1
                if page > 50:
                    break

            if episodes and self._db_pool:
                await self._cache_episodes(tvdb_id, episodes)

            logging.info(f"TVDB: {len(episodes)} Episoden fuer "
                         f"Serie {tvdb_id} geladen ({pref_lang})")
            return episodes
        except Exception as e:
            logging.error(f"TVDB Episoden laden fehlgeschlagen "
                          f"(ID {tvdb_id}): {e}")
            return []

    async def _cache_episodes(self, tvdb_id: int,
                              episodes: list[dict]) -> None:
        """Episoden in DB cachen"""
        if not self._db_pool:
            return
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute(
                        "DELETE FROM tvdb_episode_cache "
                        "WHERE series_tvdb_id = %s",
                        (tvdb_id,)
                    )
                    for ep in episodes:
                        await cur.execute(
                            "INSERT INTO tvdb_episode_cache "
                            "(series_tvdb_id, season_number, episode_number, "
                            "episode_name, aired, runtime, overview, "
                            "image_url) "
                            "VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
                            (
                                tvdb_id, ep["season_number"],
                                ep["episode_number"], ep["episode_name"],
                                ep["aired"], ep["runtime"],
                                ep.get("overview", ""),
                                ep.get("image_url", ""),
                            )
                        )
        except Exception as e:
            logging.error(f"TVDB Episode cachen fehlgeschlagen: {e}")

    # === Match & Update ===

    async def match_and_update_series(self, series_id: int,
                                      tvdb_id: int,
                                      library_service: 'LibraryService'
                                      ) -> dict:
        """TVDB-ID zuordnen, Infos holen, Episoden + Cast + Artworks cachen"""
        info = await self.get_series_info(tvdb_id)
        if not info:
            return {"error": "TVDB-Serien-Info nicht gefunden"}

        episodes = await self.fetch_episodes(tvdb_id)

        # Cast und Artworks cachen (im Hintergrund)
        await self.get_series_characters(tvdb_id)
        await self.get_series_artworks(tvdb_id)

        pool = self._db_pool
        if not pool:
            return {"error": "Keine DB-Verbindung"}

        try:
            async with pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        UPDATE library_series SET
                            tvdb_id = %s,
                            title = %s,
                            overview = %s,
                            first_aired = %s,
                            poster_url = %s,
                            status = %s,
                            total_seasons = %s,
                            total_episodes = %s,
                            genres = %s,
                            last_updated = NOW()
                        WHERE id = %s
                    """, (
                        tvdb_id,
                        info["name"],
                        info.get("overview", ""),
                        info.get("first_aired") or None,
                        info.get("poster_url", ""),
                        info.get("status", ""),
                        info.get("total_seasons", 0),
                        len(episodes),
                        info.get("genres", ""),
                        series_id,
                    ))

            await self._update_episode_titles(series_id, tvdb_id)
            await library_service._update_series_counts(series_id)

            return {
                "success": True,
                "name": info["name"],
                "total_episodes": len(episodes),
            }
        except Exception as e:
            logging.error(f"TVDB Match fehlgeschlagen: {e}")
            return {"error": str(e)}

    async def _update_episode_titles(self, series_id: int,
                                     tvdb_id: int) -> None:
        """Episoden-Titel aus TVDB-Cache in lokale Videos uebertragen"""
        if not self._db_pool:
            return
        try:
            async with self._db_pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        UPDATE library_videos v
                        JOIN tvdb_episode_cache tc
                            ON tc.series_tvdb_id = %s
                            AND tc.season_number = v.season_number
                            AND tc.episode_number = v.episode_number
                        SET v.episode_title = tc.episode_name
                        WHERE v.series_id = %s
                    """, (tvdb_id, series_id))
        except Exception as e:
            logging.error(f"Episoden-Titel aktualisieren fehlgeschlagen: {e}")

    # === Auto-Match ===

    @staticmethod
    def _clean_search_title(title: str) -> tuple[str, str]:
        """Bereinigt Titel fuer bessere TVDB-Suche.
        Entfernt fuehrende Sortier-Nummern, Aufloesung-Suffixe, Klammern."""
        import re
        t = title.strip()
        # Fuehrende Sortier-Nummern entfernen ("1 X-Men" -> "X-Men")
        t = re.sub(r'^\d{1,2}\s+', '', t)
        # Aufloesung/Qualitaets-Suffixe entfernen
        t = re.sub(
            r'\s*(720p|1080p|2160p|4k|bluray|bdrip|webrip|web-dl|hdtv|'
            r'x264|x265|hevc|aac|dts|remux)\s*',
            ' ', t, flags=re.IGNORECASE
        ).strip()
        # Klammern mit Inhalt entfernen "(2020)" etc.
        t_no_parens = re.sub(r'\s*\([^)]*\)\s*', ' ', t).strip()
        # Trailing-Nummern entfernen ("X-Men 1" -> "X-Men")
        t_no_num = re.sub(r'\s+\d{1,2}$', '', t)
        # Variante 1: bereinigt, Variante 2: ohne Klammern und Trailing-Nummern
        clean1 = t.strip()
        clean2 = t_no_parens.strip() if t_no_parens != clean1 else t_no_num.strip()
        return clean1, clean2

    async def collect_suggestions(
        self,
        media_type: str,
        progress_callback=None,
    ) -> list[dict]:
        """Sammelt TVDB-Vorschlaege fuer alle Serien oder Filme ohne TVDB.
        media_type: 'series' oder 'movies'
        Gibt Liste von Vorschlaegen zurueck: [{
            id, local_name, year, type,
            suggestions: [{tvdb_id, name, year, poster, overview}]
        }]"""
        if not self._db_pool:
            return []

        # Items ohne TVDB laden
        async with self._db_pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cur:
                if media_type == "series":
                    await cur.execute(
                        "SELECT id, folder_name, title "
                        "FROM library_series WHERE tvdb_id IS NULL "
                        "ORDER BY title"
                    )
                else:
                    await cur.execute(
                        "SELECT id, folder_name, title, year "
                        "FROM library_movies WHERE tvdb_id IS NULL "
                        "ORDER BY title"
                    )
                items = await cur.fetchall()

        total = len(items)
        proposals = []

        for i, item in enumerate(items):
            name = item.get("title") or item["folder_name"]
            t_full, t_clean = self._clean_search_title(name)

            try:
                search_fn = (self.search_series if media_type == "series"
                             else self.search_movies)
                results = await search_fn(t_full)
                if not results and t_clean != t_full:
                    results = await search_fn(t_clean)
            except Exception as e:
                logging.warning(f"TVDB-Suche fehlgeschlagen: {name}: {e}")
                results = []

            # Top 3 Vorschlaege sammeln
            suggestions = []
            for r in (results or [])[:3]:
                suggestions.append({
                    "tvdb_id": r.get("tvdb_id"),
                    "name": r.get("name", ""),
                    "year": r.get("year", ""),
                    "poster": r.get("poster", ""),
                    "overview": (r.get("overview") or "")[:150],
                })

            proposals.append({
                "id": item["id"],
                "local_name": name,
                "year": item.get("year"),
                "type": media_type,
                "suggestions": suggestions,
            })

            if progress_callback:
                await progress_callback(
                    i + 1, total, name, len(proposals)
                )

        return proposals
