"""Beaver session 子系统对 runtime 暴露的统一门面。"""

from __future__ import annotations

from pathlib import Path
from typing import Any

from .models import MessageRecord
from .search import SessionSearchService
from .store import SessionStore


class SessionManager:
    """供 AgentLoop / services / MCP tools 使用的统一 session facade。"""

    def __init__(self, workspace: str | Path, db_path: str | Path | None = None) -> None:
        self.workspace = Path(workspace)
        self.sessions_dir = self.workspace / "sessions"
        self.sessions_dir.mkdir(parents=True, exist_ok=True)
        self.db_path = Path(db_path) if db_path is not None else self.sessions_dir / "state.db"
        self.store = SessionStore(self.db_path)
        self.search = SessionSearchService(self.store)

    def close(self) -> None:
        self.store.close()

    def ensure_session(
        self,
        session_id: str,
        *,
        source: str = "unknown",
        model: str | None = None,
        title: str | None = None,
        user_id: str | None = None,
        parent_session_id: str | None = None,
    ) -> str:
        return self.store.ensure_session(
            session_id,
            source=source,
            model=model,
            title=title,
            user_id=user_id,
            parent_session_id=parent_session_id,
        )

    def get_session(self, session_id: str) -> dict[str, Any] | None:
        record = self.store.get_session_record(session_id)
        return record.to_dict() if record is not None else None

    def get_or_create(
        self,
        session_id: str,
        *,
        source: str = "unknown",
        model: str | None = None,
        title: str | None = None,
        user_id: str | None = None,
        parent_session_id: str | None = None,
    ) -> dict[str, Any]:
        self.ensure_session(
            session_id,
            source=source,
            model=model,
            title=title,
            user_id=user_id,
            parent_session_id=parent_session_id,
        )
        session = self.get_session(session_id)
        if session is None:
            raise RuntimeError(f"Failed to create session {session_id!r}")
        return session

    def append_message(self, session_id: str, **kwargs: Any) -> int:
        return self.store.append_message(session_id, **kwargs)

    def get_event_records(self, session_id: str) -> list[MessageRecord]:
        """返回当前 session 的完整事件流。

        这里和 `get_messages_as_conversation()` 的区别很关键：
        - `get_event_records()` 面向 runtime / replay / audit，保留隐藏系统事件
        - `get_messages_as_conversation()` 面向 prompt builder，只暴露可进上下文的事件

        第 6 阶段开始后，session 已不再只是“聊天消息存储”，而是在逐步收敛成
        “外部事件流 + 上层投影视图”。
        """

        return self.store.get_event_records(session_id)

    def get_run_event_records(self, session_id: str, run_id: str) -> list[MessageRecord]:
        """返回某一次 direct run / future bus run 对应的事件片段。"""

        return self.store.get_run_event_records(session_id, run_id)

    def update_latest_assistant_event_payload(
        self,
        session_id: str,
        run_id: str,
        updates: dict[str, Any],
    ) -> None:
        """把 run 级 UI 状态投影回最新 assistant 可见消息。"""

        self.store.update_latest_assistant_event_payload(session_id, run_id, updates)

    def set_run_context_visible(self, session_id: str, run_id: str, visible: bool) -> None:
        self.store.set_run_context_visible(session_id, run_id, visible)

    def list_run_ids(self, session_id: str) -> list[str]:
        """按出现顺序列出当前 session 的所有 run_id。"""

        return self.store.list_run_ids(session_id)

    def get_messages_as_conversation(self, session_id: str) -> list[dict[str, Any]]:
        return self.store.get_messages_as_conversation(session_id)

    def get_visible_history(self, session_id: str, max_messages: int = 500) -> list[dict[str, Any]]:
        """返回适合注入 prompt 的可见历史切片。

        这里故意不直接暴露完整事件流，而是继续提供“模型可消费历史”这个投影视图：
        1. 只包含 `context_visible=True` 的事件
        2. 继续保留旧式窗口裁剪逻辑，避免当前主链行为突然变化
        3. 让 `ContextBuilder` 明确消费的是“上游裁剪后的可见片段”
        """

        records = self.get_event_records(session_id)
        completed_run_ids = {
            record.run_id
            for record in records
            if record.run_id and record.event_type == "run_completed"
        }
        failed_run_ids = {
            record.run_id
            for record in records
            if record.run_id
            and record.event_type == "run_completed"
            and (
                record.finish_reason == "error"
                or (record.event_payload or {}).get("finish_reason") == "error"
            )
        }
        history = []
        for record in records:
            if not record.context_visible or record.role == "system":
                continue
            if record.role == "tool":
                continue
            if record.role == "assistant" and record.tool_calls:
                continue
            if record.run_id and record.run_id not in completed_run_ids:
                continue
            if record.run_id and record.run_id in failed_run_ids:
                continue
            if record.role == "assistant" and record.finish_reason == "error":
                continue
            history.append(record.to_conversation_message())
        sliced = history[-max_messages:]
        for index, message in enumerate(sliced):
            if message.get("role") == "user":
                sliced = sliced[index:]
                break
        return sliced

    def get_history(self, session_id: str, max_messages: int = 500) -> list[dict[str, Any]]:
        """兼容旧名称，实际返回可见历史切片。"""

        return self.get_visible_history(session_id, max_messages=max_messages)

    def update_system_prompt(self, session_id: str, system_prompt: str) -> None:
        self.store.update_system_prompt(session_id, system_prompt)

    def update_usage(self, session_id: str, **kwargs: Any) -> None:
        self.store.update_usage(session_id, **kwargs)

    def end_session(self, session_id: str, end_reason: str) -> None:
        self.store.end_session(session_id, end_reason)

    def reopen_session(self, session_id: str) -> None:
        self.store.reopen_session(session_id)

    def list_sessions_rich(self, **kwargs: Any) -> list[dict[str, Any]]:
        return self.search.list_sessions_rich(**kwargs)

    def search_messages(self, **kwargs: Any) -> list[dict[str, Any]]:
        return self.search.search_messages(**kwargs)

    def resolve_session_id(self, session_id_or_prefix: str) -> str | None:
        return self.search.resolve_session_id(session_id_or_prefix)
