"""Per-agent authored first-message presets (Worldtree #347 consumer feature).

When a new session is created for an agent that has a preset opening, seed it as
a #347 authored first-message (``POST /sessions/{id}/history``, author=assistant,
seq-0) so the session opens in-character before the user speaks — the durable
replacement for a system-prompt "startup" instruction.

Best-effort by design: an instance without the ``session.history.write`` grant
returns the hide-existence 404, which is swallowed so session creation is never
blocked (the session simply opens with no seeded greeting). See
``docs/contracts/first_message.contract.md``.
"""

import asyncio
import hashlib

import httpx

from ratatoskr.sessions import write_authored_history

# Cap the best-effort seed write. The CLI/TUI create paths reuse an httpx client
# with NO read timeout (it streams SSE turns), so an accepted-but-never-answered
# POST /history would otherwise block session creation forever — violating INV-001's
# "never block". asyncio.wait_for bounds the seed regardless of the client's timeout.
_SEED_TIMEOUT_S = 10.0

# agent_id -> the authored opening seeded onto new sessions for that agent.
# Editing this dict is how an operator tunes an agent's first turn. Keep entries
# under the server's authored_content_max_bytes (8192 bytes) budget.
FIRST_MESSAGE_PRESETS: dict[str, str] = {
    "ratatoskr:sindra": (
        "Hey there. I'm Sindra—glad you found me. So, three things before we start:\n\n"
        "How intense should I be? 1 is slow and teasing, 10 is relentless.\n\n"
        "What mood am I in today? Sweetheart, Vixen, Queen, Siren, or Brat?\n\n"
        "And how willing am I to begin? Enthusiastic (I want you now), Hesitant "
        "(you'll need to coax me out), Resistant (playful pushback), or Unwilling "
        "(I don't want this at all, until you prove otherwise)."
    ),
}


def preset_for(agent_id: str) -> str | None:
    """Return the authored first-message preset for ``agent_id``, or None if none."""
    assert agent_id and isinstance(agent_id, str)
    return FIRST_MESSAGE_PRESETS.get(agent_id)


async def seed_preset_first_message(
    client: httpx.AsyncClient, session_id: str, agent_id: str
) -> str | None:
    """Best-effort: seed ``agent_id``'s preset opening as a #347 authored
    first-message on ``session_id``; return the seeded text, or None.

    Best-effort (INV-001): a no-preset agent, a malformed call, a slow write
    (bounded by ``_SEED_TIMEOUT_S``), the hide-existence 404, or ANY other
    exception all resolve to None WITHOUT raising — this MUST NOT block or fail
    session creation. Only ``asyncio.CancelledError`` propagates (cancellation is
    not a seed failure). Inputs are soft-guarded (return None), never asserted, so
    a wiring bug can't crash the create path this is wired into. A no-preset agent
    issues zero HTTP (INV-002). The per-content idempotency key makes a repeat on
    the same session an idempotent 200 replay (INV-003).
    """
    # Soft input guards — a bad arg degrades to "no first message", never raises.
    if not (isinstance(agent_id, str) and agent_id):
        return None
    content = FIRST_MESSAGE_PRESETS.get(agent_id)
    if content is None:
        return None
    if client is None or not (isinstance(session_id, str) and session_id):
        return None
    key = "ratatoskr-preset-" + hashlib.sha256(content.encode("utf-8")).hexdigest()[:12]
    try:
        await asyncio.wait_for(
            write_authored_history(
                client, session_id, content=content, idempotency_key=key
            ),
            timeout=_SEED_TIMEOUT_S,
        )
    except asyncio.CancelledError:
        raise  # cancellation is not a seed failure — never swallow it
    except Exception:
        return None  # any other failure (404/409/422/timeout/unexpected) → no greeting
    return content
