"""Presence state serializer — transforms ADR-023 presence dicts for consumers.

Converts the raw presence schema (version, liveness, mood, energy, etc.)
into the camelCase world-state payload consumed by the Workshop 3D renderer
and WebSocket gateway.
"""

import logging
import time
from datetime import UTC, datetime

logger = logging.getLogger(__name__)

# Default Pip familiar state (used when familiar module unavailable)
DEFAULT_PIP_STATE = {
    "name": "Pip",
    "mood": "sleepy",
    "energy": 0.5,
    "color": "0x00b450",  # emerald green
    "trail_color": "0xdaa520",  # gold
}


def _get_familiar_state() -> dict:
    """Get Pip familiar state from familiar module, with graceful fallback.

    Returns a dict with name, mood, energy, color, and trail_color.
    Falls back to default state if familiar module unavailable or raises.
    """
    try:
        from timmy.familiar import pip_familiar

        snapshot = pip_familiar.snapshot()
        # Map PipSnapshot fields to the expected agent_state format
        return {
            "name": snapshot.name,
            "mood": snapshot.state,
            "energy": DEFAULT_PIP_STATE["energy"],  # Pip doesn't track energy yet
            "color": DEFAULT_PIP_STATE["color"],
            "trail_color": DEFAULT_PIP_STATE["trail_color"],
        }
    except Exception as exc:
        logger.warning("Familiar state unavailable, using default: %s", exc)
        return DEFAULT_PIP_STATE.copy()


# Valid bark styles for Matrix protocol
BARK_STYLES = {"speech", "thought", "whisper", "shout"}


def produce_bark(agent_id: str, text: str, reply_to: str = None, style: str = "speech") -> dict:
    """Format a chat response as a Matrix bark message.

    Barks appear as floating text above agents in the Matrix 3D world with
    typing animation. This function formats the text for the Matrix protocol.

    Parameters
    ----------
    agent_id:
        Unique identifier for the agent (e.g. ``"timmy"``).
    text:
        The chat response text to display as a bark.
    reply_to:
        Optional message ID or reference this bark is replying to.
    style:
        Visual style of the bark. One of: "speech" (default), "thought",
        "whisper", "shout". Invalid styles fall back to "speech".

    Returns
    -------
    dict
        Bark message with keys ``type``, ``agent_id``, ``data`` (containing
        ``text``, ``reply_to``, ``style``), and ``ts``.

    Examples
    --------
    >>> produce_bark("timmy", "Hello world!")
    {
        "type": "bark",
        "agent_id": "timmy",
        "data": {"text": "Hello world!", "reply_to": None, "style": "speech"},
        "ts": 1742529600,
    }
    """
    # Validate and normalize style
    if style not in BARK_STYLES:
        style = "speech"

    # Truncate text to 280 characters (bark, not essay)
    truncated_text = text[:280] if text else ""

    return {
        "type": "bark",
        "agent_id": agent_id,
        "data": {
            "text": truncated_text,
            "reply_to": reply_to,
            "style": style,
        },
        "ts": int(time.time()),
    }


def produce_thought(
    agent_id: str, thought_text: str, thought_id: int, chain_id: str = None
) -> dict:
    """Format a thinking engine thought as a Matrix thought message.

    Thoughts appear as subtle floating text in the 3D world, streaming from
    Timmy's thinking engine (/thinking/api). This function wraps thoughts in
    Matrix protocol format.

    Parameters
    ----------
    agent_id:
        Unique identifier for the agent (e.g. ``"timmy"``).
    thought_text:
        The thought text to display. Truncated to 500 characters.
    thought_id:
        Unique identifier for this thought (sequence number).
    chain_id:
        Optional chain identifier grouping related thoughts.

    Returns
    -------
    dict
        Thought message with keys ``type``, ``agent_id``, ``data`` (containing
        ``text``, ``thought_id``, ``chain_id``), and ``ts``.

    Examples
    --------
    >>> produce_thought("timmy", "Considering the options...", 42, "chain-123")
    {
        "type": "thought",
        "agent_id": "timmy",
        "data": {"text": "Considering the options...", "thought_id": 42, "chain_id": "chain-123"},
        "ts": 1742529600,
    }
    """
    # Truncate text to 500 characters (thoughts can be longer than barks)
    truncated_text = thought_text[:500] if thought_text else ""

    return {
        "type": "thought",
        "agent_id": agent_id,
        "data": {
            "text": truncated_text,
            "thought_id": thought_id,
            "chain_id": chain_id,
        },
        "ts": int(time.time()),
    }


