use std::path::PathBuf;
use std::process::Command;

use assert_cmd::Command as AssertCmd;
use predicates::str::contains as pred_contains;

/// Path to the roboticus binary (Cargo sets CARGO_BIN_EXE_roboticus when running integration tests).
fn roboticus_bin() -> PathBuf {
    std::env::var("CARGO_BIN_EXE_roboticus")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            let mut exe = std::env::current_exe().expect("current exe");
            exe.pop(); // deps
            exe.pop(); // debug
            exe.push("roboticus");
            exe
        })
}

fn roboticus_cmd() -> Command {
    Command::new(roboticus_bin())
}

#[test]
fn version_shows_semver() {
    let output = roboticus_cmd()
        .arg("version")
        .output()
        .expect("failed to run roboticus-server version");
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    let out = format!("{stdout}{stderr}");
    assert!(
        out.contains("roboticus") || out.contains("0."),
        "output: {out}"
    );
}

#[test]
fn init_creates_config_file() {
    let dir = tempfile::tempdir().unwrap();
    let output = roboticus_cmd()
        .arg("init")
        .current_dir(dir.path())
        .output()
        .expect("failed to run init");
    assert!(output.status.success() || String::from_utf8_lossy(&output.stderr).contains("already"));
    // Config file should exist after init
    assert!(dir.path().join("roboticus.toml").exists() || output.status.success());
}

#[test]
fn check_without_config_returns_error() {
    let dir = tempfile::tempdir().unwrap();
    // Isolate HOME so resolve_config_path won't find the developer's real config
    let fake_home = dir.path().join("home");
    std::fs::create_dir_all(&fake_home).unwrap();
    let output = roboticus_cmd()
        .arg("check")
        .env("HOME", &fake_home)
        .current_dir(dir.path())
        .output()
        .expect("failed to run check");
    // Should fail gracefully when no config exists
    assert!(
        !output.status.success(),
        "check without config should exit non-zero"
    );
}

#[test]
fn cli_help_shows_subcommands() {
    AssertCmd::new(roboticus_bin())
        .arg("--help")
        .assert()
        .success()
        .stdout(pred_contains("init"))
        .stdout(pred_contains("serve"))
        .stdout(pred_contains("status"));
}

#[test]
fn cli_init_creates_config() {
    let dir = tempfile::TempDir::new().unwrap();
    let config_path = dir.path().join("roboticus.toml");
    // Init with default path "."; run from temp dir so roboticus.toml is created there
    AssertCmd::new(roboticus_bin())
        .current_dir(dir.path())
        .arg("init")
        .assert()
        .success();
    assert!(
        config_path.exists(),
        "roboticus.toml should exist at {:?}",
        config_path
    );
    let raw = std::fs::read_to_string(&config_path).expect("read roboticus.toml");
    assert!(
        raw.contains("api_key = \"rk_"),
        "init should generate [server] api_key by default"
    );
}

#[test]
fn cli_check_validates_config() {
    let dir = tempfile::TempDir::new().unwrap();
    let config_path = dir.path().join("roboticus.toml");
    // First init (run in dir, init ".")
    AssertCmd::new(roboticus_bin())
        .current_dir(dir.path())
        .args(["init", "."])
        .assert()
        .success();
    assert!(config_path.exists(), "init must create roboticus.toml");
    // Then check (config file path)
    AssertCmd::new(roboticus_bin())
        .args(["check", "--config", config_path.to_str().unwrap()])
        .assert()
        .success();
}

#[test]
fn cli_status_handles_no_server() {
    // status may exit 0 with a warning when server is not running, or fail; we assert it runs and responds
    let out = AssertCmd::new(roboticus_bin())
        .args(["status", "--url", "http://127.0.0.1:19999"])
        .output()
        .unwrap();
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("not running")
            || stderr.contains("Start with")
            || out.status.code() != Some(0),
        "status when server is down should warn or fail: {}",
        stderr
    );
}

#[test]
fn cli_config_show_handles_no_server() {
    AssertCmd::new(roboticus_bin())
        .args(["config", "show", "--url", "http://127.0.0.1:19999"])
        .assert()
        .failure();
}

#[test]
fn cli_wallet_handles_no_server() {
    AssertCmd::new(roboticus_bin())
        .args(["wallet", "show", "--url", "http://127.0.0.1:19999"])
        .assert()
        .failure();
}

#[test]
fn cli_sessions_handles_no_server() {
    AssertCmd::new(roboticus_bin())
        .args(["sessions", "list", "--url", "http://127.0.0.1:19999"])
        .assert()
        .failure();
}

#[test]
fn cli_metrics_handles_no_server() {
    AssertCmd::new(roboticus_bin())
        .args(["metrics", "--url", "http://127.0.0.1:19999"])
        .assert()
        .failure();
}

