"""Tests for activity-based stall detection."""

import time
from unittest.mock import patch

from openjarvis.agents._stubs import AgentResult
from openjarvis.agents.executor import AgentExecutor
from openjarvis.agents.manager import AgentManager
from openjarvis.core.events import EventBus, EventType


def test_activity_tracking_updates_last_activity_at(tmp_path):
    """EventBus TOOL_CALL_START updates last_activity_at for the right agent."""
    mgr = AgentManager(str(tmp_path / "test.db"))
    bus = EventBus()
    executor = AgentExecutor(mgr, bus)

    agent = mgr.create_agent("stall-test")

    def fake_invoke(agent_dict):
        bus.publish(
            EventType.TOOL_CALL_START,
            {
                "agent": agent_dict["id"],
                "tool": "web_search",
            },
        )
        return AgentResult(content="done", metadata={})

    with patch.object(executor, "_invoke_agent", side_effect=fake_invoke):
        executor.execute_tick(agent["id"])

    updated = mgr.get_agent(agent["id"])
    assert updated["last_activity_at"] is not None
    assert updated["last_activity_at"] > 0
    mgr.close()


def test_activity_tracking_filters_by_agent_id(tmp_path):
    """Events from other agents don't update this agent's last_activity_at."""
    mgr = AgentManager(str(tmp_path / "test.db"))
    bus = EventBus()
    executor = AgentExecutor(mgr, bus)

    agent_a = mgr.create_agent("agent-a")
    agent_b = mgr.create_agent("agent-b")

    def fake_invoke(agent_dict):
        # Emit event for agent_b while agent_a is executing
        bus.publish(
            EventType.TOOL_CALL_START,
            {
                "agent": agent_b["id"],
                "tool": "web_search",
            },
        )
        return AgentResult(content="done", metadata={})

    with patch.object(executor, "_invoke_agent", side_effect=fake_invoke):
        executor.execute_tick(agent_a["id"])

    updated_b = mgr.get_agent(agent_b["id"])
    assert updated_b["last_activity_at"] is None
    mgr.close()


def test_reconcile_detects_stalled_agent(tmp_path):
    """_reconcile() marks agent as stalled when last_activity_at is too old."""
    mgr = AgentManager(str(tmp_path / "test.db"))
    bus = EventBus(record_history=True)
    executor = AgentExecutor(mgr, bus)
    from openjarvis.agents.scheduler import AgentScheduler

    scheduler = AgentScheduler(mgr, executor, event_bus=bus)

    agent = mgr.create_agent(
        "stall-me",
        config={
            "timeout_seconds": 10,
            "max_stall_retries": 3,
        },
    )
    mgr.update_agent(agent["id"], status="running", last_activity_at=time.time() - 30)

    scheduler._reconcile()

    updated = mgr.get_agent(agent["id"])
    assert updated["stall_retries"] == 1

    stall_events = [
        e for e in bus.history if e.event_type == EventType.AGENT_STALL_DETECTED
    ]
    assert len(stall_events) == 1
    mgr.close()


def test_reconcile_skips_active_agent(tmp_path):
    """_reconcile() does NOT mark agent as stalled if activity is recent."""
    mgr = AgentManager(str(tmp_path / "test.db"))
    bus = EventBus(record_history=True)
    executor = AgentExecutor(mgr, bus)
    from openjarvis.agents.scheduler import AgentScheduler

    scheduler = AgentScheduler(mgr, executor, event_bus=bus)

    agent = mgr.create_agent("active", config={"timeout_seconds": 10})
    mgr.update_agent(agent["id"], status="running", last_activity_at=time.time() - 2)

    scheduler._reconcile()

    updated = mgr.get_agent(agent["id"])
    assert updated["status"] == "running"
    mgr.close()


def test_reconcile_retries_exhausted_sets_error(tmp_path):
    """After max_stall_retries, agent goes to error status."""
    mgr = AgentManager(str(tmp_path / "test.db"))
    bus = EventBus()
    executor = AgentExecutor(mgr, bus)
    from openjarvis.agents.scheduler import AgentScheduler

    scheduler = AgentScheduler(mgr, executor, event_bus=bus)

    agent = mgr.create_agent(
        "exhausted",
        config={
            "timeout_seconds": 10,
            "max_stall_retries": 2,
        },
    )
    mgr.update_agent(
        agent["id"],
        status="running",
        last_activity_at=time.time() - 30,
        stall_retries=2,
    )

    scheduler._reconcile()

    updated = mgr.get_agent(agent["id"])
    assert updated["status"] == "error"
    mgr.close()
