//! Agent-task command from-spec dispatch defaults and controller dispatch arg tests.

use super::support::*;
use clap::Parser;

use crate::cli_surface::{Cli, Commands};

use super::super::AgentTaskCommand;

#[test]
fn from_spec_dispatch_defaults_use_spec_git_checkout() {
    let repo = tempfile::tempdir().expect("repo dir");
    let git_status = Command::new("git")
        .arg("-C")
        .arg(repo.path())
        .arg("init")
        .status()
        .expect("git init runs");
    assert!(git_status.success());
    let spec_dir = repo.path().join(".github/homeboy/controllers");
    std::fs::create_dir_all(&spec_dir).expect("spec dir");
    let spec_path = spec_dir.join("loop.json");
    std::fs::write(&spec_path, "{}").expect("spec file");
    let mut spec = AgentTaskRepoLoopSpec {
        schema: None,
        loop_id: "repo-loop-cli-defaults".to_string(),
        phase: "init".to_string(),
        config_version: "v1".to_string(),
        metadata: Value::Null,
        entities: Vec::new(),
        agents: Vec::new(),
        tools: Vec::new(),
        abilities: Vec::new(),
        workflows: Vec::new(),
        artifacts: Vec::new(),
        artifact_graph: Vec::new(),
        dependencies: Vec::new(),
        gates: Vec::new(),
        metrics: Vec::new(),
        gate_bundles: Vec::new(),
        policy: None,
        phases: Vec::new(),
        actions: Vec::new(),
        initial_event: None,
    };

    apply_from_spec_dispatch_defaults(&mut spec, &format!("@{}", spec_path.display()));
    let expected_root = std::fs::canonicalize(repo.path()).expect("canonical repo path");

    assert_eq!(
        spec.metadata["dispatch_defaults"]["cwd"],
        expected_root.display().to_string()
    );
    assert_eq!(
        spec.metadata["dispatch_defaults"]["repo"],
        repo.path()
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_string()
    );
}

#[test]
fn from_spec_dispatch_defaults_fall_back_to_current_git_checkout() {
    let repo = tempfile::tempdir().expect("repo dir");
    let git_status = Command::new("git")
        .arg("-C")
        .arg(repo.path())
        .arg("init")
        .status()
        .expect("git init runs");
    assert!(git_status.success());
    let mut spec = AgentTaskRepoLoopSpec {
        schema: None,
        loop_id: "repo-loop-cli-cwd-defaults".to_string(),
        phase: "init".to_string(),
        config_version: "v1".to_string(),
        metadata: Value::Null,
        entities: Vec::new(),
        agents: Vec::new(),
        tools: Vec::new(),
        abilities: Vec::new(),
        workflows: Vec::new(),
        artifacts: Vec::new(),
        artifact_graph: Vec::new(),
        dependencies: Vec::new(),
        gates: Vec::new(),
        metrics: Vec::new(),
        gate_bundles: Vec::new(),
        policy: None,
        phases: Vec::new(),
        actions: Vec::new(),
        initial_event: None,
    };
    spec.workflows.push(
        homeboy::core::agent_tasks::controller_service::AgentTaskRepoLoopSpecWorkflow {
            workflow_id: "store-idea".to_string(),
            agent_id: None,
            prompt: Some("cook the next workflow".to_string()),
            tasks: Vec::new(),
            entity_ids: Vec::new(),
            fan_out: None,
            tools: Vec::new(),
            abilities: Vec::new(),
            artifacts: Vec::new(),
            consumes: Vec::new(),
            emits: Vec::new(),
            dependencies: Vec::new(),
            gates: Vec::new(),
            metrics: Vec::new(),
            runtime_execution: Value::Null,
            inputs: Value::Null,
        },
    );

    apply_from_spec_dispatch_defaults_with_cwd(&mut spec, "-", || Some(repo.path().to_path_buf()));
    let expected_root = std::fs::canonicalize(repo.path()).expect("canonical repo path");
    let expected_repo = repo
        .path()
        .file_name()
        .unwrap()
        .to_string_lossy()
        .to_string();

    assert_eq!(
        spec.metadata["dispatch_defaults"]["cwd"],
        expected_root.display().to_string()
    );
    assert_eq!(spec.metadata["dispatch_defaults"]["repo"], expected_repo);

    with_isolated_home(|_| {
        let report = agent_task_controller_service::init_from_spec(ControllerFromSpecRequest {
            spec: spec.clone(),
        })
        .expect("from-spec initialized");
        match &report.actions[0].action {
            AgentTaskLoopPolicyAction::SpawnTask { request, .. } => {
                assert_eq!(
                    request["dispatch"]["cwd"].as_str(),
                    Some(expected_root.display().to_string().as_str())
                );
                assert_eq!(
                    request["dispatch"]["repo"].as_str(),
                    Some(expected_repo.as_str())
                );
            }
            other => panic!("expected compiled spawn task, got {other:?}"),
        }
    });
}

