"""POST /v1/audio/transcriptions — OpenAI-compatible synchronous endpoint.

For dictation-length audio. Long media (> max_sync_audio_s) is rejected with a
413 pointing at the job API, which does not hold the request open for minutes.
"""
from __future__ import annotations

from fastapi import APIRouter, Depends, Form, HTTPException, UploadFile
from fastapi.responses import Response
from loguru import logger

from ..auth import Principal
from ..deps import AuthDep, get_config, get_engine
from ..engine import EngineHost
from ..formats import VALID_FORMATS, render
from ..upload import probe_duration_s, spool_upload, _safe_unlink

router = APIRouter()


def _requested_tier(requested: str) -> str | None:
    """The explicit, known tier to switch to, or None to keep the loaded model
    (whisper-1 / empty / unknown alias → OpenAI-client compat)."""
    req = (requested or "").strip()
    if not req or req == "whisper-1" or req not in EngineHost.ALL_TIERS:
        return None
    return req


def _validate_requested_model(engine: EngineHost, requested: str) -> None:
    """Reject an explicit tier that is not downloaded on the server, BEFORE the
    upload is spooled (no wasted transfer)."""
    tier = _requested_tier(requested)
    if tier and not engine.model_exists(tier):
        raise HTTPException(
            status_code=400,
            detail=f"Model '{tier}' is not downloaded on the server. "
                   f"Download it first via POST /v1/models/{tier}/download.",
        )


async def _resolve_model(engine: EngineHost, requested: str) -> None:
    """Switch to the requested tier if one was explicitly named. Call INSIDE
    the admission gate so it cannot interleave with a concurrent transcription."""
    tier = _requested_tier(requested)
    if tier:
        await engine.switch_model(tier)


@router.post("/v1/audio/transcriptions")
async def transcribe(
    file: UploadFile,
    model: str = Form("whisper-1"),
    language: str = Form(""),
    response_format: str = Form("json"),
    temperature: float = Form(0.0),
    principal: Principal = AuthDep,
    engine: EngineHost = Depends(get_engine),
    config=Depends(get_config),
) -> Response:
    if response_format not in VALID_FORMATS:
        raise HTTPException(
            status_code=400,
            detail=f"Unsupported response_format '{response_format}'",
        )
    if not engine.is_ready:
        raise HTTPException(status_code=503, detail="Engine not ready",
                            headers={"Retry-After": "5"})

    # Reject an unknown/not-downloaded explicit tier early (before the upload).
    _validate_requested_model(engine, model)

    from paths import data_dir  # shared module from src/ (on sys.path)
    tmp = await spool_upload(file, config.max_upload_mb * 1024 * 1024, data_dir())
    try:
        # Demuxing attacker-controlled media can hang/slow; never on the event
        # loop and never unbounded — off-thread with a hard timeout.
        import asyncio
        loop = asyncio.get_running_loop()
        try:
            duration = await asyncio.wait_for(
                loop.run_in_executor(None, probe_duration_s, tmp), timeout=20.0)
        except asyncio.TimeoutError:
            raise HTTPException(status_code=400, detail="Could not read audio file")
        if duration is not None and duration > config.max_sync_audio_s:
            raise HTTPException(
                status_code=413,
                detail=(f"Audio is {int(duration)}s; the synchronous endpoint is "
                        f"capped at {config.max_sync_audio_s}s. Use POST /v1/jobs "
                        f"for long files."),
            )
        lang = language.strip() or None
        want_ts = response_format in ("verbose_json", "srt", "vtt")
        # Switch (if an explicit tier was requested) and transcribe INSIDE the
        # admission gate so no concurrent request can swap the model between the
        # switch and the transcription — the pair is atomic per request.
        async with engine.admit():
            await _resolve_model(engine, model)
            text, meta = await engine.service.transcribe_file(
                tmp,
                language=lang,
                with_timestamps=want_ts,
                multilingual=lang is None,
            )
        if not text.strip() and not meta:
            raise HTTPException(status_code=500,
                                detail="Transcription failed on the server")
        engine.usage.record(principal, meta.get("duration") or 0.0,
                            engine.status()["model"], "sync")
        body, media_type = render(text, meta, response_format)
        return Response(content=body, media_type=media_type)
    finally:
        _safe_unlink(tmp)
