"""Messages: one conversation per person, read backwards on demand.

The page is the ordinary chat shell with two differences: it opens on the most
recent turns rather than on all of them, and above them sits a sentinel that
fetches the page before whenever it is scrolled into view.

That sentinel is the mirror of `GET /api/chats/{id}/tail`, which polls forwards,
and it keeps the same four properties for the same reasons — most of all
answering **204 to a cursor it cannot place** rather than falling back to "the
oldest hundred", which would prepend a block the page already holds.
"""

from __future__ import annotations

import logging

from fastapi import APIRouter, Request, Response, status

from lembas.api.deps import Db, RequiredUser
from lembas.api.pages import _chat_context, sidebar_context
from lembas.db.models import Message, Schedule
from lembas.services import messages as messages_service
from lembas.services import schedules as schedules_service
from lembas.services.markdown import render_markdown
from lembas.services.schedule import clock
from lembas.services.schedule import rule as rule_service
from lembas.web.templating import render

log = logging.getLogger(__name__)

router = APIRouter(tags=["messages"])


def _bodies(messages: list[Message]) -> dict[str, str]:
    """Markdown rendered server-side, keyed by id, as `chat_detail` does."""
    return {m.id: render_markdown(m.content) for m in messages if m.role == "user"}


@router.get("/messages")
async def messages_page(request: Request, db: Db, user: RequiredUser):
    conversation = messages_service.for_user(db, user)
    live = messages_service.live_messages(db, conversation)

    # The schedules that post in here, listed beside the conversation because
    # this is where somebody would look for them -- a schedule whose output
    # arrives in this thread and whose controls are two pages away is one nobody
    # will find when they want to stop it.
    posting = list(
        db.scalars(
            schedules_service.visible(user)
            .where(Schedule.target == "messages")
            .order_by(Schedule.created_at.desc())
        )
    )
    zone = clock.zone_for(user)

    return render(
        request,
        "messages/index.html",
        {
            "chat": conversation,
            "messages": live,
            "compacted": [],
            "bodies": _bodies(live),
            "inherited_prompt": "",
            "inherited_from": "",
            "more_before": bool(live) and messages_service.has_more_before(
                db, conversation, live[0]
            ),
            "oldest_id": live[0].id if live else "",
            "schedules": [
                {
                    "row": row,
                    "summary": rule_service.describe(row.rule_json or {}, zone=zone),
                }
                for row in posting
            ],
            **_chat_context(db, user, conversation),
            **sidebar_context(db, user),
        },
    )


@router.get("/api/messages/history")
async def messages_history(
    request: Request, db: Db, user: RequiredUser, before: str = ""
) -> Response:
    """The page of turns immediately before `before`, oldest first.

    204 rather than a fallback whenever the cursor cannot be placed: an absent
    one, one from another chat, one belonging to a message that has gone. The
    alternative -- answering with the oldest page -- would prepend a block the
    reader is already looking at, and a duplicated transcript is something only
    a reload can reconcile.
    """
    conversation = messages_service.for_user(db, user)
    cursor = db.get(Message, before) if before else None
    if cursor is None or cursor.chat_id != conversation.id:
        return Response(status_code=status.HTTP_204_NO_CONTENT)

    page = messages_service.older_than(db, conversation, cursor)
    if not page:
        return Response(status_code=status.HTTP_204_NO_CONTENT)

    from lembas.web.templating import templates

    return templates.TemplateResponse(
        request,
        "messages/_history.html",
        {
            "messages": page,
            "bodies": _bodies(page),
            "more_before": messages_service.has_more_before(db, conversation, page[0]),
            "oldest_id": page[0].id,
            # `render()` injects `user` and friends; `TemplateResponse` does
            # not, and `chat/_message.html` dereferences both `user` and `chat`
            # -- the same reason the SSE path passes them by hand. Missing
            # either is a 500 on scroll and nothing at all on the page that
            # rendered fine.
            "user": user,
            "chat": conversation,
            **_chat_context(db, user, conversation),
        },
    )
