"""The clips archive: everything you've cut or exported, in one place.

Saved clips (timeline cuts, hotkey marks, replay-buffer saves) live in the
highlights tree; finished editor exports live in My Edits. The editor could
already browse both, but only from inside the editor — which is the wrong
place to go when you just want last night's clip and its share link.

A share index (src -> {url, via}) is kept so a clip that has been shared
shows its link immediately instead of re-uploading to find out.
"""
import json
import os
import re
import threading
import time
import urllib.parse
from pathlib import Path

from fastapi import APIRouter, HTTPException, Request

from clipshelf.config import DATA, EXPORTS, HIGHLIGHTS_DIR, RENDERS

router = APIRouter(tags=["clips"])

SHARE_INDEX = DATA / "share_index.json"
_lock = threading.Lock()

_CLIP_RE = re.compile(r"^(?:(\d+)k|c)_(\d{2}-\d{2}-\d{2})(?:_([\w-]{1,40}))?\.mp4$")
_NIGHT_RE = re.compile(r"^(.+)_(\d{4}-\d{2}-\d{2})$")


# ------------------------------------------------------------ share index
def share_index() -> dict:
    try:
        return json.loads(SHARE_INDEX.read_text())
    except Exception:
        return {}


def record_share(src: str, url: str, via: str, slug: str = ""):
    with _lock:
        idx = share_index()
        idx[src] = {"url": url, "via": via, "slug": slug, "ts": int(time.time())}
        tmp = SHARE_INDEX.with_suffix(".json.tmp")
        tmp.write_text(json.dumps(idx))
        tmp.replace(SHARE_INDEX)


def forget_share(src: str):
    with _lock:
        idx = share_index()
        if idx.pop(src, None) is not None:
            tmp = SHARE_INDEX.with_suffix(".json.tmp")
            tmp.write_text(json.dumps(idx))
            tmp.replace(SHARE_INDEX)


# ------------------------------------------------------------ listing
def _entry(path: Path, src: str, kind: str, game: str = "", name: str = "",
           idx: dict = None) -> dict:
    st = path.stat()
    shared = (idx or {}).get(src)
    return {
        "src": src,
        "kind": kind,                       # "clip" | "edit"
        "game": game,
        "name": name or path.stem,
        "file": path.name,
        "size": st.st_size,
        "ts": int(st.st_mtime),
        "thumb": "/api/thumb?src=" + urllib.parse.quote(src),
        "share": shared,
    }


@router.get("/api/clips/saved")
def clips_saved(limit: int = 400, game: str = "", q: str = ""):
    """Every saved clip and export, newest first."""
    from clipshelf import game_detect
    idx = share_index()
    out = []
    hl = Path(HIGHLIGHTS_DIR)
    if hl.is_dir():
        for gdir in hl.iterdir():
            if not gdir.is_dir() or (game and gdir.name != game):
                continue
            for night in gdir.iterdir():
                if not night.is_dir() or night.name == "reels":
                    continue
                nm = _NIGHT_RE.match(night.name)
                for f in night.iterdir():
                    if f.name.endswith("_proxy.mp4") or not f.is_file():
                        continue
                    m = _CLIP_RE.match(f.name)
                    if not m:
                        continue
                    rel = f"{gdir.name}/{night.name}/{f.name}"
                    src = "/api/editor/highlights/file/" + rel
                    label = (m.group(3) or "").replace("-", " ")
                    kills = m.group(1)
                    name = label or (f"{kills}-kill clip" if kills else "clip")
                    e = _entry(f, src, "clip", gdir.name, name, idx)
                    e.update(rel=rel, map=(nm.group(1) if nm else ""),
                             date=(nm.group(2) if nm else ""),
                             game_label=game_detect.display_name(gdir.name),
                             kills=int(kills) if kills else 0)
                    out.append(e)
    if not game:
        for f in Path(EXPORTS).iterdir() if Path(EXPORTS).is_dir() else []:
            if not f.is_file() or f.suffix.lower() != ".mp4":
                continue
            src = "/clips/" + f.name
            name = f.stem.split("_CUT ")[0].replace("_", " ")
            e = _entry(f, src, "edit", "", name, idx)
            e.update(rel=f.name, game_label="my edits")
            out.append(e)
    if q:
        ql = q.lower()
        out = [e for e in out if ql in e["name"].lower()
               or ql in e.get("game_label", "").lower()]
    out.sort(key=lambda e: -e["ts"])
    return {"status": "success", "clips": out[:max(1, min(1000, limit))],
            "games": sorted({e["game"] for e in out if e["game"]})}


