"""Editor projects: CRUD, autosave-draft expiry, self-contained bundles."""
import json
import os
import re
import threading
import time
import uuid

from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse

from clipshelf.config import ASSETS, PROJECTS, SOUNDS
from clipshelf.routers.editor_common import slugify

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


def _expire_draft():
    """The autosave draft dies 48h after its last edit; manual saves never expire."""
    f = PROJECTS / "draft.json"
    try:
        if f.exists() and time.time() - f.stat().st_mtime > 48 * 3600:
            f.unlink()
    except OSError:
        pass


def _expiry_loop():
    while True:
        _expire_draft()
        time.sleep(3600)


def start():
    threading.Thread(target=_expiry_loop, daemon=True).start()


@router.get("/api/editor/projects")
def list_projects():
    _expire_draft()
    out = []
    for f in PROJECTS.glob("*.json"):
        try:
            j = json.loads(f.read_text())
            out.append({
                "id": f.stem,
                "name": j.get("name", f.stem),
                "updated": j.get("updated", 0),
                "clips": len(j.get("clips", [])),
                "dur": j.get("dur", 0),
            })
        except Exception:
            continue
    out.sort(key=lambda p: -p["updated"])
    return {"projects": out}


@router.post("/api/editor/projects")
def save_project(body: dict):
    pid = body.get("id") or f"{slugify(body.get('name', 'untitled'))}-{uuid.uuid4().hex[:6]}"
    body["id"] = pid
    body["updated"] = time.time()
    (PROJECTS / f"{pid}.json").write_text(json.dumps(body))
    return {"id": pid}


@router.get("/api/editor/projects/{pid}")
def get_project(pid: str):
    f = PROJECTS / f"{slugify(pid)}.json"
    if not f.exists():
        raise HTTPException(404, "no such project")
    return json.loads(f.read_text())


@router.delete("/api/editor/projects/{pid}")
def delete_project(pid: str):
    f = PROJECTS / f"{slugify(pid)}.json"
    if f.exists():
        f.unlink()
    return {"ok": True}


@router.get("/api/editor/projects/{pid}/bundle")
def project_bundle(pid: str):
    """Self-contained backup: the project JSON plus every asset/sound it
    references, zipped. Media is already compressed — store, don't deflate."""
    import tempfile
    import zipfile
    from starlette.background import BackgroundTask
    f = PROJECTS / f"{slugify(pid)}.json"
    if not f.exists():
        raise HTTPException(404, "no such project")
    txt = f.read_text()
    tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
    with zipfile.ZipFile(tmp, "w", zipfile.ZIP_STORED) as z:
        z.writestr("project.json", txt)
        for kind, fname in set(re.findall(r"/api/editor/(assets|sounds)/([\w.\- %]+)", txt)):
            src = (ASSETS if kind == "assets" else SOUNDS) / fname
            if src.exists():
                z.write(src, f"{kind}/{fname}")
    tmp.close()
    return FileResponse(tmp.name, filename=f"{slugify(pid)}-bundle.zip",
                        media_type="application/zip",
                        background=BackgroundTask(os.unlink, tmp.name))
