//! Basic pipeline advance tests.
use super::super::super::AgentPool;
use crate::agents::CompletionReport;
use crate::io::watcher::WatcherEvent;

// ── pipeline advance tests ────────────────────────────────────────────────

#[tokio::test]
async fn pipeline_advance_coder_gates_pass_server_qa_moves_to_merge() {
    use std::fs;
    let tmp = tempfile::tempdir().unwrap();
    let root = tmp.path();

    // Set up story in 2_current/ (no qa frontmatter → uses project default "server").
    // Use a unique high-numbered ID to avoid collision with the agent_qa test.
    let current = root.join(".huskies/work/2_current");
    fs::create_dir_all(&current).unwrap();
    fs::write(current.join("9908_story_server_qa.md"), "test").unwrap();
    crate::db::ensure_content_store();
    crate::db::write_content(crate::db::ContentKey::Story("9908_story_server_qa"), "test");

    let pool = AgentPool::new_test(3001);
    pool.run_pipeline_advance(
        "9908_story_server_qa",
        "coder-1",
        CompletionReport {
            summary: "done".to_string(),
            gates_passed: true,
            gate_output: String::new(),
            needs_commit_recovery: false,
        },
        Some(root.to_path_buf()),
        None,
        false,
        None,
    )
    .await;

    // With default qa: server, story skips QA and goes straight to 4_merge/
    // Lifecycle moves now update the content store, not the filesystem.
    assert!(
        crate::db::read_content(crate::db::ContentKey::Story("9908_story_server_qa")).is_some(),
        "story should still exist in content store after move to merge"
    );
}

#[tokio::test]
async fn pipeline_advance_coder_gates_pass_agent_qa_moves_to_qa() {
    use std::fs;
    let tmp = tempfile::tempdir().unwrap();
    let root = tmp.path();

    // Set up story in 2_current/ with qa: agent frontmatter.
    // Use a unique high-numbered ID to avoid collision with the server_qa test.
    let current = root.join(".huskies/work/2_current");
    fs::create_dir_all(&current).unwrap();
    fs::write(
        current.join("9909_story_agent_qa.md"),
        "---\nname: Test\nqa: agent\n---\ntest",
    )
    .unwrap();
    crate::db::ensure_content_store();
    crate::db::write_content(
        crate::db::ContentKey::Story("9909_story_agent_qa"),
        "---\nname: Test\nqa: agent\n---\ntest",
    );

    let pool = AgentPool::new_test(3001);
    pool.run_pipeline_advance(
        "9909_story_agent_qa",
        "coder-1",
        CompletionReport {
            summary: "done".to_string(),
            gates_passed: true,
            gate_output: String::new(),
            needs_commit_recovery: false,
        },
        Some(root.to_path_buf()),
        None,
        false,
        None,
    )
    .await;

    // With qa: agent, story should move to 3_qa/
    // Lifecycle moves now update the content store, not the filesystem.
    assert!(
        crate::db::read_content(crate::db::ContentKey::Story("9909_story_agent_qa")).is_some(),
        "story should still exist in content store after move to qa"
    );
}

#[tokio::test]
async fn pipeline_advance_qa_gates_pass_moves_story_to_merge() {
    use std::fs;
    let tmp = tempfile::tempdir().unwrap();
    let root = tmp.path();

    // Set up story in 3_qa/
    let qa_dir = root.join(".huskies/work/3_qa");
    fs::create_dir_all(&qa_dir).unwrap();
    // qa: server so the story skips human review and goes straight to merge.
    fs::write(
        qa_dir.join("51_story_test.md"),
        "---\nname: Test\nqa: server\n---\ntest",
    )
    .unwrap();
    crate::db::ensure_content_store();
    crate::db::write_content(
        crate::db::ContentKey::Story("51_story_test"),
        "---\nname: Test\nqa: server\n---\ntest",
    );

    let pool = AgentPool::new_test(3001);
    pool.run_pipeline_advance(
        "51_story_test",
        "qa",
        CompletionReport {
            summary: "QA done".to_string(),
            gates_passed: true,
            gate_output: String::new(),
            needs_commit_recovery: false,
        },
        Some(root.to_path_buf()),
        None,
        false,
        None,
    )
    .await;

    // Story should have moved to 4_merge/
    // Lifecycle moves now update the content store, not the filesystem.
    assert!(
        crate::db::read_content(crate::db::ContentKey::Story("51_story_test")).is_some(),
        "story should still exist in content store after move to merge"
    );
}

