"""Structured message history helpers for PydanticAI agents."""

from __future__ import annotations

from datetime import UTC, datetime, timedelta

import logfire
import pydantic_core
from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter

# Conversations with no activity for longer than this are treated as expired.
# A new inbound message after this window starts a fresh session (no prior
# history is sent to the LLM).  Applies to all channels.
SESSION_WINDOW_HOURS: int = 12


def is_conversation_stale(last_message_at: datetime | None) -> bool:
    """Return True when the conversation has been idle beyond the session window.

    All datetimes in the DB are naive UTC (see ``utcnow()`` in ``app.db.base``).
    A conversation with no messages yet (``None``) is not stale — it was just
    created.
    """
    if last_message_at is None:
        return False
    cutoff = datetime.now(UTC).replace(tzinfo=None) - timedelta(hours=SESSION_WINDOW_HOURS)
    return last_message_at < cutoff


def serialize_messages(messages: list[ModelMessage]) -> str:
    """Serialize a list of ModelMessages to a JSON string."""
    return pydantic_core.to_json(ModelMessagesTypeAdapter.dump_python(messages)).decode("utf-8")


def deserialize_messages(json_str: str | None) -> list[ModelMessage]:
    """Deserialize a JSON string to a list of ModelMessages.

    Returns empty list if input is None or malformed.
    """
    if json_str is None:
        return []
    try:
        return list(ModelMessagesTypeAdapter.validate_json(json_str))
    except Exception:
        logfire.warning("message_history_deserialization_failed", length=len(json_str))
        return []


async def keep_recent_messages(
    messages: list[ModelMessage],
    *,
    limit: int = 20,
) -> list[ModelMessage]:
    """History processor that keeps the last `limit` messages.

    If trimming would split a tool-call/tool-result pair
    (ModelResponse followed by ModelRequest with tool results),
    includes the paired message to maintain coherence.
    """
    if len(messages) <= limit:
        return messages

    trimmed = messages[-limit:]

    # Check if we split a tool pair: if the first message in trimmed
    # is a ModelRequest that starts with tool-return parts, include
    # the preceding ModelResponse.
    from pydantic_ai.messages import ModelRequest, ToolReturnPart

    first = trimmed[0]
    if (
        isinstance(first, ModelRequest)
        and first.parts
        and isinstance(first.parts[0], ToolReturnPart)
    ):
        # Find the preceding message in the original list
        idx = len(messages) - limit
        if idx > 0:
            trimmed = [messages[idx - 1], *trimmed]

    return trimmed
