"""Issue #260: awaken AI-native 结构化注入测试

测试 awaken() 从 session_record fact 注入 [prev_session] 结构化标签，
磁盘兜底 [disk_recovery]，以及 reflect 磁盘持久化。
"""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from index1.cognitive.db_cog import CogDatabase
from index1.cognitive.hooks import HookHandler


@pytest.fixture(autouse=True)
def _isolate_hooks_state(tmp_path, monkeypatch):
    """隔离 INDEX1_HOME 到 tmp_path"""
    monkeypatch.setattr(
        "index1.cognitive.hooks.INDEX1_HOME", tmp_path / ".claude-index1",
    )


@pytest.fixture()
def index1_home(tmp_path) -> Path:
    home = tmp_path / ".claude-index1"
    home.mkdir(parents=True, exist_ok=True)
    return home


@pytest.fixture()
def cog_db(tmp_path: Path) -> CogDatabase:
    db = CogDatabase(tmp_path / "test_awaken_structured.db", embedding_dim=4)
    db.connect()
    yield db
    db.close()


@pytest.fixture()
def handler(cog_db: CogDatabase) -> HookHandler:
    from index1.config import Config
    config = Config(observer_enabled=False)
    return HookHandler(cog_db, config=config)


def _base_data(**overrides) -> dict:
    data = {"cwd": "/home/user/my-project", "session_id": "test-session-2"}
    data.update(overrides)
    return data


# ── Task 1: session_record 结构化注入 ──


class TestSessionRecordInjection:
    """awaken 从 session_record fact 注入 [prev_session] 结构化标签"""

    def test_prev_session_tag_injected(self, handler, cog_db):
        """session_record 存在时，注入 [prev_session] 标签"""
        cog_db.insert_fact({
            "collection": "my-project",
            "assertion": "Session record for previous session",
            "category": "session_record",
            "confidence": 1.0,
            "session_id": "test-session-1",
            "context_json": json.dumps({
                "session_record": {
                    "files_changed": ["src/hooks.py", "src/db.py"],
                    "key_changes": [
                        {"action": "fix", "identifiers": ["_conn", "conn"]},
                    ],
                    "patterns": ["error-fix", "refactor"],
                    "outcome": "all tests passed",
                    "incomplete_work": [],
                },
            }),
        })

        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "[prev_session]" in output
        assert "[/prev_session]" in output
        assert "hooks.py" in output  # Path.name 提取
        assert "fix:_conn,conn" in output
        assert "error-fix" in output
        assert "all tests passed" in output

    def test_no_prev_session_when_empty(self, handler, cog_db):
        """无 session_record 时不注入 [prev_session]"""
        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "[prev_session]" not in output

    def test_incomplete_work_injected(self, handler, cog_db):
        """incomplete_work 字段被注入"""
        cog_db.insert_fact({
            "collection": "my-project",
            "assertion": "Session record with incomplete work",
            "category": "session_record",
            "confidence": 1.0,
            "session_id": "test-session-1",
            "context_json": json.dumps({
                "session_record": {
                    "files_changed": ["src/main.py"],
                    "key_changes": [],
                    "patterns": [],
                    "outcome": "partial",
                    "incomplete_work": ["fix auth bug", "add tests"],
                },
            }),
        })

        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "incomplete:" in output
        assert "fix auth bug" in output

    def test_l1_type_validation(self, handler, cog_db):
        """L1: 非法类型字段不会导致崩溃"""
        cog_db.insert_fact({
            "collection": "my-project",
            "assertion": "Malformed session record",
            "category": "session_record",
            "confidence": 1.0,
            "session_id": "test-session-1",
            "context_json": json.dumps({
                "session_record": {
                    "files_changed": "not-a-list",  # 应该是 list
                    "key_changes": 42,  # 应该是 list
                    "patterns": None,  # 应该是 list
                    "outcome": ["not-a-string"],  # 应该是 str
                },
            }),
        })

        # 不崩溃，优雅降级
        result = handler.awaken(_base_data())
        output = result.get("output", "")
        # 因为所有字段都是非法类型，不应有 prev_session 标签
        # （或者有标签但内容为空）
        assert "output" in result


# ── Task 2+3: 磁盘兜底 ──


