"""Session Checkpoint 测试 — Issue #223

异常中断检测 + raw_events 回放聚合。
"""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest

from index1.session_context import SessionContext


# ── 辅助函数 ──


def _make_raw_event(
    tool_name: str,
    file_path: str = "",
    importance: float = 0.0,
    summary: str = "",
    new_string: str = "",
) -> dict:
    """构造 raw_event 记录"""
    tool_input = {}
    if file_path:
        tool_input["file_path"] = file_path
    if new_string:
        tool_input["new_string"] = new_string
    return {
        "event_id": 1,
        "tool_name": tool_name,
        "collection": "test",
        "payload": json.dumps({"tool_input": tool_input}),
        "importance": importance,
        "summary": summary,
        "signals_json": "{}",
        "timestamp": "2026-02-14T10:00:00",
    }


# ── aggregate_checkpoint 测试 ──


class TestAggregateCheckpoint:
    def test_normal_aggregation(self):
        """正常聚合 — active_files / recent_errors / tool_sequence 正确"""
        events = [
            _make_raw_event("Edit", file_path="/src/hooks.py", importance=0.6),
            _make_raw_event("Bash", importance=0.9, summary="TypeError in observe"),
            _make_raw_event("Read", file_path="/src/db_cog.py", importance=0.05),
            _make_raw_event("Edit", file_path="/src/signals.py", importance=0.4),
        ]
        db_cog = MagicMock()
        db_cog.get_recent_raw_events.return_value = events

        result = SessionContext.aggregate_checkpoint(db_cog, "session-1")

        assert "/src/hooks.py" in result["active_files"]
        assert "/src/db_cog.py" in result["active_files"]
        assert "/src/signals.py" in result["active_files"]
        assert len(result["recent_errors"]) == 1
        assert "TypeError" in result["recent_errors"][0]
        assert "Edit" in result["tool_sequence"]
        assert "Bash" in result["tool_sequence"]
        assert result["event_count"] == 4
        assert result["is_warm"] is True

    def test_empty_session(self):
        """空 session — 返回空结构，不崩溃"""
        db_cog = MagicMock()
        db_cog.get_recent_raw_events.return_value = []

        result = SessionContext.aggregate_checkpoint(db_cog, "session-empty")

        assert result["active_files"] == []
        assert result["recent_errors"] == []
        assert result["tool_sequence"] == []
        assert result["event_count"] == 0
        assert result["is_warm"] is False

    def test_none_db(self):
        """db_cog 为 None — 返回空结构"""
        result = SessionContext.aggregate_checkpoint(None, "session-1")
        assert result["is_warm"] is False
        assert result["event_count"] == 0

    def test_none_session_id(self):
        """session_id 为 None — 返回空结构"""
        result = SessionContext.aggregate_checkpoint(MagicMock(), None)
        assert result["is_warm"] is False

    def test_db_error_graceful(self):
        """DB 查询失败 — 静默降级"""
        db_cog = MagicMock()
        db_cog.get_recent_raw_events.side_effect = Exception("DB locked")

        result = SessionContext.aggregate_checkpoint(db_cog, "session-1")

        assert result["is_warm"] is False
        assert result["event_count"] == 0

    def test_errors_filtered_by_importance(self):
        """只有 importance >= 0.9 的事件进入 recent_errors"""
        events = [
            _make_raw_event("Edit", importance=0.6, summary="normal edit"),
            _make_raw_event("Bash", importance=0.8, summary="test passed"),
            _make_raw_event("Bash", importance=0.9, summary="fatal error"),
            _make_raw_event("Edit", importance=0.95, summary="traceback found"),
        ]
        db_cog = MagicMock()
        db_cog.get_recent_raw_events.return_value = events

        result = SessionContext.aggregate_checkpoint(db_cog, "s1")

        assert len(result["recent_errors"]) == 2
        assert "fatal error" in result["recent_errors"][0]
        assert "traceback" in result["recent_errors"][1]

    def test_tool_sequence_reversed(self):
        """tool_sequence 按时间正序（raw_events 默认 DESC，需 reverse）"""
        events = [
            _make_raw_event("Read"),   # 最新（DESC 排第一）
            _make_raw_event("Edit"),
            _make_raw_event("Bash"),   # 最旧
        ]
        db_cog = MagicMock()
        db_cog.get_recent_raw_events.return_value = events

        result = SessionContext.aggregate_checkpoint(db_cog, "s1")

        # reversed → Bash, Edit, Read（时间正序）
        assert result["tool_sequence"] == ["Bash", "Edit", "Read"]


# ── get_last_session_id 测试 ──


class TestGetLastSessionId:
    @pytest.fixture(autouse=True)
    def _setup_db(self, tmp_path):
        from index1.cognitive.db_cog import CogDatabase
        self.db = CogDatabase(tmp_path / "test_cog.db", embedding_dim=4)

    def test_no_sessions(self):
        """无历史 session — 返回 None"""
        db = self.db
        assert db.get_last_session_id("test-project") is None

    def test_returns_most_recent(self):
        """返回最近的 session"""
        db = self.db
        db.get_or_create_session("session-1", "test-project")
        db.get_or_create_session("session-2", "test-project")

        result = db.get_last_session_id("test-project")
        assert result == "session-2"

    def test_exclude_current(self):
        """排除当前 session 后返回上一个"""
        db = self.db
        db.get_or_create_session("session-1", "test-project")
        db.get_or_create_session("session-2", "test-project")

        result = db.get_last_session_id("test-project", exclude_current="session-2")
        assert result == "session-1"

    def test_exclude_only_session(self):
        """只有一个 session 且被排除 — 返回 None"""
        db = self.db
        db.get_or_create_session("session-1", "test-project")

        result = db.get_last_session_id("test-project", exclude_current="session-1")
        assert result is None

    def test_collection_isolation(self):
        """不同 collection 的 session 互不影响"""
        db = self.db
        db.get_or_create_session("s-alpha", "project-a")
        db.get_or_create_session("s-beta", "project-b")

        assert db.get_last_session_id("project-a") == "s-alpha"
        assert db.get_last_session_id("project-b") == "s-beta"
