# retoor <retoor@molodetz.nl>

import logging

import uuid_utils
from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect
from fastapi.responses import JSONResponse, RedirectResponse

from devplacepy import stealth
from devplacepy.constants import DEVII_GUEST_COOKIE
from devplacepy.database import get_int_setting, mark_notifications_read_by_target
from devplacepy.seo import base_seo_context, site_url
from devplacepy.services.manager import service_manager

from devplacepy.responses import respond
from devplacepy.schemas import DeviiPageOut
from devplacepy.utils import (
    _user_from_api_key,
    _user_from_session,
    get_current_user,
    is_admin,
    is_primary_admin,
    require_user,
)
from devplacepy.services.audit import record as audit

logger = logging.getLogger("devii.router")

router = APIRouter()

GUEST_COOKIE = DEVII_GUEST_COOKIE
GUEST_COOKIE_MAX_AGE = 31536000
CHANNELS = frozenset({"main", "docs"})


def _service():
    return service_manager.get_service("devii")


def _bearer_key(headers) -> str:
    key = headers.get("x-api-key", "")
    if key:
        return key.strip()
    scheme, _, credentials = headers.get("authorization", "").partition(" ")
    if scheme.lower() == "bearer":
        return credentials.strip()
    return ""


def _resolve_ws_owner(websocket: WebSocket):
    user = _user_from_session(websocket)
    if not user:
        key = _bearer_key(websocket.headers)
        if key:
            user = _user_from_api_key(key)
    if user:
        return (
            "user",
            user["uid"],
            user.get("username", ""),
            user.get("api_key", ""),
            is_admin(user),
            is_primary_admin(user),
        )
    guest_id = websocket.cookies.get(GUEST_COOKIE) or ""
    return "guest", guest_id, "guest", "", False, False


def _owner_from_request(request: Request):
    user = get_current_user(request)
    if user:
        return "user", user["uid"], user.get("username", "")
    guest_id = request.cookies.get(GUEST_COOKIE) or ""
    return "guest", guest_id, "guest"


@router.get("/")
async def devii_page(request: Request):
    user = get_current_user(request)
    if user:
        mark_notifications_read_by_target(user["uid"], "/devii")
    seo_ctx = base_seo_context(
        request,
        title="Devii",
        description="Your personal AI development assistant on DevPlace.",
        robots="noindex,nofollow",
    )
    response = respond(
        request,
        "devii.html",
        {
            **seo_ctx,
            "request": request,
            "user": user,
        },
        model=DeviiPageOut,
    )
    if not user and not request.cookies.get(GUEST_COOKIE):
        response.set_cookie(
            GUEST_COOKIE,
            uuid_utils.uuid7().hex,
            httponly=True,
            samesite="lax",
            max_age=GUEST_COOKIE_MAX_AGE,
        )
    return response


@router.get("/session")
async def devii_session(request: Request):
    user = get_current_user(request)
    svc = _service()
    payload = {
        "loggedIn": bool(user),
        "username": user.get("username") if user else "guest",
        "enabled": bool(svc and svc.is_enabled()),
        "guestsEnabled": bool(svc and svc.guests_enabled()),
        "baseUrl": site_url(request),
    }
    response = JSONResponse(payload)
    if not user and not request.cookies.get(GUEST_COOKIE):
        response.set_cookie(
            GUEST_COOKIE,
            uuid_utils.uuid7().hex,
            httponly=True,
            samesite="lax",
            max_age=GUEST_COOKIE_MAX_AGE,
        )
    return response


def _validate_target(value: str) -> str:
    if value.startswith("/") and not value.startswith("//"):
        return value
    return "/"


@router.get("/adopt")
async def devii_adopt(request: Request):
    target = _validate_target(request.query_params.get("next", "/"))
    response = RedirectResponse(target, status_code=303)
    svc = _service()
    if svc is None:
        return response
    owner_kind, owner_id, _ = _owner_from_request(request)
    session = svc.hub().find(owner_kind, owner_id) if owner_id else None
    token = session.take_pending_session() if session else None
    if token:
        max_age = max(1, get_int_setting("session_remember_days", 30)) * 86400
        response.set_cookie(
            "session", token, max_age=max_age, httponly=True, samesite="lax"
        )
    return response


@router.get("/usage")
async def devii_usage(request: Request):
    svc = _service()
    owner_kind, owner_id, _ = _owner_from_request(request)
    if svc is None:
        return JSONResponse({"used_pct": 0.0, "turns_today": 0})
    viewer_is_admin = is_admin(get_current_user(request))
    spent = svc.spent_24h(owner_kind, owner_id) if owner_id else 0.0
    limit = svc.daily_limit_for(owner_kind, viewer_is_admin)
    turns = svc.hub().ledger.turns_24h(owner_kind, owner_id) if owner_id else 0
    used_pct = round(min(100.0, spent / limit * 100), 1) if limit > 0 else 0.0
    payload = {
        "used_pct": used_pct,
        "turns_today": turns,
        "owner_kind": owner_kind,
    }
    if viewer_is_admin:
        payload["spent_24h"] = round(spent, 6)
        payload["limit"] = limit
    return JSONResponse(payload)