_ROOTS = (("/api/editor/highlights/file/", HIGHLIGHTS_DIR),
          ("/clips/", EXPORTS),
          ("/api/editor/renders/", RENDERS))     # a fresh export, before it is filed


def _resolve(src: str) -> Path:
    """A clip src -> a real file, refusing anything outside the known roots."""
    from urllib.parse import unquote
    src = unquote(src)
    for prefix, base in _ROOTS:
        if src.startswith(prefix):
            root = Path(base)
            p = root / src[len(prefix):]
            break
    else:
        raise HTTPException(400, "not a clip url")
    p = p.resolve()
    if not str(p).startswith(str(root.resolve())) or not p.is_file():
        raise HTTPException(404, "no such clip")
    return p


@router.delete("/api/clips/saved")
def clips_delete(src: str):
    """Delete a clip and its sidecars. The share (if any) is NOT deleted —
    that lives on a server and has its own delete."""
    p = _resolve(src)
    for extra in (p.with_name(p.stem + "_proxy.mp4"),
                  p.with_name(p.stem + "_thumb.jpg")):
        try:
            extra.unlink(missing_ok=True)
        except OSError:
            pass
    p.unlink(missing_ok=True)
    forget_share(src)
    # tidy an emptied night folder so the archive doesn't fill with husks
    try:
        parent = p.parent
        if parent != Path(HIGHLIGHTS_DIR) and not any(parent.iterdir()):
            parent.rmdir()
    except OSError:
        pass
    return {"status": "success"}


@router.post("/api/clips/save_as")
async def clips_save_as(request: Request):
    """Save a clip somewhere the user picks.

    In the app window an <a download> does nothing — the WebView blocks
    page-initiated downloads — so the button asks the app to run a native
    save dialog and copy the file itself. In a browser this route is not
    used; the link works there."""
    from clipshelf import desktop
    body = await request.json()
    p = _resolve(str(body.get("src") or ""))
    name = str(body.get("name") or "").strip()
    if name:
        name = re.sub(r"[^\w .-]+", "", name)[:80]
        if not name.lower().endswith(".mp4"):
            name += ".mp4"
    dest = desktop.save_file_as(str(p), name or p.name)
    if dest is None:
        raise HTTPException(400, "no app window — use the download link")
    if dest == "":
        return {"status": "cancelled"}
    return {"status": "success", "path": dest}


@router.post("/api/clips/reveal")
async def clips_reveal(request: Request):
    """Open the file manager at a clip — the desktop answer to "where did
    that go?"."""
    from clipshelf import desktop
    body = await request.json()
    target = str(body.get("path") or "")
    p = Path(target) if target else _resolve(str(body.get("src") or ""))
    if not p.exists():
        raise HTTPException(404, "no such file")
    return {"status": "success" if desktop.reveal(p) else "error"}


@router.post("/api/clips/rename")
async def clips_rename(request: Request):
    body = await request.json()
    p = _resolve(str(body.get("src") or ""))
    name = re.sub(r"[^\w -]+", "", str(body.get("name") or "")).strip()[:40]
    if not name:
        raise HTTPException(400, "need a name")
    m = _CLIP_RE.match(p.name)
    if m:
        prefix = f"{m.group(1)}k" if m.group(1) else "c"
        new = p.with_name(f"{prefix}_{m.group(2)}_{name.replace(' ', '-')}.mp4")
    else:
        new = p.with_name(f"{name.replace(' ', '_')}_CUT {time.strftime('%Y-%m-%d %H-%M-%S')}.mp4")
    if new.exists():
        raise HTTPException(409, "a clip with that name already exists")
    old_src = str(body.get("src"))
    p.rename(new)
    proxy = p.with_name(p.stem + "_proxy.mp4")
    if proxy.exists():
        proxy.rename(new.with_name(new.stem + "_proxy.mp4"))
    idx = share_index()
    if old_src in idx:                      # keep the share link attached
        rel_root = ("/api/editor/highlights/file/"
                    if old_src.startswith("/api/editor/highlights/file/") else "/clips/")
        base = Path(HIGHLIGHTS_DIR) if rel_root != "/clips/" else Path(EXPORTS)
        newsrc = rel_root + str(new.relative_to(base)).replace(os.sep, "/")
        s = idx[old_src]
        record_share(newsrc, s["url"], s["via"], s.get("slug", ""))
        forget_share(old_src)
    return {"status": "success", "file": new.name}
