"""Route handlers for the session dashboard."""

from __future__ import annotations

import asyncio
from pathlib import Path
from typing import TYPE_CHECKING

import attrs
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from sse_starlette.sse import EventSourceResponse, ServerSentEvent  # type: ignore[attr-defined]

from blackbox.analytics import extract_meta
from blackbox.dashboard.rendering import (
    FILTER_PRESETS,
    fmt_duration,
    fmt_relative,
    fmt_time,
    passes_filter,
    render_log_html,
)
from blackbox.dashboard.transcript import parse_transcript, parse_transcript_tail, scan_sessions
from blackbox.models import SessionInfo

if TYPE_CHECKING:
    from collections.abc import AsyncIterator

    from blackbox.dashboard.watcher import SessionWatcher
    from blackbox.models import LogEntry

TEMPLATES_DIR = Path(__file__).parent / "templates"
PROJECTS_DIR = Path.home() / ".claude" / "projects"

HISTORY_BATCH = 200

router = APIRouter()
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
templates.env.globals["fmt_time"] = fmt_time  # type: ignore[assignment]  # ty: ignore[invalid-assignment]
templates.env.globals["fmt_duration"] = fmt_duration  # type: ignore[assignment]  # ty: ignore[invalid-assignment]
templates.env.globals["fmt_relative"] = fmt_relative  # type: ignore[assignment]  # ty: ignore[invalid-assignment]


def mark_live(sessions: list[SessionInfo], watcher: SessionWatcher) -> list[SessionInfo]:
    live_ids = watcher.live_session_ids()
    if not live_ids:
        return sessions
    return [attrs.evolve(s, is_live=True) if s.session_id in live_ids else s for s in sessions]


@router.get("/", response_class=HTMLResponse)
async def index(request: Request, session: str = "") -> HTMLResponse:
    watcher: SessionWatcher = request.app.state.watcher
    sessions = mark_live(watcher.get_sessions(scan_sessions), watcher)
    return templates.TemplateResponse(
        request,
        "index.html",
        context={"sessions": sessions, "selected_id": session},
    )


@router.get("/sessions", response_class=HTMLResponse)
async def session_list(request: Request, selected: str = "") -> HTMLResponse:
    watcher: SessionWatcher = request.app.state.watcher
    sessions = mark_live(watcher.get_sessions(scan_sessions), watcher)
    return templates.TemplateResponse(
        request,
        "partials/session_list.html",
        context={"sessions": sessions, "selected_id": selected},
    )


@router.get("/sessions/{project_path}/{session_id}", response_class=HTMLResponse)
async def session_detail(
    request: Request,
    project_path: str,
    session_id: str,
    filter: str = "compact",
) -> HTMLResponse:
    projects_dir: Path = request.app.state.projects_dir
    transcript_path = projects_dir / project_path / f"{session_id}.jsonl"
    if not transcript_path.exists():
        return HTMLResponse(
            '<div class="text-center text-gray-500 py-12">Session not found</div>',
        )
    info = build_session_info(transcript_path, session_id, project_path)
    meta = extract_meta(transcript_path)
    return templates.TemplateResponse(
        request,
        "partials/session_detail.html",
        context={
            "session": info,
            "meta": meta,
            "filter": filter,
            "filters": list(FILTER_PRESETS.keys()),
        },
    )


def build_session_info(path: Path, session_id: str, project_path: str) -> SessionInfo:
    """Build a SessionInfo from a transcript file for the detail view."""
    from blackbox.dashboard.transcript import decode_project_name, quick_session_info  # noqa: PLC0415

    info = quick_session_info(path, session_id, project_path, decode_project_name(project_path))
    if info:
        return info
    return SessionInfo(
        session_id=session_id,
        project_path=project_path,
        project_name=project_path,
        transcript_path=str(path),
        started_at=path.stat().st_mtime,
    )


def filter_and_render(entries: list[LogEntry], filter_name: str, allowed: set[str] | None) -> list[str]:
    return [
        html
        for entry in entries
        if passes_filter(entry, filter_name, allowed)
        for html in [render_log_html(entry)]
        if html
    ]


async def log_stream(
    transcript_path: Path,
    filter_name: str,
) -> AsyncIterator[ServerSentEvent]:
    allowed = FILTER_PRESETS.get(filter_name)
    entries = await asyncio.to_thread(parse_transcript, transcript_path)

    batch: list[str] = []
    for entry in entries:
        if passes_filter(entry, filter_name, allowed):
            html = render_log_html(entry)
            if html:
                batch.append(html)
        if len(batch) >= HISTORY_BATCH:
            yield ServerSentEvent(data="\n".join(batch), event="log")
            batch = []
    if batch:
        yield ServerSentEvent(data="\n".join(batch), event="log")

    offset = transcript_path.stat().st_size
    while True:
        await asyncio.sleep(1.0)
        try:
            current_size = transcript_path.stat().st_size
        except OSError:
            break
        if current_size <= offset:
            continue
        new_entries, offset = await asyncio.to_thread(parse_transcript_tail, transcript_path, offset)
        rendered = filter_and_render(new_entries, filter_name, allowed)
        if rendered:
            yield ServerSentEvent(data="\n".join(rendered), event="log")


@router.get("/sessions/{project_path}/{session_id}/logs")
async def session_logs(
    request: Request,
    project_path: str,
    session_id: str,
    filter: str = "compact",
) -> EventSourceResponse:
    projects_dir: Path = request.app.state.projects_dir
    transcript_path = projects_dir / project_path / f"{session_id}.jsonl"
    return EventSourceResponse(log_stream(transcript_path, filter))
