#!/usr/bin/env python3
"""redok — personal file share CLI for redok.xyz.

Subcommands:
  up <file>...   upload one or more files, print URL per line
  ls             list files (id, name, size, age, url)
  rm <id>...     delete by id
  open <id>      open the preview page in the default browser

Auth token loaded from ~/.claude/skills/redok/.token (chmod 600).
"""
from __future__ import annotations
import json
import mimetypes
import os
import secrets
import subprocess
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

BASE = os.environ.get("REDOK_BASE", "https://redok.xyz")
TOKEN_FILE = Path(__file__).resolve().parent / ".token"


def die(msg: str, code: int = 1) -> None:
    print(f"redok: {msg}", file=sys.stderr)
    sys.exit(code)


def load_token() -> str:
    tok = os.environ.get("REDOK_TOKEN")
    if tok:
        return tok.strip()
    if not TOKEN_FILE.exists():
        die(f"no token at {TOKEN_FILE} (and REDOK_TOKEN env not set)")
    return TOKEN_FILE.read_text().strip()


UA = "redok-cli/1.0 (+https://redok.xyz)"

def auth_headers() -> dict[str, str]:
    return {"Authorization": f"Bearer {load_token()}", "User-Agent": UA}


def fmt_size(n: int) -> str:
    units = ["B", "KB", "MB", "GB", "TB"]
    i = 0
    f = float(n)
    while f >= 1024 and i < len(units) - 1:
        f /= 1024
        i += 1
    return f"{f:.1f} {units[i]}" if i else f"{int(f)} {units[i]}"


def fmt_age(ms: int) -> str:
    s = max(0, int((time.time() * 1000 - ms) / 1000))
    if s < 60: return "now"
    if s < 3600: return f"{s // 60}m"
    if s < 86400: return f"{s // 3600}h"
    return f"{s // 86400}d"


def encode_multipart(fields: dict[str, str], files: list[tuple[str, str, bytes, str]]) -> tuple[bytes, str]:
    """fields = {name: value}; files = [(field_name, filename, content, content_type), ...]"""
    boundary = "----redok-" + secrets.token_hex(16)
    body = bytearray()
    for k, v in fields.items():
        body += f"--{boundary}\r\n".encode()
        body += f'Content-Disposition: form-data; name="{k}"\r\n\r\n'.encode()
        body += v.encode("utf-8") + b"\r\n"
    for field, fname, data, ctype in files:
        safe = fname.replace('"', "")
        body += f"--{boundary}\r\n".encode()
        body += f'Content-Disposition: form-data; name="{field}"; filename="{safe}"\r\n'.encode()
        body += f"Content-Type: {ctype}\r\n\r\n".encode()
        body += data + b"\r\n"
    body += f"--{boundary}--\r\n".encode()
    return bytes(body), f"multipart/form-data; boundary={boundary}"


def request(method: str, path: str, *, body: bytes | None = None, ctype: str | None = None,
            timeout: int = 600) -> dict:
    headers = auth_headers()
    if ctype:
        headers["Content-Type"] = ctype
    url = BASE + path
    req = urllib.request.Request(url, data=body, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            data = r.read()
    except urllib.error.HTTPError as e:
        try:
            err = json.loads(e.read())
        except Exception:
            err = {"error": e.reason}
        if e.code == 401:
            die("auth error — token rejected. update ~/.claude/skills/redok/.token")
        die(f"{method} {path} → {e.code} {err.get('error', e.reason)}")
    except urllib.error.URLError as e:
        die(f"network error: {e.reason}")
    if not data:
        return {}
    return json.loads(data)


def cmd_up(args: list[str]) -> None:
    if not args:
        die("usage: redok up <file>...")
    paths = [Path(a).expanduser() for a in args]
    for p in paths:
        if not p.exists():
            die(f"not found: {p}")
        if not p.is_file():
            die(f"not a file: {p}")
    for p in paths:
        ctype = mimetypes.guess_type(p.name)[0] or "application/octet-stream"
        body, content_type = encode_multipart({}, [("file", p.name, p.read_bytes(), ctype)])
        print(f"↑ {p.name} ({fmt_size(p.stat().st_size)})…", file=sys.stderr, flush=True)
        r = request("POST", "/api/upload", body=body, ctype=content_type)
        print(f"{BASE}{r['url']}")


def cmd_ls(_args: list[str]) -> None:
    items = request("GET", "/api/files")
    if not items:
        print("no files", file=sys.stderr)
        return
    # column widths
    id_w = max(2, max(len(f["id"]) for f in items))
    name_w = min(40, max(4, max(len(f["filename"]) for f in items)))
    print(f"{'ID':<{id_w}}  {'NAME':<{name_w}}  {'SIZE':>9}  AGE   URL", file=sys.stderr)
    for f in items:
        name = f["filename"]
        if len(name) > name_w:
            name = name[: name_w - 1] + "…"
        print(f"{f['id']:<{id_w}}  {name:<{name_w}}  {fmt_size(f['size']):>9}  "
              f"{fmt_age(f['uploaded_at']):<4}  {BASE}/f/{f['id']}")


def cmd_rm(args: list[str]) -> None:
    if not args:
        die("usage: redok rm <id>...")
    for fid in args:
        request("DELETE", f"/api/files/{fid}")
        print(f"× {fid}")


def cmd_open(args: list[str]) -> None:
    if not args:
        die("usage: redok open <id>")
    url = f"{BASE}/f/{args[0]}"
    if sys.platform == "darwin":
        subprocess.run(["open", url], check=False)
    elif sys.platform.startswith("linux"):
        subprocess.run(["xdg-open", url], check=False)
    else:
        print(url)


COMMANDS = {"up": cmd_up, "ls": cmd_ls, "rm": cmd_rm, "open": cmd_open}


def main(argv: list[str]) -> None:
    if len(argv) < 2 or argv[1] in {"-h", "--help", "help"}:
        print(__doc__, file=sys.stderr)
        sys.exit(0 if len(argv) >= 2 else 2)
    cmd = argv[1]
    if cmd not in COMMANDS:
        die(f"unknown command: {cmd} (use up | ls | rm | open)")
    COMMANDS[cmd](argv[2:])


if __name__ == "__main__":
    main(sys.argv)
