#!/usr/bin/env python3
"""jellyfin-cli — Jellyfin media server from the terminal.

Query recently added media, search your library, browse by collection,
and check server status. Requires JELLYFIN_URL and JELLYFIN_API_KEY.
"""

import argparse
import json
import os
import sys
import warnings
from typing import Any, Dict, List, Optional, Tuple

warnings.simplefilter("ignore")

import requests

DEFAULT_SERVER = "http://localhost:8096"
ENV_URL = os.getenv("JELLYFIN_URL", DEFAULT_SERVER)
ENV_KEY = os.getenv("JELLYFIN_API_KEY", "")

QUIET = False
GLOBAL_FLAGS: Dict[str, Any] = {"json": False, "dry_run": False, "quiet": False, "verbose": False}


def log(msg):
    if not QUIET and not GLOBAL_FLAGS.get("json", False):
        print(msg)


def warn(msg):
    print(f"Warning: {msg}", file=sys.stderr)


def die(msg, exit_code=1):
    print(f"Error: {msg}", file=sys.stderr)
    sys.exit(exit_code)


def emit(human, data):
    if GLOBAL_FLAGS.get("json", False):
        print(json.dumps(data, default=str))
    else:
        print(human)


def _preparse_global_flags(argv):
    GLOBAL_BOOLS = {"--json", "--dry-run", "--quiet", "--verbose"}
    flags, filtered = {}, [argv[0]]
    i = 1
    while i < len(argv):
        arg = argv[i]
        if arg in GLOBAL_BOOLS:
            flags[arg.lstrip("-").replace("-", "_")] = True
            i += 1
        elif arg in ("--help", "-h"):
            return flags, argv
        elif arg == "--":
            filtered.extend(argv[i:])
            break
        else:
            filtered.append(arg)
            i += 1
    return flags, filtered


class JellyfinClient:
    """Jellyfin API client (v10.8+ compatible)."""

    def __init__(self, url="", key="", dry_run=False):
        self.url = (url or ENV_URL).rstrip("/")
        self.key = key or ENV_KEY
        self.dry_run = dry_run

    def _get(self, path, params=None):
        url = f"{self.url}{path}"
        if self.dry_run:
            return {"dry_run": True, "url": url, "params": params}
        if not self.key:
            die("JELLYFIN_API_KEY not set. Generate one in Dashboard → API Keys.")
        try:
            resp = requests.get(url, params=params,
                                headers={"X-Emby-Token": self.key, "Accept": "application/json"},
                                timeout=30)
        except requests.ConnectionError as e:
            die(f"Cannot connect to {self.url}: {e}")
        if resp.status_code == 401:
            die("Auth failed (401). Check JELLYFIN_API_KEY.")
        if resp.status_code >= 400:
            try:
                detail = resp.json()
            except Exception:
                detail = resp.text[:200]
            die(f"API error ({resp.status_code}): {detail}")
        return resp.json()

    def get_info(self):
        return self._get("/System/Info")

    def get_users(self):
        return self._get("/Users")

    def get_recent(self, user_id, limit=10, fields="Overview,DateCreated"):
        return self._get(f"/Users/{user_id}/Items/Latest",
                         params={"limit": limit, "fields": fields})

    def get_next_up(self, user_id, limit=10):
        return self._get(f"/Shows/NextUp",
                         params={"userId": user_id, "limit": limit})

    def search(self, query, limit=20, include_types=None):
        params = {"searchTerm": query, "limit": limit, "recursive": True}
        if include_types:
            params["includeItemTypes"] = ",".join(include_types)
        return self._get("/Search/Hints", params=params)

    def get_libraries(self):
        return self._get("/Library/MediaFolders")

    def get_items(self, parent_id, types=None, limit=50, sort_by="SortName",
                  sort_order="Ascending", start_index=0):
        params = {"parentId": parent_id, "limit": limit,
                  "sortBy": sort_by, "sortOrder": sort_order,
                  "startIndex": start_index, "recursive": True}
        if types:
            params["includeItemTypes"] = ",".join(types)
        return self._get("/Items", params=params)

    def get_item(self, item_id):
        return self._get(f"/Items/{item_id}")

    def get_seasons(self, series_id, user_id):
        return self._get(f"/Shows/{series_id}/Seasons", params={"userId": user_id})

    def get_episodes(self, series_id, season_id, user_id):
        return self._get(f"/Shows/{series_id}/Episodes",
                         params={"seasonId": season_id, "userId": user_id})

    def get_stats(self):
        return self._get("/Items/Counts")


def get_admin_user_id(client):
    users = client.get_users() or []
    admin = next((u for u in users if u.get("Policy", {}).get("IsAdministrator")), None)
    return admin.get("Id", "") if admin else ""


def fmt_date(ts):
    if not ts:
        return "?"
    return ts[:10] if len(ts) > 10 else ts


def cmd_info(client, args):
    if client.dry_run:
        return emit("[dry-run] Get server info", {"dry_run": True})
    data = client.get_info() or {}
    name = data.get("ServerName", "?")
    version = data.get("Version", "?")
    os_info = f"{data.get('OperatingSystem', '?')}"
    users = len(client.get_users() or [])
    emit(f"🖥️  {name}  v{version}\n   OS: {os_info}\n   Users: {users}",
         {"name": name, "version": version, "operating_system": os_info, "users": users})