#[test]
fn from_spec_dispatch_defaults_replace_stale_workspace_cwd() {
    let repo = tempfile::tempdir().expect("repo dir");
    let git_status = Command::new("git")
        .arg("-C")
        .arg(repo.path())
        .arg("init")
        .status()
        .expect("git init runs");
    assert!(git_status.success());
    let mut spec = AgentTaskRepoLoopSpec {
        schema: None,
        loop_id: "repo-loop-stale-cwd-defaults".to_string(),
        phase: "init".to_string(),
        config_version: "v1".to_string(),
        metadata: json!({
            "dispatch_defaults": {
                "cwd": "/path/that/does/not/exist",
                "repo": "stale-repo"
            }
        }),
        entities: Vec::new(),
        agents: Vec::new(),
        tools: Vec::new(),
        abilities: Vec::new(),
        workflows: Vec::new(),
        artifacts: Vec::new(),
        artifact_graph: Vec::new(),
        dependencies: Vec::new(),
        gates: Vec::new(),
        metrics: Vec::new(),
        gate_bundles: Vec::new(),
        policy: None,
        phases: Vec::new(),
        actions: Vec::new(),
        initial_event: None,
    };

    homeboy::core::agent_tasks::controller_service::apply_spec_dispatch_defaults_with_cwd(
        &mut spec,
        "-",
        || Some(repo.path().to_path_buf()),
    );
    let expected_root = std::fs::canonicalize(repo.path()).expect("canonical repo path");

    assert_eq!(
        spec.metadata["dispatch_defaults"]["cwd"],
        expected_root.display().to_string()
    );
    assert_eq!(spec.metadata["dispatch_defaults"]["repo"], "stale-repo");
}

#[test]
fn from_spec_dispatch_defaults_replace_stale_cwd_in_snapshot_workspace() {
    let workspace = tempfile::tempdir().expect("workspace dir");
    let mut spec = AgentTaskRepoLoopSpec {
        schema: None,
        loop_id: "repo-loop-snapshot-cwd-defaults".to_string(),
        phase: "init".to_string(),
        config_version: "v1".to_string(),
        metadata: json!({
            "dispatch_defaults": {
                "cwd": "/path/that/does/not/exist",
                "repo": "wp-site-generator"
            }
        }),
        entities: Vec::new(),
        agents: Vec::new(),
        tools: Vec::new(),
        abilities: Vec::new(),
        workflows: Vec::new(),
        artifacts: Vec::new(),
        artifact_graph: Vec::new(),
        dependencies: Vec::new(),
        gates: Vec::new(),
        metrics: Vec::new(),
        gate_bundles: Vec::new(),
        policy: None,
        phases: Vec::new(),
        actions: Vec::new(),
        initial_event: None,
    };

    homeboy::core::agent_tasks::controller_service::apply_spec_dispatch_defaults_with_cwd(
        &mut spec,
        "-",
        || Some(workspace.path().to_path_buf()),
    );
    let expected_root = std::fs::canonicalize(workspace.path()).expect("canonical workspace path");

    assert_eq!(
        spec.metadata["dispatch_defaults"]["cwd"],
        expected_root.display().to_string()
    );
    assert_eq!(
        spec.metadata["dispatch_defaults"]["repo"],
        "wp-site-generator"
    );
}

