"""Browser Worker — Playwright Chromium + WebSocket stream."""

from __future__ import annotations

import asyncio
import json
import logging
from contextlib import asynccontextmanager
from typing import Any

from fastapi import FastAPI, Header, HTTPException, WebSocket, WebSocketDisconnect
from pydantic import BaseModel, Field

from config import settings
from session_manager import manager

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("browser_worker")


def _check_key(header: str | None) -> None:
    if not settings.WORKER_SECRET:
        raise HTTPException(status_code=503, detail="Worker secret not configured")
    if not header or header != settings.WORKER_SECRET:
        raise HTTPException(status_code=401, detail="Invalid worker key")


class CreateSessionBody(BaseModel):
    session_id: str = Field(min_length=8, max_length=64)
    viewport_w: int = Field(default=1280, ge=320, le=1920)
    viewport_h: int = Field(default=720, ge=240, le=1080)


@asynccontextmanager
async def lifespan(_app: FastAPI):
    await manager.start()
    logger.info("Playwright Chromium ready")
    yield
    await manager.stop()


app = FastAPI(title="Nexus Browser Worker", lifespan=lifespan)


@app.get("/health")
async def health():
    return {
        "status": "ok",
        "sessions": manager.session_count(),
        "max_sessions": settings.WORKER_MAX_SESSIONS,
    }


@app.post("/v1/sessions")
async def create_session(
    body: CreateSessionBody,
    x_nexus_worker_key: str | None = Header(default=None, alias="X-Nexus-Worker-Key"),
):
    _check_key(x_nexus_worker_key)
    try:
        await manager.reserve_session(body.session_id, body.viewport_w, body.viewport_h)
    except ValueError as exc:
        code = str(exc)
        if code == "max_sessions":
            raise HTTPException(status_code=503, detail="Max sessions reached") from exc
        if code == "session_exists":
            raise HTTPException(status_code=409, detail="Session already exists") from exc
        raise HTTPException(status_code=400, detail=code) from exc
    return {"session_id": body.session_id, "viewport_w": body.viewport_w, "viewport_h": body.viewport_h}


@app.delete("/v1/sessions/{session_id}")
async def delete_session(
    session_id: str,
    x_nexus_worker_key: str | None = Header(default=None, alias="X-Nexus-Worker-Key"),
):
    _check_key(x_nexus_worker_key)
    if not await manager.destroy_session(session_id):
        raise HTTPException(status_code=404, detail="Session not found")
    return {"ok": True}


@app.websocket("/v1/sessions/{session_id}/stream")
async def session_stream(
    websocket: WebSocket,
    session_id: str,
    x_nexus_worker_key: str | None = Header(default=None, alias="X-Nexus-Worker-Key"),
):
    key = x_nexus_worker_key or websocket.headers.get("x-nexus-worker-key")
    if not settings.WORKER_SECRET or key != settings.WORKER_SECRET:
        await websocket.close(code=4401, reason="Invalid worker key")
        return

    if not manager.get_session(session_id):
        await websocket.close(code=4404, reason="Session not found")
        return

    await websocket.accept()

    outbound: asyncio.Queue[dict[str, Any]] = asyncio.Queue()

    async def send(msg: dict[str, Any]) -> None:
        await outbound.put(msg)

    try:
        await manager.attach_stream(session_id, send)
    except ValueError:
        await websocket.close(code=4404, reason="Session not found")
        return
    except RuntimeError:
        await manager.destroy_session(session_id)
        await websocket.close(code=4503, reason="Browser not ready")
        return

    async def pump_out() -> None:
        while True:
            msg = await outbound.get()
            await websocket.send_json(msg)

    pump_task = asyncio.create_task(pump_out(), name=f"ws_out_{session_id}")
    try:
        while True:
            raw = await websocket.receive_text()
            try:
                msg = json.loads(raw)
            except json.JSONDecodeError:
                await send({"type": "ERROR", "message": "Invalid JSON", "code": "BAD_JSON"})
                continue
            if not isinstance(msg, dict):
                continue
            await manager.handle_message(session_id, msg)
    except WebSocketDisconnect:
        pass
    finally:
        pump_task.cancel()
        try:
            await pump_task
        except asyncio.CancelledError:
            pass
        await manager.destroy_session(session_id)


if __name__ == "__main__":
    import uvicorn

    host, _, port = settings.WORKER_BIND.partition(":")
    uvicorn.run("main:app", host=host or "0.0.0.0", port=int(port or 8443), log_level="info")