def cmd_recent(client, args):
    p = argparse.ArgumentParser(prog="jellyfin-cli recent")
    p.add_argument("--limit", type=int, default=10)
    p.add_argument("--movies", action="store_true")
    p.add_argument("--episodes", action="store_true")
    parsed, _ = p.parse_known_args(args)

    if client.dry_run:
        return emit("[dry-run] Get recent items", {"dry_run": True})

    uid = get_admin_user_id(client)
    if not uid:
        return emit("No admin user found.", {"error": "no admin user"})

    items = client.get_recent(uid, limit=parsed.limit) or []
    if not items:
        return emit("No recent items.", {"items": []})

    if parsed.episodes:
        items = [i for i in items if i.get("Type") == "Episode"]
    elif parsed.movies:
        items = [i for i in items if i.get("Type") == "Movie"]

    lines, out = [], []
    for i in items:
        name = i.get("Name", "?")
        itype = i.get("Type", "?")
        date = fmt_date(i.get("DateCreated", ""))
        year = i.get("ProductionYear", "")
        series = i.get("SeriesName", "")
        series_str = f" [{series}]" if series else ""
        lines.append(f"  {name:45}{series_str}  ({year})  {itype}  added {date}")
        out.append({"name": name, "type": itype, "year": year,
                     "series": series, "date_added": date, "id": i.get("Id")})
    emit(f"Recently added:\n" + "\n".join(lines), {"items": out})


def cmd_search(client, args):
    p = argparse.ArgumentParser(prog="jellyfin-cli search")
    p.add_argument("--query", "-q", required=True)
    p.add_argument("--type", help="Comma-separated types (Movie,Series,Episode)")
    p.add_argument("--limit", type=int, default=20)
    parsed, _ = p.parse_known_args(args)

    if client.dry_run:
        return emit(f"[dry-run] Search: {parsed.query}", {"dry_run": True})

    types = parsed.type.split(",") if parsed.type else None
    data = client.search(parsed.query, limit=parsed.limit, include_types=types) or {}
    hints = data.get("SearchHints", [])
    if not hints:
        return emit("No results.", {"results": []})

    lines, out = [], []
    for h in hints:
        name = h.get("Name", "?")
        itype = h.get("Type", "?")
        year = h.get("ProductionYear", "")
        series = h.get("Series", "")
        series_str = f" [{series}]" if series else ""
        lines.append(f"  {name:45}{series_str}  ({year})  [{itype}]")
        out.append({"name": name, "type": itype, "year": year, "series": series, "id": h.get("ItemId")})
    emit(f"{len(hints)} result(s):\n" + "\n".join(lines), {"results": out})


def cmd_libraries(client, args):
    if client.dry_run:
        return emit("[dry-run] List libraries", {"dry_run": True})
    data = client.get_libraries() or []
    if not data:
        return emit("No libraries found.", {"libraries": []})
    lines, out = [], []
    for lib in data:
        name = lib.get("Name", "?")
        lid = lib.get("Id", "?")
        ctype = lib.get("CollectionType", "?")
        lines.append(f"  {name:30}  [{ctype}]  id={lid}")
        out.append({"name": name, "id": lid, "type": ctype})
    emit(f"{len(data)} libraries:\n" + "\n".join(lines), {"libraries": out})


def cmd_stats(client, args):
    if client.dry_run:
        return emit("[dry-run] Get server stats", {"dry_run": True})
    data = client.get_stats() or {}
    emit(f"📊 Library stats:\n"
         f"   Movies: {data.get('MovieCount', '?')}\n"
         f"   Series: {data.get('SeriesCount', '?')}\n"
         f"   Episodes: {data.get('EpisodeCount', '?')}\n"
         f"   Songs: {data.get('SongCount', '?')}",
         {"movies": data.get("MovieCount"), "series": data.get("SeriesCount"),
          "episodes": data.get("EpisodeCount"), "songs": data.get("SongCount")})


def main():
    global GLOBAL_FLAGS, QUIET
    GLOBAL_FLAGS, filtered_argv = _preparse_global_flags(sys.argv)
    if GLOBAL_FLAGS.get("quiet", False):
        QUIET = True
    if GLOBAL_FLAGS.get("json", False):
        warnings.simplefilter("ignore")

    parser = argparse.ArgumentParser(prog="jellyfin-cli", description="Jellyfin media server CLI.")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("info", help="Server info")
    re = sub.add_parser("recent", help="Recently added"); re.add_argument("--limit", type=int, default=10); re.add_argument("--movies", action="store_true"); re.add_argument("--episodes", action="store_true")
    se = sub.add_parser("search", help="Search media"); se.add_argument("--query", "-q", required=True); se.add_argument("--type"); se.add_argument("--limit", type=int, default=20)
    sub.add_parser("libraries", help="List libraries")
    sub.add_parser("stats", help="Library statistics")

    args = parser.parse_args(filtered_argv[1:])
    if not args.command:
        parser.print_help()
        sys.exit(1)

    client = JellyfinClient(dry_run=GLOBAL_FLAGS.get("dry_run", False))

    cmd_map = {
        "info": cmd_info, "recent": cmd_recent, "search": cmd_search,
        "libraries": cmd_libraries, "stats": cmd_stats,
    }
    handler = cmd_map.get(args.command)
    if not handler:
        parser.print_help()
        sys.exit(1)

    remaining = filtered_argv[filtered_argv.index(args.command) + 1:]
    handler(client, remaining)


if __name__ == "__main__":
    main()