#[test]
fn controller_dispatch_args_preserve_top_level_workspace_context_in_plan() {
    let repo = tempfile::tempdir().expect("repo dir");
    let repo_path = repo.path().display().to_string();
    let request = json!({
        "mode": "dispatch",
        "cwd": repo_path.clone(),
        "repo": "wp-site-generator@canonical-loop-main-20260616",
        "dispatch": {
            "prompt": "cook the next workflow",
            "backend": "sample-runtime"
        }
    });

    let command =
        homeboy::core::agent_tasks::controller_service::controller_request_dispatch_command(
            &request,
            &homeboy::core::agent_tasks::controller_service::ControllerDispatchOverrides::default(),
        )
        .expect("dispatch command");
    let dispatch_request =
        homeboy::core::agent_tasks::dispatch_service::resolve_dispatch_request(command)
            .expect("dispatch request");
    let plan = homeboy::core::agent_tasks::dispatch_service::build_dispatch_plan_with_provider_requirements(
            &dispatch_request,
            |_backend, _selector| false,
        )
        .expect("dispatch plan");
    let task = plan.tasks.first().expect("plan task");

    assert_eq!(task.workspace.root.as_deref(), Some(repo_path.as_str()));
    assert_eq!(
        task.workspace.slug.as_deref(),
        Some("wp-site-generator@canonical-loop-main-20260616")
    );
    assert_eq!(
        task.executor.config["workspace_root"].as_str(),
        Some(repo_path.as_str())
    );
    assert_eq!(
        task.executor.config["repo"].as_str(),
        Some("wp-site-generator@canonical-loop-main-20260616")
    );
    assert_eq!(
        plan.metadata["workspace_root"].as_str(),
        Some(repo_path.as_str())
    );
}

#[test]
fn cook_dispatch_provider_id_alias_maps_to_selector() {
    let cli = Cli::try_parse_from([
        "homeboy",
        "agent-task",
        "cook",
        "--to-worktree",
        "homeboy@cook-provider-id",
        "--verify",
        "true",
        "--backend",
        "sample-backend",
        "--dispatch-provider-id",
        "sample-provider",
        "--prompt",
        "cook",
    ])
    .expect("dispatch provider id alias parses");

    let Commands::AgentTask(agent_task) = cli.command else {
        panic!("expected agent-task command");
    };
    let AgentTaskCommand::Cook(args) = agent_task.command else {
        panic!("expected cook command");
    };

    assert_eq!(args.dispatch.selector.as_deref(), Some("sample-provider"));
    assert_eq!(args.dispatch.model, None);
}

#[test]
fn controller_dispatch_provider_id_alias_maps_to_dispatch_selector() {
    let cli = Cli::try_parse_from([
        "homeboy",
        "agent-task",
        "controller",
        "from-spec",
        "@controller.json",
        "--dispatch-provider-id",
        "sample-provider",
    ])
    .expect("controller dispatch provider id alias parses");

    let Commands::AgentTask(agent_task) = cli.command else {
        panic!("expected agent-task command");
    };
    let AgentTaskCommand::Controller(controller) = agent_task.command else {
        panic!("expected controller command");
    };
    let super::super::AgentTaskControllerCommand::FromSpec(args) = controller.command else {
        panic!("expected controller from-spec command");
    };

    assert_eq!(
        args.dispatch.dispatch_selector.as_deref(),
        Some("sample-provider")
    );
    assert_eq!(args.dispatch.dispatch_model, None);
}

#[test]
fn controller_events_command_applies_generic_event() {
    with_isolated_home(|_| {
        agent_task_controller_service::init(
            homeboy::core::agent_tasks::controller_service::ControllerInitRequest {
                loop_id: "controller-events-cli".to_string(),
                phase: "init".to_string(),
                config_version: "v1".to_string(),
            },
        )
        .expect("controller initialized");

        let (value, status) = apply_controller_event(AgentTaskControllerApplyEventArgs {
            loop_id: "controller-events-cli".to_string(),
            event_type: "task.completed".to_string(),
            event_id: Some("event-1".to_string()),
            event_key: Some("task#1".to_string()),
            entity_id: Some("entity-1".to_string()),
            payload: Some(r#"{"status":"ok"}"#.to_string()),
        })
        .expect("event applied");

        assert_eq!(status, 0);
        assert_eq!(
            value["schema"],
            homeboy::core::agent_tasks::controller_service::APPLY_EVENT_RESULT_SCHEMA
        );
        assert_eq!(
            value["controller"]["history"][0]["event_type"],
            "task.completed"
        );
        assert_eq!(value["controller"]["history"][0]["entity_id"], "entity-1");
        assert_eq!(value["controller"]["history"][0]["payload"]["status"], "ok");
    });
}
