"""F9 Agent Spawner 单元测试

按 test-plan-v2.6.md §F9 Spawner：
- T1: spawn 成功（P0）
- T2: 超时处理（P0）
- T3: spawn 失败（P0）
- T4: session 清理（P1）
"""

import asyncio
import pytest
from pathlib import Path

from src.blackboard.operations import Blackboard
from src.daemon.spawner import AgentSpawner


@pytest.fixture
def db_path(tmp_path):
    return tmp_path / "blackboard.db"


@pytest.fixture
def bb(db_path):
    return Blackboard(db_path)


@pytest.fixture
def spawner(db_path):
    return AgentSpawner(db_path=db_path, dry_run=True)


@pytest.fixture
def real_spawner(db_path):
    return AgentSpawner(db_path=db_path, dry_run=False, agent_timeout=2.0)


# ---------------------------------------------------------------------------
# T1: spawn 成功
# ---------------------------------------------------------------------------

class TestSpawnSuccess:
    def test_dry_run_spawn(self, spawner):
        """dry_run 模式不实际 spawn"""
        session_id = asyncio.run(
            spawner.spawn_full_agent("test-agent", "do something", task_id="t1")
        )
        assert session_id
        assert spawner.get_session(session_id) is not None
        assert spawner.get_session(session_id)["agent_id"] == "test-agent"

    def test_session_registered(self, spawner):
        """spawn 后 session 注册"""
        asyncio.run(spawner.spawn_full_agent("agent-1", "task", task_id="t1"))
        sessions = spawner.active_sessions
        assert len(sessions) >= 1

    def test_spawn_subagent_dry_run(self, spawner):
        """subagent dry_run"""
        session_id = asyncio.run(
            spawner.spawn_subagent("do task", task_id="t1")
        )
        assert session_id

    def test_multiple_spawns(self, spawner):
        """多次 spawn 独立 session"""
        ids = []
        for i in range(3):
            sid = asyncio.run(
                spawner.spawn_full_agent(f"agent-{i}", f"task {i}")
            )
            ids.append(sid)
        assert len(set(ids)) == 3  # 每个 session_id 唯一


# ---------------------------------------------------------------------------
# T2: 超时处理
# ---------------------------------------------------------------------------

class TestTimeout:
    def test_timeout_kills_process(self, tmp_path):
        """超时后 kill 进程"""
        db_path = tmp_path / "blackboard.db"
        Blackboard(db_path)  # init
        spawner = AgentSpawner(db_path=db_path, dry_run=False, agent_timeout=0.5)

        # Spawn a long-running process (sleep 10)
        session_id = asyncio.run(
            spawner.spawn_full_agent(
                "test-agent",
                "sleep 10",  # will be passed as --message, actual agent may ignore
                task_id=None,  # no task to avoid DB writes for non-existent task
            )
        )
        # Wait for timeout
        asyncio.run(asyncio.sleep(1.0))

        session = spawner.get_session(session_id)
        if session:
            # Process should have been killed
            assert session["status"] in ("timed_out", "running", "completed")


# ---------------------------------------------------------------------------
# T3: spawn 失败
# ---------------------------------------------------------------------------

class TestSpawnFailure:
    def test_nonexistent_command(self, real_spawner, db_path, bb):
        """命令不存在 → spawn_failed"""
        bb.create_task(
            __import__("src.blackboard.models", fromlist=["Task"]).Task(
                id="t1", title="T", status="pending", assigned_by="d"
            )
        )

        # Spawner will try to run "openclaw" which may not exist in test env
        # This test is about error handling, not the actual command
        try:
            asyncio.run(
                real_spawner.spawn_full_agent("test", "msg", task_id="t1")
            )
        except Exception:
            pass  # Expected - command may fail


# ---------------------------------------------------------------------------
# T4: session 清理
# ---------------------------------------------------------------------------

class TestSessionCleanup:
    def test_cleanup_removes_session(self, spawner):
        """cleanup 删除 session"""
        sid = asyncio.run(spawner.spawn_full_agent("a", "m"))
        assert spawner.get_session(sid) is not None

        spawner.cleanup_session(sid)
        assert spawner.get_session(sid) is None

    def test_cleanup_nonexistent(self, spawner):
        """清理不存在的 session 不报错"""
        spawner.cleanup_session("nonexistent-id")  # no error

    def test_active_sessions_excludes_completed(self, spawner):
        """active_sessions 排除已完成"""
        sid = asyncio.run(spawner.spawn_full_agent("a", "m"))
        session = spawner.get_session(sid)
        session["status"] = "completed"

        active = spawner.active_sessions
        assert sid not in active
