"""Tests for VidaAgent — 24/7 持久化 Agent Daemon operator。"""

from __future__ import annotations

import asyncio
import json
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock

from sage.vida.vida_agent import VidaAgent, VidaResult


def _make_memory_bridge() -> MagicMock:
    bridge = MagicMock()
    bridge.store_working = AsyncMock(return_value="wm_1")
    bridge.recall_working = AsyncMock(return_value=[{"id": "w1", "text": "hello"}])
    bridge.recall_episodic = AsyncMock(return_value=[{"id": "e1", "text": "past"}])
    bridge.recall_semantic = AsyncMock(return_value=[{"id": "s1", "text": "knowledge"}])
    bridge.store_episodic = AsyncMock(return_value="ep_1")
    bridge.persist_all = AsyncMock(
        return_value={
            "working_memory": True,
            "episodic_memory": True,
            "semantic_memory": True,
        }
    )
    bridge.close = AsyncMock(return_value=None)
    return bridge


def _make_react_loop(answer: str = "这是回答") -> MagicMock:
    loop = MagicMock()
    loop.run_with_tools = AsyncMock(return_value=answer)
    return loop


def _make_reflection_engine() -> MagicMock:
    engine = MagicMock()
    engine.start = AsyncMock(return_value=None)
    engine.stop = AsyncMock(return_value=None)
    engine.reflect = AsyncMock(return_value=["洞察 1"])
    return engine


def _create_agent(
    tmp_path: Path, *, with_reflection: bool = True
) -> tuple[VidaAgent, MagicMock, MagicMock, MagicMock | None]:
    bridge = _make_memory_bridge()
    react_loop = _make_react_loop()
    reflection = _make_reflection_engine() if with_reflection else None
    state_file = tmp_path / "vida_state.json"
    agent = VidaAgent(
        react_loop=react_loop,
        memory_bridge=bridge,
        reflection_engine=reflection,
        config={
            "state_file": str(state_file),
            "working_memory_capacity": 2,
        },
    )
    return agent, bridge, react_loop, reflection


class TestVidaAgentLifecycle:
    """生命周期相关测试。"""

    def test_start_and_shutdown(self, tmp_path: Path):
        agent, bridge, _, reflection = _create_agent(tmp_path)

        async def _run() -> None:
            await agent.start()
            assert agent.is_running
            assert agent.is_accepting
            if reflection is not None:
                reflection.start.assert_awaited_once()

            await agent.shutdown(drain=True)
            assert not agent.is_accepting
            bridge.persist_all.assert_awaited()
            bridge.close.assert_awaited_once()
            if reflection is not None:
                reflection.stop.assert_awaited_once()

        asyncio.run(_run())


class TestVidaAgentProcessing:
    """消息处理主流程测试。"""

    def test_ask_returns_result_and_writes_memory(self, tmp_path: Path):
        agent, bridge, react_loop, reflection = _create_agent(tmp_path)

        async def _run() -> None:
            await agent.start()
            result = await agent.ask(user_id="u1", content="你好")
            assert isinstance(result, VidaResult)
            assert result.ok
            assert result.answer == "这是回答"

            react_loop.run_with_tools.assert_awaited_once()
            assert bridge.store_working.await_count >= 2
            bridge.store_episodic.assert_awaited_once()
            if reflection is not None:
                reflection.reflect.assert_awaited_once()

            await agent.shutdown(drain=True)

        asyncio.run(_run())

    def test_wait_result_for_submitted_message(self, tmp_path: Path):
        agent, _, _, _ = _create_agent(tmp_path)

        async def _run() -> None:
            await agent.start()
            message_id = await agent.submit(user_id="u2", content="问题")
            result = await agent.wait_result(message_id, timeout=3.0)
            assert result.message_id == message_id
            assert result.ok
            await agent.shutdown(drain=True)

        asyncio.run(_run())

    def test_submit_when_not_accepting_raises(self, tmp_path: Path):
        agent, _, _, _ = _create_agent(tmp_path)

        async def _run() -> None:
            try:
                await agent.submit(user_id="u", content="x")
                raise AssertionError("expected RuntimeError")
            except RuntimeError:
                pass

        asyncio.run(_run())

    def test_handle_event_uses_unified_payload(self, tmp_path: Path):
        agent, _, _, _ = _create_agent(tmp_path)

        class _Event:
            event_type = "interval"
            source = "timer"
            payload = {"user_id": "u_event", "content": "来自事件", "timeout": 2}

        async def _run() -> None:
            await agent.start()
            result = await agent.handle_event(_Event())
            assert result.ok
            assert result.answer == "这是回答"
            await agent.shutdown(drain=True)

        asyncio.run(_run())


class TestVidaAgentRecovery:
    """崩溃恢复测试。"""

    def test_recover_pending_and_inflight_from_state_file(self, tmp_path: Path):
        state_file = tmp_path / "vida_state.json"
        payload = {
            "version": 1,
            "pending": [
                {
                    "message_id": "m_pending",
                    "user_id": "u1",
                    "content": "pending question",
                    "metadata": {},
                    "created_at": 1.0,
                }
            ],
            "inflight": {
                "message_id": "m_inflight",
                "user_id": "u2",
                "content": "inflight question",
                "metadata": {},
                "created_at": 2.0,
            },
            "processed_count": 3,
            "failed_count": 1,
            "working_writes_since_reflect": 0,
            "saved_at": 10.0,
        }
        state_file.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")

        bridge = _make_memory_bridge()
        react_loop = _make_react_loop(answer="恢复回答")
        agent = VidaAgent(
            react_loop=react_loop,
            memory_bridge=bridge,
            reflection_engine=None,
            config={"state_file": str(state_file)},
        )

        async def _run() -> None:
            await agent.start()
            assert agent.pending_count == 2
            assert agent.processed_count >= 3

            await agent.shutdown(drain=True)
            assert agent.processed_count >= 5

        asyncio.run(_run())

    def test_shutdown_persists_state_file(self, tmp_path: Path):
        state_file = tmp_path / "vida_state.json"
        agent, _, _, _ = _create_agent(tmp_path, with_reflection=False)

        async def _run() -> None:
            await agent.start()
            await agent.submit(user_id="u3", content="保存状态")
            await agent.shutdown(drain=False)

        asyncio.run(_run())

        assert state_file.exists()
        raw = json.loads(state_file.read_text(encoding="utf-8"))
        assert raw["version"] == 1
        assert "pending" in raw
