"""Watch slot API — pin CRUD and live metrics."""

from __future__ import annotations

from typing import Literal

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import BaseModel, Field

from server.api.auth_jwt import get_current_admin
from server.api.dependencies import get_db
from server.application.services.watch_service import SlotsFullError, WatchService
from server.domain.models import Admin
from server.utils.watch_metrics import WATCH_PIN_TTL_DEFAULT_MINUTES

WatchTtlMinutes = Literal[30, 60, 120, 480, 1440]
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(prefix="/api/watch", tags=["watch"])


class WatchPinCreate(BaseModel):
    server_id: int = Field(..., gt=0)
    slot_index: int | None = Field(None, ge=0, le=3)
    replace_slot: int | None = Field(None, ge=0, le=3, description="监测已满时指定替换槽位")
    ttl_minutes: WatchTtlMinutes = Field(
        WATCH_PIN_TTL_DEFAULT_MINUTES,
        description="监测窗口：30分/1h/2h/8h/24h，默认 30 分钟",
    )


class WatchPinReplace(BaseModel):
    server_id: int = Field(..., gt=0)


class WatchPinMonitoringUpdate(BaseModel):
    monitoring_enabled: bool = Field(..., description="是否开启此槽位实时监测")


class WatchPinTtlUpdate(BaseModel):
    ttl_minutes: WatchTtlMinutes = Field(..., description="监测窗口：30分/1h/2h/8h/24h")


def _client_ip(request: Request) -> str:
    return request.client.host if request.client else ""


@router.get("/pins")
async def list_watch_pins(
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    return await svc.list_slots(admin.id)


@router.post("/pins")
async def create_watch_pin(
    payload: WatchPinCreate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    try:
        return await svc.pin_server(
            admin=admin,
            server_id=payload.server_id,
            slot_index=payload.slot_index,
            replace_slot=payload.replace_slot,
            ttl_minutes=payload.ttl_minutes,
            ip_address=_client_ip(request),
        )
    except SlotsFullError as e:
        raise HTTPException(status_code=409, detail="监测槽已满，请选择要替换的槽位") from e
    except ValueError as e:
        raise HTTPException(status_code=422, detail=str(e)) from e


@router.put("/pins/{slot_index}")
async def replace_watch_pin(
    slot_index: int,
    payload: WatchPinReplace,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    try:
        return await svc.replace_slot(
            admin=admin,
            slot_index=slot_index,
            server_id=payload.server_id,
            ip_address=_client_ip(request),
        )
    except ValueError as e:
        raise HTTPException(status_code=422, detail=str(e)) from e


@router.delete("/pins/{slot_index}")
async def delete_watch_pin(
    slot_index: int,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    try:
        return await svc.remove_slot(
            admin=admin,
            slot_index=slot_index,
            ip_address=_client_ip(request),
        )
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e


@router.patch("/pins/{slot_index}/monitoring")
async def update_watch_pin_monitoring(
    slot_index: int,
    payload: WatchPinMonitoringUpdate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    try:
        return await svc.set_slot_monitoring(
            admin=admin,
            slot_index=slot_index,
            monitoring_enabled=payload.monitoring_enabled,
            ip_address=_client_ip(request),
        )
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e


@router.patch("/pins/{slot_index}/ttl")
async def update_watch_pin_ttl(
    slot_index: int,
    payload: WatchPinTtlUpdate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    try:
        return await svc.set_slot_ttl(
            admin=admin,
            slot_index=slot_index,
            ttl_minutes=payload.ttl_minutes,
            ip_address=_client_ip(request),
        )
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e


@router.get("/metrics")
async def get_watch_metrics(
    server_ids: str = Query(..., description="逗号分隔 server_id"),
    hours: int = Query(24, ge=1, le=168),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    ids = _parse_server_ids(server_ids)
    svc = WatchService(db)
    return await svc.get_metrics(admin.id, server_ids=ids, hours=hours)


@router.get("/probe-records")
async def get_watch_probe_records(
    server_id: int | None = None,
    session_id: int | None = None,
    probe_status: str | None = None,
    hours: int = Query(24, ge=1, le=168),
    page: int = Query(1, ge=1),
    per_page: int = Query(50, ge=1, le=200),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    return await svc.get_probe_records(
        admin.id,
        server_id=server_id,
        session_id=session_id,
        probe_status=probe_status,
        hours=hours,
        page=page,
        per_page=per_page,
    )


@router.get("/sessions")
async def get_watch_sessions(
    hours: int = Query(168, ge=1, le=168),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    return await svc.get_sessions(admin.id, hours=hours)


@router.get("/processes/{server_id}")
async def get_watch_processes(
    server_id: int,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    svc = WatchService(db)
    pinned = await svc.pinned_server_map(admin.id)
    if server_id not in pinned:
        raise HTTPException(status_code=404, detail="该服务器不在当前监测槽中")
    return await svc.get_processes(server_id)


@router.post("/servers/{server_id}/install-psutil")
async def install_watch_psutil(
    server_id: int,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Install psutil on the pinned server via SSH (for watch probe metrics)."""
    svc = WatchService(db)
    try:
        return await svc.install_psutil_ssh(
            admin=admin,
            server_id=server_id,
            ip_address=_client_ip(request),
        )
    except ValueError as e:
        raise HTTPException(status_code=422, detail=str(e)) from e


def _parse_server_ids(server_ids: str) -> list[int]:
    ids: list[int] = []
    for part in server_ids.split(","):
        part = part.strip()
        if not part:
            continue
        try:
            ids.append(int(part))
        except ValueError:
            raise HTTPException(status_code=422, detail="server_ids 格式无效") from None
    if not ids:
        raise HTTPException(status_code=422, detail="server_ids 不能为空")
    return ids


@router.get("/live")
async def get_watch_live(
    server_ids: str = Query(..., description="逗号分隔 server_id"),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    ids = _parse_server_ids(server_ids)
    svc = WatchService(db)
    return await svc.get_live_for_servers(ids)