#[test]
fn cli_version_shows_version() {
    AssertCmd::new(roboticus_bin())
        .arg("version")
        .assert()
        .success()
        .stderr(predicates::str::contains(env!("CARGO_PKG_VERSION")));
}

#[test]
fn cli_version_json_outputs_structured_fields() {
    AssertCmd::new(roboticus_bin())
        .args(["--json", "version"])
        .assert()
        .success()
        .stdout(pred_contains("\"version\""))
        .stdout(pred_contains("\"edition\""))
        .stdout(pred_contains("\"target\""))
        .stdout(pred_contains("\"os\""));
}

#[test]
fn cli_completion_variants_work() {
    AssertCmd::new(roboticus_bin())
        .args(["completion", "bash"])
        .assert()
        .success()
        .stdout(pred_contains("completion"));
    AssertCmd::new(roboticus_bin())
        .args(["completion", "zsh"])
        .assert()
        .success()
        .stdout(pred_contains("compctl"));
    AssertCmd::new(roboticus_bin())
        .args(["completion", "fish"])
        .assert()
        .success()
        .stdout(pred_contains("complete -c roboticus"));
}

#[test]
fn cli_check_invalid_config_fails() {
    let dir = tempfile::TempDir::new().unwrap();
    let config_path = dir.path().join("roboticus.toml");
    std::fs::write(&config_path, "not valid toml = [").unwrap();
    AssertCmd::new(roboticus_bin())
        .args(["check", "--config", config_path.to_str().unwrap()])
        .assert()
        .failure();
}

#[test]
fn cli_subcommand_help_paths_render() {
    for args in [
        vec!["sessions", "--help"],
        vec!["memory", "--help"],
        vec!["skills", "--help"],
        vec!["schedule", "--help"],
        vec!["metrics", "--help"],
        vec!["wallet", "--help"],
        vec!["config", "--help"],
        vec!["models", "--help"],
        vec!["plugins", "--help"],
        vec!["agents", "--help"],
        vec!["channels", "--help"],
        vec!["security", "--help"],
        vec!["auth", "--help"],
        vec!["keystore", "--help"],
        vec!["migrate", "--help"],
        vec!["daemon", "--help"],
    ] {
        AssertCmd::new(roboticus_bin())
            .args(args)
            .assert()
            .success();
    }
}

#[test]
fn cli_more_no_server_commands_fail_or_warn_cleanly() {
    let no_server = "http://127.0.0.1:19999";
    for args in [
        vec!["agents", "list", "--url", no_server],
        vec!["channels", "list", "--url", no_server],
        vec!["channels", "dead-letter", "--url", no_server],
        vec!["models", "list", "--url", no_server],
        vec!["models", "scan", "--url", no_server],
        vec!["plugins", "list", "--url", no_server],
        vec!["circuit", "status", "--url", no_server],
        vec!["circuit", "reset", "--url", no_server],
    ] {
        let out = AssertCmd::new(roboticus_bin()).args(args).output().unwrap();
        let stderr = String::from_utf8_lossy(&out.stderr).to_ascii_lowercase();
        let stdout = String::from_utf8_lossy(&out.stdout).to_ascii_lowercase();
        assert!(
            !out.status.success()
                || stderr.contains("not running")
                || stderr.contains("not reachable")
                || stderr.contains("cannot reach")
                || stderr.contains("could not connect")
                || stdout.contains("not running")
                || stdout.contains("not reachable")
                || stdout.contains("cannot reach"),
            "unexpected success output: stdout={stdout} stderr={stderr}"
        );
    }
}

#[test]
fn cli_check_json_reports_valid_payload() {
    let dir = tempfile::TempDir::new().unwrap();
    let config_path = dir.path().join("roboticus.toml");
    AssertCmd::new(roboticus_bin())
        .current_dir(dir.path())
        .args(["init", "."])
        .assert()
        .success();

    AssertCmd::new(roboticus_bin())
        .args(["--json", "check", "--config", config_path.to_str().unwrap()])
        .assert()
        .success()
        .stdout(pred_contains("\"valid\": true"))
        .stdout(pred_contains("\"agent_name\""))
        .stdout(pred_contains("\"memory_budget_sum_pct\""))
        .stdout(pred_contains("\"warnings\""));
}

#[test]
fn cli_check_json_reports_invalid_payload_for_missing_config() {
    let dir = tempfile::TempDir::new().unwrap();
    let missing = dir.path().join("missing.toml");

    AssertCmd::new(roboticus_bin())
        .args(["--json", "check", "--config", missing.to_str().unwrap()])
        .assert()
        .failure()
        .stdout(pred_contains("\"valid\":false"))
        .stdout(pred_contains("\"config_path\""))
        .stdout(pred_contains("\"error\""));
}