def serialize_presence(presence: dict) -> dict:
    """Transform an ADR-023 presence dict into the world-state API shape.

    Parameters
    ----------
    presence:
        Raw presence dict as written by
        :func:`~timmy.workshop_state.get_state_dict` or read from
        ``~/.timmy/presence.json``.

    Returns
    -------
    dict
        CamelCase world-state payload with ``timmyState``, ``familiar``,
        ``activeThreads``, ``recentEvents``, ``concerns``, ``visitorPresent``,
        ``updatedAt``, and ``version`` keys.
    """
    return {
        "timmyState": {
            "mood": presence.get("mood", "calm"),
            "activity": presence.get("current_focus", "idle"),
            "energy": presence.get("energy", 0.5),
            "confidence": presence.get("confidence", 0.7),
        },
        "familiar": presence.get("familiar"),
        "activeThreads": presence.get("active_threads", []),
        "recentEvents": presence.get("recent_events", []),
        "concerns": presence.get("concerns", []),
        "visitorPresent": False,
        "updatedAt": presence.get("liveness", datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")),
        "version": presence.get("version", 1),
    }


# ---------------------------------------------------------------------------
# Status mapping: ADR-023 current_focus → Matrix agent status
# ---------------------------------------------------------------------------
_STATUS_KEYWORDS: dict[str, str] = {
    "thinking": "thinking",
    "speaking": "speaking",
    "talking": "speaking",
    "idle": "idle",
}


def _derive_status(current_focus: str) -> str:
    """Map a free-text current_focus value to a Matrix status enum.

    Returns one of: online, idle, thinking, speaking.
    """
    focus_lower = current_focus.lower()
    for keyword, status in _STATUS_KEYWORDS.items():
        if keyword in focus_lower:
            return status
    if current_focus and current_focus != "idle":
        return "online"
    return "idle"


def produce_agent_state(agent_id: str, presence: dict) -> dict:
    """Build a Matrix-compatible ``agent_state`` message from presence data.

    Parameters
    ----------
    agent_id:
        Unique identifier for the agent (e.g. ``"timmy"``).
    presence:
        Raw ADR-023 presence dict.

    Returns
    -------
    dict
        Message with keys ``type``, ``agent_id``, ``data``, and ``ts``.
    """
    return {
        "type": "agent_state",
        "agent_id": agent_id,
        "data": {
            "display_name": presence.get("display_name", agent_id.title()),
            "role": presence.get("role", "assistant"),
            "status": _derive_status(presence.get("current_focus", "idle")),
            "mood": presence.get("mood", "calm"),
            "energy": presence.get("energy", 0.5),
            "bark": presence.get("bark", ""),
            "familiar": _get_familiar_state(),
        },
        "ts": int(time.time()),
    }


def produce_system_status() -> dict:
    """Generate a system_status message for the Matrix.

    Returns a dict with system health metrics including agent count,
    visitor count, uptime, thinking engine status, and memory count.

    Returns
    -------
    dict
        Message with keys ``type``, ``data`` (containing ``agents_online``,
        ``visitors``, ``uptime_seconds``, ``thinking_active``, ``memory_count``),
        and ``ts``.

    Examples
    --------
    >>> produce_system_status()
    {
        "type": "system_status",
        "data": {
            "agents_online": 5,
            "visitors": 2,
            "uptime_seconds": 3600,
            "thinking_active": True,
            "memory_count": 150,
        },
        "ts": 1742529600,
    }
    """
    # Count agents with status != offline
    agents_online = 0
    try:
        from timmy.agents.loader import list_agents

        agents = list_agents()
        agents_online = sum(1 for a in agents if a.get("status", "") not in ("offline", ""))
    except Exception as exc:
        logger.debug("Failed to count agents: %s", exc)

    # Count visitors from WebSocket clients
    visitors = 0
    try:
        from dashboard.routes.world import _ws_clients

        visitors = len(_ws_clients)
    except Exception as exc:
        logger.debug("Failed to count visitors: %s", exc)

    # Calculate uptime
    uptime_seconds = 0
    try:
        from datetime import UTC

        from config import APP_START_TIME

        uptime_seconds = int((datetime.now(UTC) - APP_START_TIME).total_seconds())
    except Exception as exc:
        logger.debug("Failed to calculate uptime: %s", exc)

    # Check thinking engine status
    thinking_active = False
    try:
        from config import settings
        from timmy.thinking import thinking_engine

        thinking_active = settings.thinking_enabled and thinking_engine is not None
    except Exception as exc:
        logger.debug("Failed to check thinking status: %s", exc)

    # Count memories in vector store
    memory_count = 0
    try:
        from timmy.memory_system import get_memory_stats

        stats = get_memory_stats()
        memory_count = stats.get("total_entries", 0)
    except Exception as exc:
        logger.debug("Failed to count memories: %s", exc)

    return {
        "type": "system_status",
        "data": {
            "agents_online": agents_online,
            "visitors": visitors,
            "uptime_seconds": uptime_seconds,
            "thinking_active": thinking_active,
            "memory_count": memory_count,
        },
        "ts": int(time.time()),
    }
