"""Importa una carpeta drop de Popelis via Radarr/Sonarr anadiendo metadata.

Escanea las carpetas drop (manual/movies, manual/tv), identifica cada
fichero contra TMDb/TVDB usando los endpoints de lookup de Radarr/Sonarr,
da de alta la pelicula/serie si no existe (descargando metadata: poster,
fanart, fichas), y dispara un ManualImport que mueve+renombra el fichero a
la libreria limpia (media/movies, media/tv). Las *arr son la unica fuente
de verdad de metadata -> Jellyfin solo escanea media/ y nunca mete fichas
fantasma de descargas a medias.

Solo stdlib (urllib). Orquestacion HTTP pura sobre las APIs v3 de
Radarr/Sonarr; la funcion NO toca ficheros (lo hace la *arr en su propio
namespace de contenedor).
"""

import json
import re
import time
import urllib.error
import urllib.parse
import urllib.request

_VIDEO_RE = re.compile(r"\.(mkv|mp4|avi|m4v|mov|wmv|ts|mpg|mpeg)$", re.I)


def _req(method: str, url: str, key: str, body=None, timeout: float = 60.0):
    """HTTP request a una *arr con X-Api-Key. Lanza ValueError en 4xx/5xx."""
    data = json.dumps(body).encode("utf-8") if body is not None else None
    req = urllib.request.Request(
        url,
        data=data,
        method=method,
        headers={"X-Api-Key": key, "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            raw = resp.read()
            return json.loads(raw) if raw else None
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")[:400]
        raise ValueError(f"{method} {url} -> HTTP {exc.code}: {detail}") from exc


def _title_from_path(rel: str) -> str:
    """Deriva un termino de busqueda del path relativo de un drop.

    Usa la carpeta raiz si existe (caso series: 'Futurama/Season 01/...'),
    si no el nombre del fichero. Limpia separadores, sufijos SxxExx y el
    anio + lo que venga detras para quedarse con el titulo limpio.
    """
    seg = rel.split("/")[0] if "/" in rel else rel
    base = _VIDEO_RE.sub("", seg)
    base = re.sub(r"[._]", " ", base)
    base = re.sub(r"\bS\d{1,2}E\d{1,2}.*$", "", base, flags=re.I).strip()
    base = re.sub(r"\b(19|20)\d{2}\b.*$", "", base).strip()
    return base or seg


def _import_radarr(url, key, drop, root, qpid, mode, dry, timeout):
    enc = urllib.parse.quote(drop)
    items = _req(
        "GET",
        f"{url}/api/v3/manualimport?folder={enc}&filterExistingFiles=true",
        key, timeout=timeout,
    ) or []
    existing = {m["tmdbId"]: m for m in _req("GET", f"{url}/api/v3/movie", key, timeout=timeout)}
    out, files = [], []
    for it in items:
        movie = it.get("movie")
        if not movie:
            term = _title_from_path(it["relativePath"])
            look = _req("GET", f"{url}/api/v3/movie/lookup?term={urllib.parse.quote(term)}", key, timeout=timeout)
            if not look:
                out.append({"file": it["relativePath"], "status": "no_match", "term": term})
                continue
            cand = look[0]
            if cand["tmdbId"] in existing:
                movie = existing[cand["tmdbId"]]
            elif dry:
                out.append({"file": it["relativePath"], "status": "would_add+import",
                            "match": f'{cand["title"]} ({cand.get("year")})'})
                continue
            else:
                cand.update({
                    "qualityProfileId": qpid, "rootFolderPath": root,
                    "monitored": True, "minimumAvailability": "released",
                    "addOptions": {"searchForMovie": False},
                })
                movie = _req("POST", f"{url}/api/v3/movie", key, cand, timeout=timeout)
                existing[movie["tmdbId"]] = movie
        if dry:
            out.append({"file": it["relativePath"], "status": "would_import",
                        "movie": f'{movie["title"]} ({movie.get("year")})'})
            continue
        files.append({
            "path": it["path"], "movieId": movie["id"], "quality": it["quality"],
            "languages": it.get("languages") or [{"id": 1, "name": "English"}],
        })
        out.append({"file": it["relativePath"], "status": "import_queued",
                    "movie": f'{movie["title"]} ({movie.get("year")})', "movieId": movie["id"]})
    if files and not dry:
        cmd = _req("POST", f"{url}/api/v3/command", key,
                   {"name": "ManualImport", "importMode": mode, "files": files}, timeout=timeout)
        for o in out:
            if o["status"] == "import_queued":
                o["commandId"] = cmd["id"]
    return out


def _import_sonarr(url, key, drop, root, qpid, mode, dry, refresh_wait, timeout):
    enc = urllib.parse.quote(drop)
    items = _req("GET", f"{url}/api/v3/manualimport?folder={enc}&filterExistingFiles=true", key, timeout=timeout) or []
    existing = {s["tvdbId"]: s for s in _req("GET", f"{url}/api/v3/series", key, timeout=timeout)}
    out = []
    # 1. dar de alta series desconocidas
    unknown = {}
    for it in items:
        if not it.get("series"):
            unknown.setdefault(_title_from_path(it["relativePath"]), True)
    added_any = False
    for term in unknown:
        look = _req("GET", f"{url}/api/v3/series/lookup?term={urllib.parse.quote(term)}", key, timeout=timeout)
        if not look:
            out.append({"series_term": term, "status": "no_match"})
            continue
        cand = look[0]
        if cand["tvdbId"] in existing:
            continue
        if dry:
            out.append({"series_term": term, "status": "would_add",
                        "match": f'{cand["title"]} ({cand.get("year")})'})
            continue
        for s in cand.get("seasons", []):
            s["monitored"] = True
        cand.update({
            "qualityProfileId": qpid, "rootFolderPath": root, "monitored": True,
            "addOptions": {"searchForMissingEpisodes": False, "searchForCutoffUnmetEpisodes": False},
        })
        srv = _req("POST", f"{url}/api/v3/series", key, cand, timeout=timeout)
        existing[srv["tvdbId"]] = srv
        added_any = True
        out.append({"series": f'{srv["title"]} ({srv.get("year")})', "status": "added", "seriesId": srv["id"]})
    if dry:
        for it in items:
            out.append({"file": it["relativePath"], "status": "would_import_after_add"})
        return out
    if added_any:
        time.sleep(refresh_wait)  # esperar a que Sonarr refresque la ficha de episodios
    # 2. re-listar: ahora los episodios estan matcheados
    items = _req("GET", f"{url}/api/v3/manualimport?folder={enc}&filterExistingFiles=true", key, timeout=timeout) or []
    files = []
    for it in items:
        eps = it.get("episodes") or []
        ser = it.get("series")
        if not ser or not eps:
            out.append({"file": it["relativePath"], "status": "still_unmatched",
                        "rej": [r.get("reason") for r in it.get("rejections", [])]})
            continue
        files.append({
            "path": it["path"], "seriesId": ser["id"],
            "episodeIds": [e["id"] for e in eps], "quality": it["quality"],
            "languages": it.get("languages") or [{"id": 1, "name": "English"}],
        })
        out.append({"file": it["relativePath"], "status": "import_queued",
                    "episodes": [f'S{e.get("seasonNumber"):02d}E{e.get("episodeNumber"):02d}' for e in eps]})
    if files:
        cmd = _req("POST", f"{url}/api/v3/command", key,
                   {"name": "ManualImport", "importMode": mode, "files": files}, timeout=timeout)
        for o in out:
            if o.get("status") == "import_queued":
                o["commandId"] = cmd["id"]
    return out


def popelis_import_media_drop(
    radarr_url: str,
    radarr_key: str,
    sonarr_url: str,
    sonarr_key: str,
    movies_drop: str = "/data/manual/movies",
    tv_drop: str = "/data/manual/tv",
    movie_root: str = "/data/media/movies",
    tv_root: str = "/data/media/tv",
    quality_profile_id: int = 4,
    import_mode: str = "move",
    dry_run: bool = False,
    series_refresh_wait: float = 6.0,
    timeout: float = 60.0,
) -> dict:
    """Importa los drops de pelis y series via Radarr/Sonarr con metadata.

    Pelis (Radarr): para cada fichero sin match, hace lookup por titulo,
    da de alta la pelicula (poster/fanart/ficha) si no existe y dispara un
    ManualImport (movieId explicito) que mueve el fichero a movie_root.

    Series (Sonarr): detecta series desconocidas, las da de alta, espera a
    que Sonarr refresque la ficha de episodios, re-lista el drop (ahora los
    SxxExx matchean) e importa cada episodio con su episodeId. Ficheros con
    numeracion fuera de la ficha (ej. orden DVD vs aired) quedan reportados
    como 'still_unmatched' y permanecen en el drop para resolucion manual.

    Args:
        radarr_url: URL base de Radarr sin trailing slash. Ej http://localhost:7878
        radarr_key: API key de Radarr (Settings > General).
        sonarr_url: URL base de Sonarr. Ej http://localhost:8989
        sonarr_key: API key de Sonarr.
        movies_drop: Carpeta drop de pelis EN EL NAMESPACE de Radarr (/data/...).
        tv_drop: Carpeta drop de series EN EL NAMESPACE de Sonarr (/data/...).
        movie_root: Root folder de Radarr donde se mueven las pelis.
        tv_root: Root folder de Sonarr donde se mueven las series.
        quality_profile_id: Quality profile para altas nuevas (4 = HD-1080p).
        import_mode: 'move' (default) o 'copy'. 'move' borra el origen.
        dry_run: Si True, no escribe nada: solo reporta que haria.
        series_refresh_wait: Segundos a esperar tras dar de alta una serie
                             antes de re-listar (Sonarr necesita refrescar).
        timeout: Timeout HTTP por peticion.

    Returns:
        Dict {"movies": [...], "tv": [...], "summary": {...}} con una entrada
        por fichero/serie y su status (import_queued | added | no_match |
        still_unmatched | would_*).

    Raises:
        ValueError: Si una peticion HTTP devuelve 4xx/5xx.
        urllib.error.URLError: Si no se puede conectar a una *arr.
    """
    movies = _import_radarr(radarr_url.rstrip("/"), radarr_key, movies_drop,
                            movie_root, quality_profile_id, import_mode, dry_run, timeout)
    tv = _import_sonarr(sonarr_url.rstrip("/"), sonarr_key, tv_drop, tv_root,
                        quality_profile_id, import_mode, dry_run, series_refresh_wait, timeout)
    summary = {
        "movies_queued": sum(1 for o in movies if o["status"] in ("import_queued", "would_add+import", "would_import")),
        "movies_unmatched": sum(1 for o in movies if o["status"] == "no_match"),
        "tv_queued": sum(1 for o in tv if o.get("status") == "import_queued"),
        "tv_added_series": sum(1 for o in tv if o.get("status") in ("added", "would_add")),
        "tv_unmatched": sum(1 for o in tv if o.get("status") in ("still_unmatched", "no_match")),
        "dry_run": dry_run,
    }
    return {"movies": movies, "tv": tv, "summary": summary}


if __name__ == "__main__":
    import argparse
    import os

    p = argparse.ArgumentParser(description="Importa drops de Popelis via Radarr/Sonarr")
    p.add_argument("--radarr-url", default=os.environ.get("RADARR_URL", "http://localhost:7878"))
    p.add_argument("--radarr-key", default=os.environ.get("RADARR_KEY", ""))
    p.add_argument("--sonarr-url", default=os.environ.get("SONARR_URL", "http://localhost:8989"))
    p.add_argument("--sonarr-key", default=os.environ.get("SONARR_KEY", ""))
    p.add_argument("--apply", action="store_true", help="Ejecuta de verdad (default dry-run)")
    args = p.parse_args()
    res = popelis_import_media_drop(
        args.radarr_url, args.radarr_key, args.sonarr_url, args.sonarr_key,
        dry_run=not args.apply,
    )
    print(json.dumps(res, indent=2, ensure_ascii=False))
