"""Model management: list, download-on-server, select active tier.

Satisfies the requirement that models are selectable ON the server, kept
there, and downloaded there (never on the client). The client's Settings
picker reads GET /v1/models and can trigger a download or a switch.
"""
from __future__ import annotations

import asyncio

from fastapi import APIRouter, Depends, HTTPException
from loguru import logger

from ..auth import Principal
from ..deps import AuthDep, get_engine
from ..engine import EngineHost

router = APIRouter()

# Strong refs for fire-and-forget download tasks: asyncio keeps only weak
# references, so a bare create_task could be garbage-collected mid-download.
_bg_downloads: set[asyncio.Task] = set()


@router.get("/v1/models")
async def list_models(
    principal: Principal = AuthDep,
    engine: EngineHost = Depends(get_engine),
) -> dict:
    """OpenAI-compatible list shape enriched with server-side model state
    (downloaded / loaded / downloading)."""
    infos = engine.models_info()
    return {
        "object": "list",
        "data": [
            {
                "id": m["id"],
                "object": "model",
                "owned_by": "voxcast",
                "downloaded": m["downloaded"],
                "loaded": m["loaded"],
                "downloading": m["downloading"],
                "size_bytes": m["size_bytes"],
                "vram_mb": m["vram_mb"],
            }
            for m in infos
        ],
    }


@router.post("/v1/models/{tier}/download")
async def download_model(
    tier: str,
    principal: Principal = AuthDep,
    engine: EngineHost = Depends(get_engine),
) -> dict:
    """Download a tier onto the server. Returns immediately; progress is
    reflected in GET /v1/models (downloading flag) and server logs."""
    if tier not in EngineHost.ALL_TIERS:
        raise HTTPException(status_code=404, detail=f"Unknown model tier: {tier}")
    if engine.model_exists(tier):
        return {"tier": tier, "status": "already_present"}
    # Fire-and-forget on the event loop, with a strong ref (see _bg_downloads).
    task = asyncio.create_task(_safe_download(engine, tier))
    _bg_downloads.add(task)
    task.add_done_callback(_bg_downloads.discard)
    return {"tier": tier, "status": "downloading"}


@router.post("/v1/models/{tier}/select")
async def select_model(
    tier: str,
    principal: Principal = AuthDep,
    engine: EngineHost = Depends(get_engine),
) -> dict:
    """Make `tier` the active model (downloads first if missing, then loads).
    Blocks until the switch completes so the client gets a definitive result."""
    if tier not in EngineHost.ALL_TIERS:
        raise HTTPException(status_code=404, detail=f"Unknown model tier: {tier}")
    try:
        # Under the admission gate so the stop/reload can't tear down a model
        # out from under a concurrent in-flight transcription.
        async with engine.admit():
            await engine.switch_model(tier)
    except HTTPException:
        raise
    except Exception as e:  # noqa: BLE001
        logger.error("switch_model({}) failed: {}", tier, e)
        raise HTTPException(status_code=500, detail="Could not load the model")
    return {"tier": tier, "status": "active", "device": engine.status()["device"]}


async def _safe_download(engine: EngineHost, tier: str) -> None:
    try:
        await engine.download_model(tier)
    except Exception as e:  # noqa: BLE001
        logger.error("Background model download of {} failed: {}", tier, e)
