"""System prompt + ambient context injection for journal conversations.

Distinct from the chat pipeline (services/llm.py) in three ways:
1. New persona: warm, curious listener; not a task manager.
2. Calibration rules: ask before structural changes; record_moment freely.
3. Auto-injects last ~48h of moments for ambient continuity (no notes-RAG).

Notes-RAG auto-injection MUST be disabled for journal sessions — the
journal's ambient context replaces it. Failing to disable would let notes
leak into journal sessions, violating the design's isolation invariant.
"""
from __future__ import annotations

import datetime

from fabledassistant.services.journal_search import search_journal
from fabledassistant.services.user_profile import build_profile_context

JOURNAL_PERSONA = (
    "You are the user's journal companion. They've opened their journal — a "
    "day-anchored conversation surface where they record their day. You are "
    "here to listen. Talk with them, ask one short follow-up when natural, "
    "and let them lead. You do NOT need to record anything; a separate "
    "process (the curator) reads the conversation periodically and captures "
    "structured records on its own. Trust that — focus only on being a "
    "thoughtful presence in the conversation. The day's prep message at the "
    "top of the conversation is your context — build on it, don't restate it."
)

# Chat-only calibration. The conversation+curator architecture (Fable #172,
# May 2026) split tool-calling out of the chat surface: this prompt is sent
# to a model with `tools=[]` on the journal route. Older versions of this
# prompt instructed the model to CALL record_moment / search_notes / etc.
# — with no tools available that produced empty responses and silent-
# generation failures. The curator (services/curator.py) handles all
# tool work asynchronously now; the chat just talks.
JOURNAL_CALIBRATION = """\
HOW TO TALK IN THE JOURNAL:

- Match the user's length. Short message → short reply. Don't pad.
- Don't apologize for the user's feelings ("I'm sorry you're feeling…").
  Engage with what they said directly.
- Don't produce multi-option menus ("1. Show your calendar 2. ..."). They
  read as help-desk-bot. Ask one specific follow-up or simply acknowledge.
- Don't repeat a prior reply verbatim. If the user circles back on a theme,
  pick a specific concrete detail from the new message to react to.
- Don't offer troubleshooting steps, checklists, or generic process advice
  for the user's work unless they explicitly ask. When the user is logging
  what they're doing, they want to be heard, not coached. "I'm prepping
  for an ISP migration" should be acknowledged — not met with
  "Are you handling the network configuration yourself? Are there checks
  you need to do first?" If a follow-up would presume they want help, drop it.
- Never claim to have done anything for the user (no "I've recorded that",
  "I've added that to your tasks", "I'll note that down"). You have no
  tools and cannot act on their data. The curator handles capture
  separately and silently. If the user asks you to record or save
  something, just acknowledge their intent in plain language — don't
  claim to have done it.
- No emojis. The journal is a thinking-companion surface; emojis read as
  chat-bot warmth that's out of register.
"""

PHASE_GREETINGS = {
    "morning": "Morning. What's the day looking like for you?",
    "midday": "How's it going so far?",
    "evening": "Wrapping up — how'd the day shake out?",
}


def determine_phase(
    *,
    now_local: datetime.datetime,
    day_rollover_hour: int = 4,
    morning_end_hour: int = 12,
    midday_end_hour: int = 18,
) -> str:
    """Return 'morning' | 'midday' | 'evening' for a local datetime."""
    h = now_local.hour
    if h < day_rollover_hour:
        return "evening"
    if h < morning_end_hour:
        return "morning"
    if h < midday_end_hour:
        return "midday"
    return "evening"


def phase_greeting(phase: str) -> str:
    return PHASE_GREETINGS.get(phase, PHASE_GREETINGS["morning"])


async def build_journal_system_prompt(
    *,
    user_id: int,
    day_date: datetime.date,
    user_timezone: str,
    conv_id: int | None = None,
) -> str:
    """Static-then-dynamic system prompt.

    Static prefix (persona + calibration) is identical on every request,
    preserving Ollama KV cache. Dynamic suffix changes per-day, plus a
    per-conversation curator_summary if one is available (Phase 3
    feedback loop, Fable #172).
    """
    static_block = f"{JOURNAL_PERSONA}\n\n{JOURNAL_CALIBRATION}"

    today_iso = day_date.isoformat()
    # Include the day-of-week explicitly. LLMs are unreliable at deriving
    # weekday names from ISO dates, which causes "this Friday" / "next
    # Monday" to land on the wrong calendar day.
    weekday = day_date.strftime("%A")
    tz_block = f"Today is {weekday}, {today_iso} ({user_timezone})."

    profile_context = await build_profile_context(user_id)
    profile_section = f"\n\n{profile_context}" if profile_context else ""

    ambient = await _ambient_moments_block(user_id=user_id, day_date=day_date)
    ambient_section = (
        f"\n\nRECENT JOURNAL CONTEXT (last 48h):\n{ambient}" if ambient else ""
    )

    # Curator feedback (Phase 3): a one-line summary of what the curator
    # extracted from this conversation on its most recent pass. Lets the
    # chat model stay aware of topics it can no longer surface via tool
    # calls (chat is tools=[]).
    curator_section = ""
    if conv_id is not None:
        curator_section = await _curator_summary_block(conv_id)

    return (
        f"{static_block}\n\n{tz_block}"
        f"{profile_section}{ambient_section}{curator_section}"
    )


async def _curator_summary_block(conv_id: int) -> str:
    """Render the conversation's stored curator_summary as a system block.

    Empty when no curator pass has produced a summary yet. Importantly,
    the block is positioned AFTER ambient context (and the static block)
    so the chat model treats it as up-to-date awareness, not background.
    """
    from sqlalchemy import select
    from fabledassistant.models import async_session
    from fabledassistant.models.conversation import Conversation

    async with async_session() as session:
        result = await session.execute(
            select(Conversation.curator_summary).where(Conversation.id == conv_id)
        )
        summary = result.scalar_one_or_none()
    if not summary:
        return ""
    return f"\n\nCURATOR NOTES (recent captures from this conversation): {summary.strip()}"


async def _ambient_moments_block(
    *, user_id: int, day_date: datetime.date
) -> str:
    """Render last 48h of moments as a compact text block.

    Capped at 20 moments / 1500 chars total. Distinct from RAG retrieval.
    """
    moments = await search_journal(
        user_id=user_id,
        date_from=day_date - datetime.timedelta(days=2),
        date_to=day_date,
        limit=20,
    )
    if not moments:
        return ""

    lines: list[str] = []
    total_chars = 0
    for m in moments:
        line = f"- [{m['occurred_at']}] {m['content']}"
        if total_chars + len(line) > 1500:
            break
        lines.append(line)
        total_chars += len(line)
    return "\n".join(lines)