#[test]
fn cli_mcp_add_stdio_prints_toml_snippet() {
    AssertCmd::new(roboticus_bin())
        .args(["mcp", "add", "local-stdio", "--stdio", "echo"])
        .assert()
        .success()
        .stdout(pred_contains("[[mcp.servers]]"))
        .stdout(pred_contains("name = \"local-stdio\""))
        .stdout(pred_contains("type = \"stdio\""))
        .stdout(pred_contains("command = \"echo\""));
}

#[test]
fn cli_mcp_add_sse_prints_toml_snippet() {
    AssertCmd::new(roboticus_bin())
        .args([
            "mcp",
            "add",
            "remote-sse",
            "--sse",
            "http://127.0.0.1:7001/mcp",
        ])
        .assert()
        .success()
        .stdout(pred_contains("[[mcp.servers]]"))
        .stdout(pred_contains("name = \"remote-sse\""))
        .stdout(pred_contains("type = \"sse\""))
        .stdout(pred_contains("url = \"http://127.0.0.1:7001/mcp\""));
}

#[test]
fn cli_mcp_remove_prints_manual_removal_guidance() {
    let out = AssertCmd::new(roboticus_bin())
        .args(["mcp", "remove", "legacy-server"])
        .output()
        .unwrap();
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    let stderr = String::from_utf8_lossy(&out.stderr);
    let combined = format!("{stdout}\n{stderr}");
    assert!(combined.contains("legacy-server"));
    assert!(combined.contains("[[mcp.servers]]"));
    assert!(combined.contains("roboticus daemon restart"));
}

#[test]
fn cli_mcp_test_handles_unreachable_server_cleanly() {
    let out = AssertCmd::new(roboticus_bin())
        .args(["mcp", "test", "missing", "--url", "http://127.0.0.1:19999"])
        .output()
        .unwrap();
    let stderr = String::from_utf8_lossy(&out.stderr).to_ascii_lowercase();
    assert!(
        stderr.contains("connection test: failed")
            || stderr.contains("is the roboticus server running")
            || stderr.contains("connect")
    );
}

#[test]
fn cli_security_audit_runs_on_local_config() {
    let dir = tempfile::TempDir::new().unwrap();
    let home = dir.path().join("home");
    std::fs::create_dir_all(home.join(".roboticus")).unwrap();
    let config_path = dir.path().join("roboticus.toml");
    std::fs::write(
        &config_path,
        r#"[agent]
name = "Test"
id = "test"
[server]
bind = "localhost"
port = 18789
[database]
path = ":memory:"
[models]
primary = "ollama/qwen3:8b"
"#,
    )
    .unwrap();

    AssertCmd::new(roboticus_bin())
        .env("HOME", home)
        .args([
            "security",
            "audit",
            "--config",
            config_path.to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn cli_keystore_lifecycle_round_trips_non_interactively() {
    let dir = tempfile::TempDir::new().unwrap();
    let home = dir.path().join("home");
    std::fs::create_dir_all(home.join(".roboticus")).unwrap();
    let password = "test-passphrase";

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args([
            "keystore",
            "set",
            "demo_key",
            "demo_value",
            "--password",
            password,
        ])
        .assert()
        .success()
        .stderr(pred_contains("Stored secret 'demo_key'"));

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args(["keystore", "get", "demo_key", "--password", password])
        .assert()
        .success()
        .stdout(pred_contains("demo_value"));

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args(["keystore", "list", "--password", password])
        .assert()
        .success()
        .stderr(pred_contains("demo_key"))
        .stderr(pred_contains("1 secret(s)"));

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args(["keystore", "remove", "demo_key", "--password", password])
        .assert()
        .success()
        .stderr(pred_contains("Removed 'demo_key'"));

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args(["keystore", "list", "--password", password])
        .assert()
        .success()
        .stderr(pred_contains("Keystore is empty"));
}

#[test]
fn cli_keystore_import_loads_json_entries() {
    let dir = tempfile::TempDir::new().unwrap();
    let home = dir.path().join("home");
    std::fs::create_dir_all(home.join(".roboticus")).unwrap();
    let password = "test-passphrase";
    let import_path = dir.path().join("secrets.json");
    std::fs::write(&import_path, r#"{"alpha":"one","beta":"two"}"#).unwrap();

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args([
            "keystore",
            "import",
            import_path.to_str().unwrap(),
            "--password",
            password,
        ])
        .assert()
        .success()
        .stderr(pred_contains("Imported 2 secret(s)"));

    AssertCmd::new(roboticus_bin())
        .env("HOME", &home)
        .args(["keystore", "list", "--password", password])
        .assert()
        .success()
        .stderr(pred_contains("alpha"))
        .stderr(pred_contains("beta"));
}
