# ai_mouse/server/routes_scroll.py
"""Scroll collection, training, and verification routes."""
from __future__ import annotations

import asyncio
import json
import logging
from pathlib import Path
from typing import AsyncGenerator

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

from .deps import SessionState, get_data_dir, get_state

logger = logging.getLogger(__name__)

router = APIRouter()

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _paths() -> tuple[Path, Path]:
    data_dir = get_data_dir()
    return data_dir / "scroll_traces.jsonl", data_dir / "scroll_models"


def _scroll_trace_count() -> int:
    traces_path, _ = _paths()
    if not traces_path.exists():
        return 0
    return sum(
        1
        for line in traces_path.read_text(encoding="utf-8").splitlines()
        if line.strip()
    )


def _scroll_model_trained() -> bool:
    _, models_dir = _paths()
    return (models_dir / "scroll_model.pt").exists()


# ---------------------------------------------------------------------------
# Request models
# ---------------------------------------------------------------------------


class ScrollStartRequest(BaseModel):
    mode: str = "target"
    count: int = 50
    viewport_height: int = 900


class ScrollTraceRequest(BaseModel):
    meta: dict
    events: list[dict]


class ScrollSkipRequest(BaseModel):
    current_scrollY: int = 0


class ScrollTrainRequest(BaseModel):
    epochs: int = 100


class ScrollVerifyRequest(BaseModel):
    start_scrollY: int = 1000
    target_scrollY: int = 3000
    mode: str = "target"
    n_paths: int = 5


# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------


@router.post("/start")
def scroll_start(
    req: ScrollStartRequest,
    state: SessionState = Depends(get_state),
) -> dict:
    from tools.scroll.collector import ScrollCollector

    scroll_collector = ScrollCollector(
        mode=req.mode, count=req.count, viewport_height=req.viewport_height
    )
    state.scroll_collector = scroll_collector
    target = scroll_collector.next_target(current_scrollY=0)
    return {
        "success_radius": scroll_collector.success_radius,
        **target,
    }


@router.post("/trace")
def scroll_trace(
    trace: ScrollTraceRequest,
    state: SessionState = Depends(get_state),
) -> dict:
    if state.scroll_collector is None:
        raise HTTPException(400, "Scroll collector not started")

    traces_path, _ = _paths()
    traces_path.parent.mkdir(parents=True, exist_ok=True)
    record = {"meta": trace.meta, "events": trace.events}
    with traces_path.open("a", encoding="utf-8") as f:
        f.write(json.dumps(record, ensure_ascii=False) + "\n")

    scroll_collector = state.scroll_collector
    scroll_collector.collected += 1
    remaining = scroll_collector.count - scroll_collector.collected

    if remaining > 0:
        target = scroll_collector.next_target(trace.meta.get("end_scrollY", 0))
        return {"collected": scroll_collector.collected, "remaining": remaining, **target}
    return {"collected": scroll_collector.collected, "remaining": 0, "target_scrollY": None}


@router.post("/skip")
def scroll_skip(
    req: ScrollSkipRequest,
    state: SessionState = Depends(get_state),
) -> dict:
    if state.scroll_collector is None:
        raise HTTPException(400, "Scroll collector not started")
    target = state.scroll_collector.next_target(current_scrollY=req.current_scrollY)
    return target


@router.get("/status")
def scroll_status() -> dict:
    return {"trace_count": _scroll_trace_count(), "model_trained": _scroll_model_trained()}


async def _scroll_train_sse(req: ScrollTrainRequest) -> AsyncGenerator[str, None]:
    """Run scroll training in a thread, yield SSE events via asyncio.Queue."""
    queue: asyncio.Queue[dict] = asyncio.Queue()

    def callback(msg: dict) -> None:
        queue.put_nowait(msg)

    async def run() -> None:
        from tools.scroll.trainer import train_scroll

        traces_path, models_dir = _paths()
        try:
            await asyncio.to_thread(
                train_scroll,
                data_path=traces_path,
                output_dir=models_dir,
                epochs=req.epochs,
                progress_callback=callback,
            )
        except Exception as exc:  # noqa: BLE001
            queue.put_nowait({"error": str(exc)})

    task = asyncio.create_task(run())

    while True:
        msg = await queue.get()
        yield f"data: {json.dumps(msg)}\n\n"
        if msg.get("done") or msg.get("error"):
            break

    await task


@router.post("/train")
async def scroll_train(req: ScrollTrainRequest) -> StreamingResponse:
    return StreamingResponse(
        _scroll_train_sse(req),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@router.post("/verify")
def scroll_verify(req: ScrollVerifyRequest) -> dict:
    # Uses the bundled ONNX scroll model exposed via the public ai_mouse API.
    # The legacy scroll_model.pt path is no longer wired in; export a fresh
    # scroll_decoder.onnx via `python -m tools.export_onnx` to update.
    from ai_mouse import generate_scroll

    paths = []
    for _ in range(min(req.n_paths, 12)):
        events = generate_scroll(
            req.start_scrollY,
            req.target_scrollY,
            mode=req.mode,
        )
        paths.append(events)
    return {"paths": paths}
