"""Mask Face tab — Shining LED face mask control (BLE).

Routes live under /api/mask. Backed by the FaceController subsystem
(face/mask_face.py), which owns a dedicated asyncio loop + BLE connection to the
standalone Mask project's `shiningmask` library.

Every handler is failure-safe: if the subsystem or its library is unavailable it
returns 503 (GET /status returns a degraded body) rather than crash the
dashboard. FaceController raises RuntimeError for "not connected" / "face not
started"; those map to 409. Blocking BLE calls run in a thread pool so the event
loop stays responsive.
"""

from __future__ import annotations

import asyncio
from typing import List, Optional

from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel

from Project.Sanad.core.logger import get_logger

log = get_logger("mask_routes")

router = APIRouter()


# ── lazy subsystem accessor ─────────────────────────────────

def _get_face():
    try:
        from Project.Sanad.main import mask_face  # type: ignore
        return mask_face
    except Exception:
        return None


def _require():
    mf = _get_face()
    if mf is None:
        raise HTTPException(503, "Mask face subsystem unavailable.")
    return mf


def _run(fn, *args, **kwargs):
    """Call a FaceController method, mapping its errors to HTTP status codes."""
    try:
        return fn(*args, **kwargs)
    except HTTPException:
        raise
    except RuntimeError as exc:
        raise HTTPException(409, str(exc))
    except Exception as exc:  # noqa: BLE001
        log.exception("mask operation failed")
        raise HTTPException(500, str(exc))


# ── status ──────────────────────────────────────────────────

@router.get("/status")
async def status():
    """Never raises — returns a degraded body if the subsystem is missing."""
    mf = _get_face()
    if mf is None:
        return {"available": False, "connected": False, "lib_available": False,
                "last_error": "mask face subsystem not constructed"}
    s = await asyncio.to_thread(mf.status)
    s["available"] = True
    return s


# ── connection ──────────────────────────────────────────────

@router.post("/connect")
async def connect(timeout: Optional[float] = Query(None),
                  attempts: Optional[int] = Query(None)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.connect, timeout, attempts)


@router.post("/disconnect")
async def disconnect():
    mf = _require()
    return await asyncio.to_thread(_run, mf.disconnect)


# ── simple commands ─────────────────────────────────────────

@router.post("/brightness")
async def brightness(level: int = Query(..., ge=0, le=255)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.set_brightness, level)


class TextBody(BaseModel):
    text: str = ""
    color: List[int] = [255, 255, 255]
    mode: Optional[int] = None
    bg: Optional[List[int]] = None      # background RGB (None -> black)
    speed: Optional[int] = None         # scroll speed 0-255 (None -> firmware default)


@router.post("/text")
async def text(body: TextBody):
    mf = _require()
    bg = tuple(body.bg) if body.bg else None
    return await asyncio.to_thread(_run, mf.set_text, body.text, tuple(body.color),
                                   body.mode, bg, body.speed)


@router.post("/image")
async def image(id: int = Query(...)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.show_image, id)


@router.post("/animation")
async def animation(id: int = Query(...)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.play_animation, id)


@router.post("/clear")
async def clear():
    mf = _require()
    return await asyncio.to_thread(_run, mf.clear_diy)


# ── animated face ───────────────────────────────────────────

@router.post("/face/start")
async def face_start(reload: bool = Query(False)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.face_start, reload)


@router.post("/face/stop")
async def face_stop():
    mf = _require()
    return await asyncio.to_thread(_run, mf.face_stop)


@router.post("/face/return")
async def face_return():
    """Resume the live animated face after a text/image/animation override."""
    mf = _require()
    return await asyncio.to_thread(_run, mf.return_face)


class FaceColorBody(BaseModel):
    eye: Optional[List[int]] = None      # eye/iris RGB
    mouth: Optional[List[int]] = None    # mouth RGB
    sclera: Optional[List[int]] = None   # white-of-the-eye RGB


@router.post("/face/color")
async def face_color(body: FaceColorBody):
    """Recolor the animated face (re-uploads the frame set if the face is live)."""
    mf = _require()
    return await asyncio.to_thread(_run, mf.set_face_color, body.eye, body.mouth, body.sclera)


@router.post("/speaking")
async def speaking(on: bool = Query(...)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.set_speaking, on)


@router.post("/mouth")
async def mouth(level: int = Query(..., ge=0, le=3)):
    mf = _require()
    return await asyncio.to_thread(_run, mf.set_mouth, level)


@router.post("/expression/{name}")
async def expression(name: str):
    mf = _require()
    return await asyncio.to_thread(_run, mf.show_expression, name)
