"""Local Docker container management and URL probe endpoints."""
from __future__ import annotations

import os
from pathlib import Path
from urllib.parse import quote

import requests
from fastapi import APIRouter, HTTPException

from core.docker_client import _docker_get_json, _docker_post
from core.validation import _normalize_service_url

router = APIRouter()

_LOCAL_CONTAINER_DEFS: list[dict] = [
    {"name": "litellm",                       "label": "LiteLLM Gateway",            "role": "llm", "port": 14000, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "ghcr.io/berriai/litellm:main-latest", "repo": "https://github.com/BerriAI/litellm", "description": "OpenAI-compatible LLM gateway. Use for character analysis, casting, and text refinement."},
    {"name": "llama.cpp",                     "label": "llama.cpp Server",           "role": "llm", "port": 19000, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "ghcr.io/ggml-org/llama.cpp:server", "repo": "https://github.com/ggml-org/llama.cpp", "description": "OpenAI-compatible local LLM server when launched with its server API."},
    {"name": "ollama",                        "label": "Ollama",                     "role": "llm", "port": 11434, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "ollama/ollama", "repo": "https://ollama.com", "description": "Local LLM runtime. The app uses its OpenAI-compatible /v1 endpoint."},
    {"name": "llama-swap",                    "label": "llama-swap",                 "role": "llm", "port": 28080, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "ghcr.io/mostlygeek/llama-swap:cuda", "repo": "https://github.com/mostlygeek/llama-swap", "description": "Model swapping proxy for local LLMs. Useful as the default Rehearser LLM endpoint."},
    {"name": "llama-qwen35-4b",               "label": "Qwen3.5 4B LLM",             "role": "llm", "port": 19001, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "local", "repo": "", "description": "Dedicated Qwen LLM container. Use if it exposes an OpenAI-compatible /v1 API."},
    {"name": "vllm",                          "label": "vLLM",                       "role": "llm", "port": 8000,  "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "vllm/vllm-openai:latest", "repo": "https://github.com/vllm-project/vllm", "description": "High-throughput GPU inference. Serve any HuggingFace model with PagedAttention."},
    {"name": "lmstudio",                      "label": "LM Studio",                  "role": "llm", "port": 1234,  "stack": "", "image": "local", "repo": "https://lmstudio.ai", "description": "GUI app for running quantized models. Built-in model browser, chat UI, and a local server with OpenAI API."},
    {"name": "tts-faster-qwen3-tts-voiceclone",   "label": "Qwen3 TTS · Voice Clone",     "role": "tts", "port": 8020, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "WAV voice cloning. Scans active_voices at startup; restart after adding or editing voices."},
    {"name": "tts-faster-qwen3-tts-voicedesign",  "label": "Qwen3 TTS · Voice Design",    "role": "tts", "port": 8021, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "Instruction-based voice design. Best Qwen path for tone, style, and character prompts."},
    {"name": "tts-faster-qwen3-tts-customvoice",  "label": "Qwen3 TTS · Custom Voice",    "role": "tts", "port": 8022, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "CustomVoice speakers with per-request instruct overrides for tone/style."},
    {"name": "tts-faster-qwen3-tts-streaming",    "label": "Qwen3 TTS · Streaming",       "role": "tts", "port": 8023, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:streaming", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "Low-latency progressive WAV streaming for voice clone voices."},
    {"name": "faster-whisper",                "label": "faster-whisper",             "role": "stt", "port": 8010, "stack": "faster-whisper-dgx-spark", "image": "local", "repo": "https://github.com/SYSTRAN/faster-whisper", "description": "Fast local Whisper transcription endpoint."},
    {"name": "whisper-cpp",                   "label": "whisper.cpp",                "role": "stt", "port": 8085, "stack": "", "image": "local", "repo": "https://github.com/ggml-org/whisper.cpp", "description": "Minimal C++ Whisper with a built-in HTTP server. CPU or Metal/CUDA. Low memory, fast on consumer hardware."},
    {"name": "fish-speech-api",               "label": "Fish-Speech API",            "role": "tts", "port": 38080, "stack": "fish-speech-dgx-spark", "image": "local", "repo": "https://github.com/fishaudio/fish-speech", "description": "Tone-aware cloning backend. Recommended for Rehearser character voices when you need both identity and emotion."},
    {"name": "nemotron-asr-streaming",        "label": "Nemotron ASR Streaming",      "role": "stt", "port": 8006, "stack": "nemotron-asr-streaming", "image": "local", "repo": "https://github.com/pipecat-ai/nemotron-january-2026", "description": "Streaming ASR service. Use as a custom STT endpoint if it exposes OpenAI transcription or /transcribe."},
    {"name": "parakeet-stt-multi-9011",       "label": "NVIDIA Parakeet STT",         "role": "stt", "port": 8007, "stack": "nvidia-parakeet-voice-recognition", "image": "local", "repo": "https://github.com/mARTin-B78/dgx-spark-parakeet-asr", "description": "Parakeet STT container on port 8007."},
    {"name": "nvidia-magpie-tts",             "label": "NVIDIA Magpie TTS",           "role": "tts", "port": 8091, "stack": "nvidia-speech-gateway", "image": "nvcr.io/nim/nvidia/magpie-tts-multilingual:latest", "repo": "https://github.com/mARTin-B78/dgx-spark-parakeet-asr", "description": "NVIDIA neural TTS. Fixed speaker voices, fast multilingual synthesis."},
    {"name": "nvidia-parakeet-asr",           "label": "NVIDIA Parakeet Router ASR",  "role": "stt", "port": 8090, "stack": "nvidia-speech-gateway", "image": "parakeet-tdt-v3-spark:latest", "repo": "https://github.com/mARTin-B78/dgx-spark-parakeet-asr", "description": "NVIDIA Parakeet speech router endpoint."},
    {"name": "nvidia-parakeet-rnnt-nim",      "label": "NVIDIA Parakeet RNNT NIM",    "role": "stt", "port": 8092, "stack": "nvidia-speech-gateway", "image": "nvcr.io/nim/nvidia/parakeet-1b-rnnt-multilingual:latest", "repo": "https://github.com/mARTin-B78/dgx-spark-parakeet-asr", "description": "Parakeet RNNT NIM multilingual ASR on port 8092."},
    {"name": "kokoro-tts",                    "label": "Kokoro FastAPI",              "role": "tts", "port": 8880, "stack": "kokoro-tts", "image": "local", "repo": "", "description": "OpenAI-compatible TTS. Built-in voices: af_bella, bf_emma, am_adam... ~300 MB RAM."},
    {"name": "vibevoice-msft-tts",            "label": "VibeVoice TTS",               "role": "tts", "port": 8027, "stack": "vibevoice", "image": "local", "repo": "", "description": "VibeVoice REST service. Simple text-to-speech backend."},
    {"name": "nvidia-zeroshot-nim",           "label": "NVIDIA Zeroshot NIM",         "role": "tts", "port": 8093, "stack": "nvidia-speech-gateway", "image": "local", "repo": "", "description": "Magpie Zeroshot clone endpoint with audio_prompt."},
    {"name": "nvidia-flow-nim",               "label": "NVIDIA Flow NIM",             "role": "tts", "port": 8094, "stack": "nvidia-speech-gateway", "image": "local", "repo": "", "description": "Magpie Flow clone endpoint with audio_prompt and transcript."},
    {"name": "voicebox",                      "label": "Voicebox",                   "role": "llm", "port": 17493, "stack": "voicebox", "image": "local", "repo": "", "description": "Companion voice app/control plane. Not used as a normal TTS engine unless wrapped as an API."},
    {"name": "whisperx-blackwell",            "label": "WhisperX Blackwell",          "role": "stt", "port": 8003, "stack": "whisperx-blackwell", "image": "local", "repo": "https://github.com/m-bain/whisperX", "description": "WhisperX transcription/alignment service. Use as custom STT if its API is OpenAI-compatible or /transcribe."},
    {"name": "xtts-dgx-spark",                "label": "XTTS v2",                    "role": "tts", "port": 8005, "stack": "xtts_v2", "image": "local", "repo": "https://github.com/daswer123/xtts-api-server", "description": "XTTS v2 OpenAI-compatible speaker endpoint."},
]


def _container_status(name: str) -> dict:
    try:
        code, data = _docker_get_json(f"/containers/{quote(name, safe='')}/json")
        if code == 404:
            return {"status": "not_found"}
        if code == 200 and isinstance(data, dict):
            state = data.get("State", {})
            return {
                "status": state.get("Status", "unknown"),
                "running": state.get("Running", False),
                "paused": state.get("Paused", False),
                "started_at": state.get("StartedAt", ""),
                "image": data.get("Config", {}).get("Image", ""),
            }
        return {"status": "error", "detail": f"HTTP {code}"}
    except Exception as e:
        return {"status": "error", "detail": str(e)}


@router.get("/api/local-containers")
async def get_local_containers():
    sock_ok = Path(os.environ.get("DOCKER_SOCKET", "/var/run/docker.sock")).exists()
    results = []
    for defn in _LOCAL_CONTAINER_DEFS:
        entry = {k: v for k, v in defn.items()}
        if sock_ok:
            entry.update(_container_status(defn["name"]))
        else:
            entry["status"] = "no_socket"
        results.append(entry)
    return {"containers": results, "socket_available": sock_ok}


@router.post("/api/local-containers/{name}/start")
async def start_local_container(name: str):
    if not any(c["name"] == name for c in _LOCAL_CONTAINER_DEFS):
        raise HTTPException(404, f"Unknown container: {name}")
    try:
        code, _ = _docker_post(f"/containers/{quote(name, safe='')}/start")
    except Exception as e:
        raise HTTPException(502, f"Docker start failed: {e}")
    if code not in (204, 304):
        raise HTTPException(502, f"Docker API returned HTTP {code}")
    return {"ok": True, "name": name, **_container_status(name)}


@router.post("/api/local-containers/{name}/stop")
async def stop_local_container(name: str):
    if not any(c["name"] == name for c in _LOCAL_CONTAINER_DEFS):
        raise HTTPException(404, f"Unknown container: {name}")
    try:
        code, _ = _docker_post(f"/containers/{quote(name, safe='')}/stop?t=10")
    except Exception as e:
        raise HTTPException(502, f"Docker stop failed: {e}")
    if code not in (204, 304):
        raise HTTPException(502, f"Docker API returned HTTP {code}")
    return {"ok": True, "name": name, **_container_status(name)}


@router.post("/api/local-containers/{name}/restart")
async def restart_local_container(name: str):
    if not any(c["name"] == name for c in _LOCAL_CONTAINER_DEFS):
        raise HTTPException(404, f"Unknown container: {name}")
    try:
        code, _ = _docker_post(f"/containers/{quote(name, safe='')}/restart?t=10")
    except Exception as e:
        raise HTTPException(502, f"Docker restart failed: {e}")
    if code not in (204, 304):
        raise HTTPException(502, f"Docker API returned HTTP {code}")
    return {"ok": True, "name": name, **_container_status(name)}


@router.get("/api/probe-url")
async def probe_url(url: str, type: str = "", api_key: str = ""):
    """Server-side API probe — checks service-specific endpoints and validates JSON responses."""
    base = _normalize_service_url(url).rstrip("/")
    for _suffix in ("/v1", "/api/v1"):
        if base.endswith(_suffix):
            base = base[: -len(_suffix)]
            break
    key_to_use = api_key if api_key else "sk-dummy-key"
    hdrs = {"User-Agent": "TTS-Voice-Creator/probe", "Authorization": f"Bearer {key_to_use}"}

    if type == "llm":
        checks = [("/v1/models", "data"), ("/api/tags", "models"), ("/api/version", None)]
    elif type == "stt":
        checks = [("/health", None), ("/v1/models", "data"), ("/v1/audio/transcriptions", None)]
    elif type == "tts":
        checks = [("/health", None), ("/v1/health", None), ("/v1/audio/voices", None), ("/speakers", None), ("/voices", None)]
    else:
        checks = [("", None)]

    last_err = "No response"
    for path, json_key in checks:
        try:
            r = requests.get(base + path, timeout=5, headers=hdrs, allow_redirects=True)
            if r.status_code >= 500:
                last_err = f"HTTP {r.status_code} on {path or '/'}"
                continue
            if r.status_code >= 400 and path:
                continue
            if json_key:
                try:
                    data = r.json()
                    if json_key not in data:
                        last_err = f"Unexpected response from {path} (missing '{json_key}')"
                        continue
                except Exception:
                    last_err = f"{path} returned non-JSON (HTTP {r.status_code})"
                    continue
            return {"ok": True, "status": r.status_code, "endpoint": path or "/"}
        except requests.exceptions.ConnectionError:
            return {"ok": False, "error": "Connection refused"}
        except requests.exceptions.Timeout:
            return {"ok": False, "error": "Timeout (5 s)"}
        except Exception as e:
            last_err = str(e)
    return {"ok": False, "error": last_err}

@router.get("/api/engine-models")
async def engine_models(url: str, type: str = "", api_key: str = ""):
    """Return model IDs from an OpenAI-style engine URL without exposing host-only URLs to the browser."""
    base = _normalize_service_url(url).rstrip("/")
    if not base:
        raise HTTPException(400, "URL is required")
    candidates: list[str]
    if base.endswith("/v1"):
        candidates = [base + "/models", base[:-3].rstrip("/") + "/v1/models"]
    else:
        candidates = [base.rstrip("/") + "/v1/models", base.rstrip("/") + "/models"]
    if type == "ollama":
        candidates.append(base.rstrip("/") + "/api/tags")
    last_err = "No response"
    seen: set[str] = set()
    models: list[str] = []
    key_to_use = api_key if api_key else "sk-dummy-key"
    for endpoint in candidates:
        try:
            r = requests.get(endpoint, timeout=6, headers={"Authorization": f"Bearer {key_to_use}", "User-Agent": "TTS-Voice-Creator/models"})
            if r.status_code >= 400:
                last_err = f"HTTP {r.status_code} from {endpoint}"
                continue
            payload = r.json()
            data = payload.get("data") if isinstance(payload, dict) else None
            if data is None and isinstance(payload, dict):
                data = payload.get("models")
            if data is None and isinstance(payload, list):
                data = payload
            if not isinstance(data, list):
                last_err = f"Unexpected model response from {endpoint}"
                continue
            for item in data:
                mid = ""
                if isinstance(item, str):
                    mid = item
                elif isinstance(item, dict):
                    mid = str(item.get("id") or item.get("name") or item.get("model") or "")
                mid = mid.strip()
                if mid and mid not in seen:
                    seen.add(mid)
                    models.append(mid)
            if models:
                return {"ok": True, "url": base, "endpoint": endpoint, "models": models}
        except requests.exceptions.Timeout:
            last_err = "Timeout fetching models"
        except Exception as e:
            last_err = str(e)
    return {"ok": False, "url": base, "models": models, "error": last_err}
