"""Phantomline footage library route.

Surfaces the curated stock footage manifest (static/library/footage-manifest.json)
to the studio UI so users can browse + pick clips that ship with the install
to use as the rolling visual layer in their videos.

The manifest itself is generated by `_compress_footage.py` (one-off content
prep tool). Files live in Supabase Storage at the URLs the manifest points to;
the desktop install lazily downloads + caches them in
`output/footage-cache/<clip_id>.mp4` on first use.

Endpoints:
  GET /api/library/footage              -> full manifest (cached client-side)
  GET /api/library/footage?aspect=9:16  -> filter by aspect ratio
  GET /api/library/footage?category=X   -> filter by category tag
  GET /api/library/footage/<clip_id>    -> single clip entry

This is read-only by design — the studio doesn't need to upload to the
library, just consume it. Library curation happens at content-prep time
(Kyle adds clips to Supabase + bumps manifest, ships in next release).
"""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import requests
from flask import Blueprint, Response, jsonify, request

from core import BASE_DIR


library_bp = Blueprint("library", __name__)

MANIFEST_PATH = Path(BASE_DIR) / "static" / "library" / "footage-manifest.json"
CACHE_DIR = Path(BASE_DIR) / "output" / "footage-cache"


def _load_manifest() -> dict[str, Any]:
    """Read the footage manifest from disk on every request. The file is
    small (~3 KB for 19 clips) and rarely changes, so the disk hit is
    negligible — and we get fresh data without a server restart when the
    manifest is updated by `_compress_footage.py` or hand-edited."""
    if not MANIFEST_PATH.exists():
        return {"version": 1, "clip_count": 0, "clips": []}
    try:
        return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        # Don't 500 on a malformed manifest — return an empty library so the
        # picker UI shows "No footage available" instead of crashing.
        return {"version": 1, "clip_count": 0, "clips": [], "error": str(exc)}


@library_bp.route("/api/library/footage", methods=["GET"])
def api_library_footage():
    """Return the footage manifest, optionally filtered by aspect or category.

    Filters are AND-combined so ?aspect=9:16&category=horror returns clips
    tagged horror at 9:16 only. Filters are applied case-insensitively for
    forgiveness (categories are user-edited so "Horror" and "horror" should
    both match a query for ?category=horror)."""
    manifest = _load_manifest()
    clips = manifest.get("clips") or []

    aspect = (request.args.get("aspect") or "").strip().lower()
    category = (request.args.get("category") or "").strip().lower()

    if aspect:
        clips = [c for c in clips if (c.get("aspect_ratio") or "").lower() == aspect]
    if category:
        clips = [
            c for c in clips
            if any(category == (cat or "").strip().lower() for cat in (c.get("categories") or []))
        ]

    return jsonify({
        "ok": True,
        "version": manifest.get("version", 1),
        "clip_count": len(clips),
        "clips": clips,
    })


@library_bp.route("/api/library/footage/<clip_id>", methods=["GET"])
def api_library_footage_one(clip_id: str):
    """Return a single clip by id (e.g. 'demo-02'). Useful when the studio
    has a saved-project reference and needs to re-resolve metadata at render
    time without fetching the full manifest."""
    manifest = _load_manifest()
    for c in manifest.get("clips") or []:
        if c.get("id") == clip_id:
            return jsonify({"ok": True, "clip": c})
    return jsonify({"ok": False, "error": "Clip not found"}), 404


def find_clip(clip_id: str) -> dict[str, Any] | None:
    """Look up a clip by id in the manifest. Public so server.py can use it
    when bridging library picks into the upload/registration pipeline."""
    for c in _load_manifest().get("clips") or []:
        if c.get("id") == clip_id:
            return c
    return None


def cache_clip(clip_id: str) -> tuple[Path | None, bool, str | None]:
    """Ensure the clip is cached locally at output/footage-cache/<id>.mp4.

    Returns (path, cached, error). `cached=True` means the file already
    existed at the expected size. `error` is None on success.
    Idempotent — safe to call multiple times for the same clip_id."""
    clip = find_clip(clip_id)
    if not clip:
        return None, False, "Clip not found"

    url = clip.get("url")
    expected_size = int(clip.get("size_bytes") or 0)
    if not url:
        return None, False, "Clip has no URL"

    CACHE_DIR.mkdir(parents=True, exist_ok=True)
    cached = CACHE_DIR / f"{clip_id}.mp4"

    if cached.exists() and (expected_size == 0 or cached.stat().st_size == expected_size):
        return cached, True, None

    try:
        with requests.get(url, stream=True, timeout=60) as r:
            r.raise_for_status()
            tmp = cached.with_suffix(".mp4.part")
            with open(tmp, "wb") as f:
                for chunk in r.iter_content(chunk_size=1024 * 256):
                    if chunk:
                        f.write(chunk)
            tmp.replace(cached)
    except (requests.RequestException, OSError) as exc:
        return None, False, f"Download failed: {exc}"

    return cached, False, None


@library_bp.route("/api/library/footage/<clip_id>/stream", methods=["GET"])
def api_library_footage_stream(clip_id: str):
    """Proxy-stream a library clip from Supabase so the browser can fetch it
    without CORS issues. Used by the cloud/browser render pipeline."""
    clip = find_clip(clip_id)
    if not clip:
        return jsonify({"ok": False, "error": "Clip not found"}), 404
    url = clip.get("url")
    if not url:
        return jsonify({"ok": False, "error": "Clip has no URL"}), 422
    try:
        r = requests.get(url, stream=True, timeout=120)
        r.raise_for_status()
        return Response(
            r.iter_content(chunk_size=256 * 1024),
            content_type=r.headers.get("Content-Type", "video/mp4"),
            headers={"Content-Length": r.headers.get("Content-Length", "")},
        )
    except requests.RequestException as exc:
        return jsonify({"ok": False, "error": str(exc)}), 502


@library_bp.route("/api/library/footage/<clip_id>/download", methods=["POST"])
def api_library_footage_download(clip_id: str):
    """Cache a library clip locally and return its server-side path.
    Used as a primitive by the studio's footage-picker flow."""
    path, cached, error = cache_clip(clip_id)
    if error:
        status = 404 if error == "Clip not found" else (422 if "no URL" in error else 502)
        return jsonify({"ok": False, "error": error}), status
    return jsonify({
        "ok": True,
        "cached": cached,
        "local_path": str(path.relative_to(BASE_DIR)).replace("\\", "/"),
        "size_bytes": path.stat().st_size,
    })
