#!/usr/bin/env python3
"""peertube-cli — PeerTube federated video from the terminal.

Browse videos, channels, and playlists on any PeerTube instance.
Login with OAuth2 for authenticated operations.
"""

import argparse, json, os, sys, time, warnings
from typing import Any, Dict, List, Optional, Tuple
warnings.simplefilter("ignore")
import requests

ENV_SERVER = os.getenv("PEERTUBE_SERVER", "")
CONFIG_DIR = os.path.expanduser(os.getenv("PEERTUBE_CONFIG_DIR", "~/.config/peertube-cli"))
API_BASE = "/api/v1"

QUIET = False
GLOBAL_FLAGS: Dict[str, Any] = {"json": False, "dry_run": False, "quiet": False, "verbose": False}
def log(m):    global QUIET; (not QUIET and not GLOBAL_FLAGS.get("json")) and print(m)
def warn(m):   print(f"Warning: {m}", file=sys.stderr)
def die(m, c=1): print(f"Error: {m}", file=sys.stderr); sys.exit(c)
def emit(h, d):
    if GLOBAL_FLAGS.get("json"): print(json.dumps(d, default=str))
    else: print(h)

def _preparse(argv):
    BOOLS = {"--json","--dry-run","--quiet","--verbose"}
    f, fl = {}, [argv[0]]
    i = 1
    while i < len(argv):
        a = argv[i]
        if a in BOOLS: f[a.lstrip("-").replace("-","_")] = True; i += 1
        elif a in ("--help","-h"): return f, argv
        elif a == "--": fl.extend(argv[i:]); break
        else: fl.append(a); i += 1
    return f, fl

class PeerTubeClient:
    def __init__(self, server="", dry_run=False):
        self.server = (server or ENV_SERVER or "https://your-instance.example.com").rstrip("/")
        self.dry_run = dry_run
        self._token = None
        # Try to load saved token
        token_path = os.path.join(CONFIG_DIR, "token.json")
        if os.path.isfile(token_path):
            try:
                with open(token_path) as f: data = json.load(f)
                if data.get("expires_at", 0) > time.time():
                    self._token = data.get("access_token")
            except: pass

    def _oauth_login(self):
        """Fetch OAuth client creds and exchange for token."""
        try:
            r = requests.get(f"{self.server}{API_BASE}/oauth-clients/local", timeout=15)
            clients = r.json()
        except: die(f"Cannot reach {self.server}. Check PEERTUBE_SERVER.")
        return clients.get("client_id", ""), clients.get("client_secret", "")

    def login(self, username, password):
        """Login and persist OAuth token."""
        cid, csec = self._oauth_login()
        try:
            r = requests.post(f"{self.server}{API_BASE}/users/token", data={
                "client_id": cid, "client_secret": csec,
                "grant_type": "password", "username": username,
                "password": password,
                "response_type": "code"}, timeout=15)
        except ConnectionError as e: die(f"Cannot connect: {e}")
        if r.status_code >= 400: die(f"Login failed: {r.text[:200]}")
        data = r.json()
        self._token = data.get("access_token")
        # Persist token
        os.makedirs(CONFIG_DIR, exist_ok=True)
        with open(os.path.join(CONFIG_DIR, "token.json"), "w") as f:
            json.dump({"access_token": self._token, "expires_at": time.time() + data.get("expires_in", 86400)}, f)
        return data

    def _headers(self):
        h = {"Accept": "application/json"}
        if self._token: h["Authorization"] = f"Bearer {self._token}"
        return h

    def _get(self, path, params=None):
        url = f"{self.server}{API_BASE}{path}"
        if self.dry_run: return {"dry_run":True, "url":url, "params":params, "total":0, "data":[]}
        try:
            r = requests.get(url, params=params, headers=self._headers(), timeout=30)
        except ConnectionError as e: die(f"Cannot connect: {e}")
        if r.status_code == 401:
            die("Not authenticated. Run 'peertube auth login' first.")
        if r.status_code >= 400:
            try: d = r.json()
            except: d = r.text[:200]
            die(f"API error ({r.status_code}): {d}")
        return r.json()

    def get_server_info(self): return self._get("/server/")

    def list_videos(self, page=1, limit=12, sort="-publishedAt"):
        return self._get("/videos", {"page":page, "count":limit, "sort":sort})

    def search_videos(self, query, page=1, limit=12):
        return self._get("/search/videos", {"search":query, "page":page, "count":limit})

    def get_video(self, vid): return self._get(f"/videos/{vid}")

    def list_video_comments(self, vid, page=1, limit=10):
        return self._get(f"/videos/{vid}/comments", {"page":page, "count":limit})

    def list_channels(self, page=1, limit=15):
        return self._get("/video-channels", {"page":page, "count":limit})

    def get_channel(self, name): return self._get(f"/video-channels/{name}")

    def list_channel_videos(self, name, page=1, limit=12):
        return self._get(f"/video-channels/{name}/videos", {"page":page, "count":limit})

    def list_playlists(self, page=1, limit=10):
        return self._get("/video-playlists/user", {"page":page, "count":limit})

    def get_playlist(self, pid): return self._get(f"/video-playlists/{pid}")

    def my_profile(self): return self._get("/users/me")
    def my_videos(self, page=1, limit=12): return self._get("/users/me/videos", {"page":page, "count":limit})