@router.post("/clippy/ai/chat")
async def clippy_proxy(request: Request):
    user = require_user(request)
    svc = _service()
    if svc is None or not svc.is_enabled():
        return JSONResponse({"error": "Devii is unavailable"}, status_code=503)
    cfg = svc.effective_config()
    body = await request.body()
    headers = {
        "Content-Type": "application/json",
        "X-App-Reference": "devplace-devii-v-1-0-0",
    }
    if user.get("api_key"):
        headers["Authorization"] = f"Bearer {user['api_key']}"
    elif cfg.get("devii_ai_key"):
        headers["Authorization"] = f"Bearer {cfg['devii_ai_key']}"
    async with stealth.stealth_async_client(timeout=45.0) as client:
        upstream = await client.post(cfg["devii_ai_url"], content=body, headers=headers)
    audit.record(
        request,
        "ai.clippy.chat",
        summary="clippy AI chat",
        metadata={"status_code": upstream.status_code},
    )
    return JSONResponse(
        content=upstream.json()
        if upstream.headers.get("content-type", "").startswith("application/json")
        else {"raw": upstream.text},
        status_code=upstream.status_code,
    )


@router.websocket("/ws")
async def devii_ws(websocket: WebSocket):
    await websocket.accept()
    svc = _service()
    if svc is None or not svc.is_enabled():
        await websocket.close(code=1013)
        return
    if not service_manager.owns_lock():
        await websocket.close(code=4013)
        return
    channel = websocket.query_params.get("channel", "main")
    if channel not in CHANNELS:
        channel = "main"
    owner_kind, owner_id, username, api_key, owner_is_admin, owner_is_primary_admin = (
        _resolve_ws_owner(websocket)
    )
    if owner_kind == "guest" and not svc.guests_enabled():
        await websocket.send_json(
            {"type": "error", "text": "Guest access to Devii is disabled."}
        )
        await websocket.close(code=1008)
        return
    if owner_kind == "guest" and not owner_id:
        await websocket.send_json(
            {"type": "error", "text": "Open Devii from the page to start a session."}
        )
        await websocket.close(code=1008)
        return

    session = svc.hub().get_or_create(
        owner_kind,
        owner_id,
        username,
        api_key,
        svc.instance_base_url(),
        is_admin=owner_is_admin,
        is_primary_admin=owner_is_primary_admin,
        channel=channel,
    )
    session.attach(websocket)
    try:
        await session.send_bootstrap(websocket)
        while True:
            data = await websocket.receive_json()
            kind = data.get("type")
            if kind == "input":
                text = str(data.get("text", "")).strip()
                if not text:
                    continue
                command = text.lower().lstrip("/").strip()
                if command == "stop":
                    await session.stop()
                    continue
                if command == "reset":
                    await session.reset()
                    continue
                if await session.try_answer_interaction(text):
                    continue
                if svc.quota_exceeded(owner_kind, owner_id, owner_is_admin):
                    limit = svc.daily_limit_for(owner_kind, owner_is_admin)
                    audit.record_system(
                        "ai.quota.exceeded",
                        actor_kind=owner_kind if owner_kind == "guest" else "user",
                        actor_uid=owner_id if owner_kind == "user" else None,
                        actor_username=username,
                        actor_role="admin" if owner_is_admin else ("guest" if owner_kind == "guest" else "member"),
                        origin="devii",
                        via_agent=1,
                        result="denied",
                        summary=f"AI request by {username or owner_id} blocked - 24h quota reached",
                        metadata={"limit_usd": limit, "owner_kind": owner_kind},
                    )
                    await websocket.send_json(
                        {
                            "type": "error",
                            "text": "Daily AI quota reached (100%). Try again later.",
                        }
                    )
                    continue
                session.spawn_turn(text)
            elif kind == "reset":
                await session.reset()
            elif kind == "stop":
                await session.stop()
            elif kind == "clear":
                await session.reset_conversation()
            elif kind == "visibility":
                session.set_visibility(
                    websocket,
                    bool(data.get("visible", True)),
                    bool(data.get("focused", False)),
                )
            elif kind == "clientinfo":
                offset = data.get("offset")
                session.set_clientinfo(
                    str(data.get("timezone", "") or "").strip(),
                    int(offset) if isinstance(offset, (int, float)) else None,
                )
            elif kind in ("avatar_result", "client_result"):
                session.resolve_query(str(data.get("id", "")), data.get("result"))
            elif kind == "interaction_result":
                session.resolve_interaction(
                    str(data.get("id", data.get("interaction_id", ""))),
                    data.get("result") or data,
                )
    except WebSocketDisconnect:
        pass
    except Exception:  # noqa: BLE001 - never let the socket loop crash the worker
        logger.exception("Devii websocket loop failed for %s/%s", owner_kind, owner_id)
    finally:
        session.detach(websocket)