class TestDiskFallback:
    """awaken 从 .last_session_context 磁盘文件恢复"""

    def test_disk_recovery_when_no_session_record(
        self, handler, cog_db, index1_home,
    ):
        """无 session_record 时从磁盘文件恢复"""
        context_file = index1_home / ".last_session_context"
        context_file.write_text(json.dumps({
            "collection": "my-project",
            "session_id": "prev-session",
            "active_files": ["src/hooks.py", "src/db.py"],
            "recent_errors": ["TypeError: NoneType"],
            "event_count": 15,
        }), encoding="utf-8")

        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "[disk_recovery]" in output
        assert "[/disk_recovery]" in output
        assert "hooks.py" in output
        assert "TypeError" in output

    def test_disk_recovery_skipped_when_session_record_exists(
        self, handler, cog_db, index1_home,
    ):
        """session_record 存在时不读磁盘文件"""
        # 先插入 session_record
        cog_db.insert_fact({
            "collection": "my-project",
            "assertion": "Session record exists",
            "category": "session_record",
            "confidence": 1.0,
            "session_id": "test-session-1",
            "context_json": json.dumps({
                "session_record": {
                    "files_changed": ["src/main.py"],
                    "key_changes": [],
                    "patterns": [],
                    "outcome": "ok",
                },
            }),
        })

        # 磁盘文件也存在
        context_file = index1_home / ".last_session_context"
        context_file.write_text(json.dumps({
            "collection": "my-project",
            "session_id": "prev-session",
            "active_files": ["old_file.py"],
            "recent_errors": [],
            "event_count": 5,
        }), encoding="utf-8")

        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "[prev_session]" in output
        assert "[disk_recovery]" not in output

    def test_disk_recovery_wrong_collection_ignored(
        self, handler, cog_db, index1_home,
    ):
        """磁盘文件的 collection 不匹配时忽略"""
        context_file = index1_home / ".last_session_context"
        context_file.write_text(json.dumps({
            "collection": "other-project",  # 不匹配
            "session_id": "prev-session",
            "active_files": ["src/other.py"],
            "recent_errors": [],
            "event_count": 10,
        }), encoding="utf-8")

        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "[disk_recovery]" not in output

    def test_disk_recovery_empty_file_ignored(
        self, handler, cog_db, index1_home,
    ):
        """空文件被忽略（L1 文件大小校验）"""
        context_file = index1_home / ".last_session_context"
        context_file.write_text("", encoding="utf-8")

        result = handler.awaken(_base_data())
        output = result.get("output", "")

        assert "[disk_recovery]" not in output


# ── Task 2: reflect 磁盘持久化 ──


class TestReflectDiskPersist:
    """reflect 写 .last_session_context 磁盘兜底文件"""

    def test_reflect_writes_disk_context(
        self, handler, cog_db, index1_home, monkeypatch,
    ):
        """reflect 正常写入磁盘兜底文件"""
        # 插入一些 raw_events 模拟有活动的 session
        cog_db.insert_raw_event(
            session_id="test-session-2",
            hook_event="PostToolUse",
            collection="my-project",
            payload={"tool_input": {"file_path": "src/main.py"},
                     "tool_response": "File written"},
            tool_name="Write",
            importance=0.7,
        )

        # Mock maintenance 子进程启动，不实际启动
        monkeypatch.setattr(
            handler, "_launch_maintenance", lambda *a, **kw: None,
        )

        handler.reflect(_base_data())

        context_file = index1_home / ".last_session_context"
        assert context_file.exists()

        data = json.loads(context_file.read_text(encoding="utf-8"))
        assert data["collection"] == "my-project"
        assert data["session_id"] == "test-session-2"
        assert isinstance(data["active_files"], list)

    def test_reflect_skips_inactive_session(
        self, handler, cog_db, index1_home, monkeypatch,
    ):
        """无活动的 session 不写磁盘文件（is_warm=False）"""
        monkeypatch.setattr(
            handler, "_launch_maintenance", lambda *a, **kw: None,
        )

        # 不插入任何 raw_events → aggregate_checkpoint 返回 is_warm=False
        handler.reflect(_base_data())

        context_file = index1_home / ".last_session_context"
        assert not context_file.exists()

    def test_reflect_atomic_write(
        self, handler, cog_db, index1_home, monkeypatch,
    ):
        """写入使用 atomic rename（tmp 文件不残留）"""
        cog_db.insert_raw_event(
            session_id="test-session-2",
            hook_event="PostToolUse",
            collection="my-project",
            payload={"tool_input": {"file_path": "src/app.py"},
                     "tool_response": "File edited"},
            tool_name="Edit",
            importance=0.6,
        )

        monkeypatch.setattr(
            handler, "_launch_maintenance", lambda *a, **kw: None,
        )

        handler.reflect(_base_data())

        tmp_file = index1_home / ".last_session_context.tmp"
        assert not tmp_file.exists()  # tmp 不残留

        target = index1_home / ".last_session_context"
        assert target.exists()  # target 存在
