//! Orphan-detection tests for the watchdog (bug 161).

use super::super::super::super::{AgentPool, composite_key};
use super::super::orphan::check_orphaned_agents;
use crate::agents::{AgentEvent, AgentStatus};

// ── check_orphaned_agents return value tests (bug 161) ──────────────────

#[tokio::test]
async fn check_orphaned_agents_returns_count_of_orphaned_agents() {
    let pool = AgentPool::new_test(3001);

    // Spawn two tasks that finish immediately.
    let h1 = tokio::spawn(async {});
    let h2 = tokio::spawn(async {});
    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
    assert!(h1.is_finished());
    assert!(h2.is_finished());

    pool.inject_test_agent_with_handle("story_a", "coder", AgentStatus::Running, h1);
    pool.inject_test_agent_with_handle("story_b", "coder", AgentStatus::Running, h2);

    let found = check_orphaned_agents(&pool.agents).await;
    assert_eq!(found, 2, "should detect both orphaned agents");
}

#[tokio::test]
async fn check_orphaned_agents_returns_zero_when_no_orphans() {
    let pool = AgentPool::new_test(3001);
    // Inject agents in terminal states — not orphaned.
    pool.inject_test_agent("story_a", "coder", AgentStatus::Completed);
    pool.inject_test_agent("story_b", "qa", AgentStatus::Failed);

    let found = check_orphaned_agents(&pool.agents).await;
    assert_eq!(
        found, 0,
        "no orphans should be detected for terminal agents"
    );
}

#[tokio::test]
async fn watchdog_detects_orphaned_running_agent() {
    let pool = AgentPool::new_test(3001);

    let handle = tokio::spawn(async {});
    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
    assert!(
        handle.is_finished(),
        "task should be finished before injection"
    );

    let tx =
        pool.inject_test_agent_with_handle("orphan_story", "coder", AgentStatus::Running, handle);
    let mut rx = tx.subscribe();

    pool.run_watchdog_once().await;

    {
        let agents = pool.agents.try_lock().unwrap();
        let key = composite_key("orphan_story", "coder");
        let agent = agents.get(&key).unwrap();
        assert_eq!(
            agent.status,
            AgentStatus::Failed,
            "watchdog must mark an orphaned Running agent as Failed"
        );
    }

    let event = rx.try_recv().expect("watchdog must emit an Error event");
    assert!(
        matches!(event, AgentEvent::Error { .. }),
        "expected AgentEvent::Error, got: {event:?}"
    );
}

#[tokio::test]
async fn watchdog_orphan_detection_returns_nonzero_enabling_auto_assign() {
    // This test verifies the contract that `check_orphaned_agents` returns
    // a non-zero count when orphans exist, which the watchdog uses to
    // decide whether to trigger auto-assign (bug 161).
    let pool = AgentPool::new_test(3001);

    let handle = tokio::spawn(async {});
    tokio::time::sleep(std::time::Duration::from_millis(20)).await;

    pool.inject_test_agent_with_handle("orphan_story", "coder", AgentStatus::Running, handle);

    // Before watchdog: agent is Running.
    {
        let agents = pool.agents.try_lock().unwrap();
        let key = composite_key("orphan_story", "coder");
        assert_eq!(agents.get(&key).unwrap().status, AgentStatus::Running);
    }

    // Run watchdog pass — should return 1 (orphan found).
    let found = check_orphaned_agents(&pool.agents).await;
    assert_eq!(
        found, 1,
        "watchdog must return 1 for a single orphaned agent"
    );

    // After watchdog: agent is Failed.
    {
        let agents = pool.agents.try_lock().unwrap();
        let key = composite_key("orphan_story", "coder");
        assert_eq!(
            agents.get(&key).unwrap().status,
            AgentStatus::Failed,
            "orphaned agent must be marked Failed"
        );
    }
}