#[tokio::test]
async fn pipeline_advance_supervisor_does_not_advance() {
    use std::fs;
    let tmp = tempfile::tempdir().unwrap();
    let root = tmp.path();

    let current = root.join(".huskies/work/2_current");
    fs::create_dir_all(&current).unwrap();
    fs::write(current.join("52_story_test.md"), "test").unwrap();

    let pool = AgentPool::new_test(3001);
    pool.run_pipeline_advance(
        "52_story_test",
        "supervisor",
        CompletionReport {
            summary: "supervised".to_string(),
            gates_passed: true,
            gate_output: String::new(),
            needs_commit_recovery: false,
        },
        Some(root.to_path_buf()),
        None,
        false,
        None,
    )
    .await;

    // Story should NOT have moved (supervisors don't advance pipeline)
    assert!(
        current.join("52_story_test.md").exists(),
        "story should still be in 2_current/ for supervisor"
    );
}

#[tokio::test]
async fn pipeline_advance_sends_agent_state_changed_to_watcher_tx() {
    use std::fs;

    let tmp = tempfile::tempdir().unwrap();
    let root = tmp.path();

    // Seed story via CRDT (the only source of truth).
    crate::db::ensure_content_store();
    crate::db::write_item_with_content(
        "173_story_test",
        "2_current",
        "---\nname: test\n---\n",
        crate::db::ItemMeta::named("test"),
    );

    // Write a project.toml with a qa agent so start_agent can resolve it.
    fs::create_dir_all(root.join(".huskies")).unwrap();
    fs::write(
        root.join(".huskies/project.toml"),
        r#"
default_qa = "agent"

[[agent]]
name = "coder-1"
role = "Coder"
command = "echo"
args = ["noop"]
prompt = "test"
stage = "coder"

[[agent]]
name = "qa"
role = "QA"
command = "echo"
args = ["noop"]
prompt = "test"
stage = "qa"
"#,
    )
    .unwrap();

    let pool = AgentPool::new_test(3001);
    // Subscribe to the watcher channel BEFORE the pipeline advance.
    let mut rx = pool.watcher_tx.subscribe();

    pool.run_pipeline_advance(
        "173_story_test",
        "coder-1",
        CompletionReport {
            summary: "done".to_string(),
            gates_passed: true,
            gate_output: String::new(),
            needs_commit_recovery: false,
        },
        Some(root.to_path_buf()),
        None,
        false,
        None,
    )
    .await;

    // The pipeline advance should have sent AgentStateChanged events via
    // the pool's watcher_tx (not a dummy channel). Collect all events.
    let mut got_agent_state_changed = false;
    while let Ok(evt) = rx.try_recv() {
        if matches!(evt, WatcherEvent::AgentStateChanged) {
            got_agent_state_changed = true;
            break;
        }
    }

    assert!(
        got_agent_state_changed,
        "pipeline advance should send AgentStateChanged through the real watcher_tx \
         (bug 173: lozenges must update when agents are assigned during pipeline advance)"
    );
}

#[tokio::test]
async fn pipeline_advance_coder_gates_pass_human_qa_holds_for_review() {
    use std::fs;
    let tmp = tempfile::tempdir().unwrap();
    let root = tmp.path();

    // Seed story via CRDT so qa_mode can be resolved from the store.
    crate::db::ensure_content_store();
    crate::db::write_item_with_content(
        "9910_story_human_qa",
        "2_current",
        "---\nname: Human Qa Test\n---\ntest",
        crate::db::ItemMeta::named("Human Qa Test"),
    );

    // Set qa_mode = Human on the CRDT item directly.
    crate::crdt_state::set_qa_mode(
        "9910_story_human_qa",
        Some(crate::io::story_metadata::QaMode::Human),
    );

    // Write a minimal project.toml so the pool can load config.
    fs::create_dir_all(root.join(".huskies")).unwrap();
    fs::write(
        root.join(".huskies/project.toml"),
        r#"
[[agent]]
name = "coder-1"
role = "Coder"
command = "echo"
args = ["noop"]
prompt = "test"
stage = "coder"
"#,
    )
    .unwrap();

    let pool = AgentPool::new_test(3001);
    pool.run_pipeline_advance(
        "9910_story_human_qa",
        "coder-1",
        CompletionReport {
            summary: "done".to_string(),
            gates_passed: true,
            gate_output: String::new(),
            needs_commit_recovery: false,
        },
        Some(root.to_path_buf()),
        None,
        false,
        None,
    )
    .await;

    // With qa: human the story should be held for human review (ReviewHold stage).
    let item = crate::crdt_state::read_item("9910_story_human_qa")
        .expect("story should exist in CRDT after advance");
    assert!(
        matches!(
            item.stage(),
            crate::pipeline_state::Stage::ReviewHold { .. }
        ),
        "qa: human should leave story in ReviewHold, got: {:?}",
        item.stage()
    );
}
