//! Tests for basic coder selection and front-matter agent preference in
//! `AgentPool::start_agent`.

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

#[tokio::test]
async fn start_agent_auto_selects_second_coder_when_first_busy() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    std::fs::create_dir_all(&sk).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        r#"
[[agent]]
name = "supervisor"
stage = "other"

[[agent]]
name = "coder-1"
stage = "coder"

[[agent]]
name = "coder-2"
stage = "coder"
"#,
    )
    .unwrap();

    let pool = AgentPool::new_test(3001);
    pool.inject_test_agent("other-story", "coder-1", AgentStatus::Running);

    let result = pool
        .start_agent(tmp.path(), "42_my_story", None, None, None)
        .await;
    match result {
        Ok(info) => {
            assert_eq!(info.agent_name, "coder-2");
        }
        Err(err) => {
            assert!(
                !err.contains("All coder agents are busy"),
                "should have selected coder-2 but got: {err}"
            );
            assert!(
                !err.contains("No coder agent configured"),
                "should not fail on agent selection, got: {err}"
            );
        }
    }
}

#[tokio::test]
async fn start_agent_returns_busy_when_all_coders_occupied() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    std::fs::create_dir_all(&sk).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        r#"
[[agent]]
name = "coder-1"
stage = "coder"

[[agent]]
name = "coder-2"
stage = "coder"
"#,
    )
    .unwrap();

    let pool = AgentPool::new_test(3001);
    pool.inject_test_agent("story-1", "coder-1", AgentStatus::Running);
    pool.inject_test_agent("story-2", "coder-2", AgentStatus::Pending);

    let result = pool
        .start_agent(tmp.path(), "story-3", None, None, None)
        .await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(
        err.contains("All coder agents are busy"),
        "expected busy error, got: {err}"
    );
}

#[tokio::test]
async fn start_agent_moves_story_to_current_when_coders_busy() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    let backlog = sk.join("work/1_backlog");
    std::fs::create_dir_all(&backlog).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        r#"
[[agent]]
name = "coder-1"
stage = "coder"
"#,
    )
    .unwrap();
    let story_content = "---\nname: Story 3\n---\n";
    std::fs::write(backlog.join("story-3.md"), story_content).unwrap();
    crate::db::ensure_content_store();
    crate::db::write_content(crate::db::ContentKey::Story("story-3"), story_content);

    let pool = AgentPool::new_test(3001);
    pool.inject_test_agent("story-1", "coder-1", AgentStatus::Running);

    let result = pool
        .start_agent(tmp.path(), "story-3", None, None, None)
        .await;

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(
        err.contains("All coder agents are busy"),
        "expected busy error, got: {err}"
    );
    assert!(
        err.contains("queued in work/2_current/"),
        "expected story-to-current message, got: {err}"
    );

    // The lifecycle function updates the content store (not the filesystem),
    // so verify the move via the DB.
    let content = crate::db::read_content(crate::db::ContentKey::Story("story-3"))
        .expect("story-3 should be in content store after move to current");
    assert!(
        content.contains("name: Story 3"),
        "story-3 content should be preserved after move"
    );
}

#[tokio::test]
async fn start_agent_story_already_in_current_is_noop() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    let current = sk.join("work/2_current");
    std::fs::create_dir_all(&current).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        "[[agent]]\nname = \"coder-1\"\nstage = \"coder\"\n",
    )
    .unwrap();
    std::fs::write(current.join("story-5.md"), "---\nname: Story 5\n---\n").unwrap();

    let pool = AgentPool::new_test(3001);

    let result = pool
        .start_agent(tmp.path(), "story-5", None, None, None)
        .await;
    match result {
        Ok(_) => {}
        Err(e) => {
            assert!(
                !e.contains("Failed to move"),
                "should not fail on idempotent move, got: {e}"
            );
        }
    }
}

#[tokio::test]
async fn start_agent_explicit_name_unchanged_when_busy() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    std::fs::create_dir_all(&sk).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        r#"
[[agent]]
name = "coder-1"
stage = "coder"