def fmt_video(v, idx=None):
    prefix = f"{idx}. " if idx else ""
    name = v.get("name", "?")
    author = v.get("channel",{}).get("displayName", v.get("account",{}).get("displayName","?"))
    dur = v.get("duration", 0)
    dur_str = f"{int(dur//60)}:{int(dur%60):02d}"
    views = v.get("views", 0)
    pub = (v.get("publishedAt") or "")[:10]
    return f"  {prefix}{name:55}  {dur_str}  {views} views  {author}  {pub}"


def cmd_server(client, args):
    if client.dry_run: return emit("[dry-run] Get server info", {"dry_run":True})
    d = client.get_server_info() or {}
    name = d.get("instance",{}).get("name","?")
    desc = (d.get("instance",{}).get("shortDescription","") or "")[:80]
    users = d.get("users","?"); videos = d.get("videos","?"); views = d.get("views","?")
    emit(f"🖥️  {name}\n   {desc}\n   Users: {users}  Videos: {videos}  Views: {views}",
         {"instance":{"name":name,"shortDescription":desc},"users":users,"videos":videos})

def cmd_videos(client, args):
    p = argparse.ArgumentParser(prog="peertube videos")
    p.add_argument("--limit", type=int, default=12)
    parsed, _ = p.parse_known_args(args)
    if client.dry_run: return emit("[dry-run] List videos", {"dry_run":True})
    data = client.list_videos(limit=parsed.limit) or {}
    videos = data.get("data",[])
    if not videos: return emit("No videos.", {"videos":[]})
    lines = [fmt_video(v, i+1) for i, v in enumerate(videos)]
    total = data.get("total", len(videos))
    emit(f"{total} video(s):\n"+"\n".join(lines), {"total":total,"videos":videos})

def cmd_search(client, args):
    p = argparse.ArgumentParser(prog="peertube search")
    p.add_argument("--query", "-q", required=True)
    p.add_argument("--limit", type=int, default=12)
    parsed, _ = p.parse_known_args(args)
    if client.dry_run: return emit(f"[dry-run] Search: {parsed.query}", {"dry_run":True})
    data = client.search_videos(parsed.query, limit=parsed.limit) or {}
    videos = data.get("data",[])
    if not videos: return emit("No results.", {"videos":[]})
    lines = [fmt_video(v, i+1) for i, v in enumerate(videos)]
    total = data.get("total", len(videos))
    emit(f"{total} result(s):\n"+"\n".join(lines), {"total":total,"videos":videos})

def cmd_channels(client, args):
    if client.dry_run: return emit("[dry-run] List channels", {"dry_run":True})
    data = client.list_channels() or {}
    channels = data.get("data",[])
    if not channels: return emit("No channels.", {"channels":[]})
    lines, out = [], []
    for c in channels:
        dn = c.get("displayName","?"); name = c.get("name","?"); vid = c.get("videosCount",0); subs = c.get("subscribersCount",0)
        lines.append(f"  {dn:30} @{name}  {vid} videos  {subs} subscribers")
        out.append({"displayName":dn,"name":name,"videosCount":vid,"subscribersCount":subs})
    emit(f"{len(channels)} channel(s):\n"+"\n".join(lines), {"channels":out})

def cmd_me(client, args):
    if client.dry_run: return emit("[dry-run] Get profile", {"dry_run":True})
    d = client.my_profile() or {}
    uname = d.get("username","?"); role = d.get("role","?")
    vid = d.get("videosCount",0); views = d.get("viewsCount",0)
    emit(f"👤 @{uname}  Role: {role}  Videos: {vid}  Views: {views}",
         {"username":uname,"role":role,"videosCount":vid,"viewsCount":views})

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

    parser = argparse.ArgumentParser(prog="peertube", description="PeerTube federated video.",
        epilog="Set PEERTUBE_SERVER. Login: peertube auth login --username ... --password ...")
    sub = parser.add_subparsers(dest="command")

    # auth
    ap = sub.add_parser("auth", help="Authentication")
    asub = ap.add_subparsers(dest="auth_action")
    lp = asub.add_parser("login", help="Login to PeerTube instance")
    lp.add_argument("--username", required=True); lp.add_argument("--password", required=True)

    # other commands
    sub.add_parser("server", help="Server info")
    sub.add_parser("videos", help="List videos").add_argument("--limit",type=int,default=12)
    sub.add_parser("channels", help="List channels")
    sp = sub.add_parser("search", help="Search videos")
    sp.add_argument("--query","-q",required=True); sp.add_argument("--limit",type=int,default=12)
    sub.add_parser("me", help="My profile")

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

    client = PeerTubeClient(dry_run=GLOBAL_FLAGS.get("dry_run",False))
    remaining = filtered_argv[filtered_argv.index(args.command)+1:]

    if args.command == "auth":
        if args.auth_action == "login":
            result = client.login(args.username, args.password)
            emit(f"✅ Logged in to {client.server}", {"status":"logged_in","server":client.server})
        else: ap.print_help()
        return

    # Commands that need auth
    if not client._token and not GLOBAL_FLAGS.get("dry_run"):
        die("Not logged in. Run 'peertube auth login --username ... --password ...' first, or use --dry-run.")

    handlers = {"server":cmd_server,"videos":cmd_videos,"search":cmd_search,"channels":cmd_channels,"me":cmd_me}
    h = handlers.get(args.command)
    if not h: parser.print_help(); sys.exit(1)
    h(client, remaining)

if __name__ == "__main__": main()