[[agent]]
name = "coder-2"
stage = "coder"
"#,
    )
    .unwrap();

    let pool = AgentPool::new_test(3001);
    pool.inject_test_agent("story-1", "coder-1", AgentStatus::Running);

    let result = pool
        .start_agent(tmp.path(), "story-2", Some("coder-1"), None, None)
        .await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(
        err.contains("coder-1") && err.contains("already running"),
        "expected explicit busy error, got: {err}"
    );
}

// ── front matter agent preference (bug 379) ──────────────────────────────

#[tokio::test]
async fn start_agent_honours_front_matter_agent_when_idle() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    let backlog = sk.join("work/1_backlog");
    std::fs::create_dir_all(&backlog).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        r#"
[[agent]]
name = "coder-sonnet"
stage = "coder"

[[agent]]
name = "coder-opus"
stage = "coder"
"#,
    )
    .unwrap();
    // Story file with agent preference in front matter.
    std::fs::write(
        backlog.join("368_story_test.md"),
        "---\nname: Test Story\nagent: coder-opus\n---\n# Story 368\n",
    )
    .unwrap();

    let pool = AgentPool::new_test(3010);
    // coder-sonnet is busy so without front matter the auto-selection
    // would skip coder-opus and try something else.
    pool.inject_test_agent("other-story", "coder-sonnet", AgentStatus::Running);

    let result = pool
        .start_agent(tmp.path(), "368_story_test", None, None, None)
        .await;
    match result {
        Ok(info) => {
            assert_eq!(
                info.agent_name, "coder-opus",
                "should pick the front-matter preferred agent"
            );
        }
        Err(err) => {
            // Allowed to fail for infrastructure reasons (no git repo),
            // but NOT due to agent selection ignoring the preference.
            assert!(
                !err.contains("All coder agents are busy"),
                "should not report busy when coder-opus is idle: {err}"
            );
            assert!(
                !err.contains("coder-sonnet"),
                "should not have picked coder-sonnet: {err}"
            );
        }
    }
}

#[tokio::test]
async fn start_agent_returns_error_when_front_matter_agent_busy() {
    let tmp = tempfile::tempdir().unwrap();
    let sk = tmp.path().join(".huskies");
    let backlog = sk.join("work/1_backlog");
    let current = sk.join("work/2_current");
    std::fs::create_dir_all(&backlog).unwrap();
    std::fs::create_dir_all(&current).unwrap();
    std::fs::write(
        sk.join("project.toml"),
        r#"
[[agent]]
name = "coder-sonnet"
stage = "coder"

[[agent]]
name = "coder-opus"
stage = "coder"
"#,
    )
    .unwrap();
    let story_content = "---\nname: Test Story\nagent: coder-opus\n---\n# Story 368\n";
    std::fs::write(backlog.join("368_story_test.md"), story_content).unwrap();
    // Also write to the filesystem current dir and content store so that
    // start_agent reads the correct front matter even when another test has
    // left a stale entry for "368_story_test" in the global CRDT.
    std::fs::write(current.join("368_story_test.md"), story_content).unwrap();
    crate::db::ensure_content_store();
    crate::db::write_content(
        crate::db::ContentKey::Story("368_story_test"),
        story_content,
    );
    // Story 929: agent pin comes from the CRDT register, not YAML. Seed it.
    crate::crdt_state::init_for_test();
    crate::crdt_state::write_item_str(
        "368_story_test",
        "2_current",
        Some("Test Story"),
        Some("coder-opus"),
        None,
        None,
    );

    let pool = AgentPool::new_test(3011);
    // Preferred agent is busy — should NOT fall back to coder-sonnet.
    pool.inject_test_agent("other-story", "coder-opus", AgentStatus::Running);

    let result = pool
        .start_agent(tmp.path(), "368_story_test", None, None, None)
        .await;
    assert!(
        result.is_err(),
        "expected error when preferred agent is busy"
    );
    let err = result.unwrap_err();
    assert!(
        err.contains("coder-opus"),
        "error should mention the preferred agent: {err}"
    );
    assert!(
        err.contains("busy") || err.contains("queued"),
        "error should say agent is busy or story is queued: {err}"
    );
}
