use decapod::core::workunit;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;

fn run_decapod(dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> std::process::Output {
    let mut cmd = Command::new(env!("CARGO_BIN_EXE_decapod"));
    cmd.current_dir(dir).args(args);
    for (k, v) in envs {
        cmd.env(k, v);
    }
    cmd.output().expect("run decapod")
}

fn setup_repo() -> (TempDir, PathBuf, String) {
    let tmp = TempDir::new().expect("tmpdir");
    let dir = tmp.path().to_path_buf();

    let git_init = Command::new("git")
        .current_dir(&dir)
        .args(["init", "-b", "master"])
        .output()
        .expect("git init");
    assert!(git_init.status.success(), "git init failed");

    let init = run_decapod(&dir, &["init", "--force"], &[]);
    assert!(
        init.status.success(),
        "decapod init failed: {}",
        String::from_utf8_lossy(&init.stderr)
    );

    let acquire = run_decapod(
        &dir,
        &["session", "acquire"],
        &[("DECAPOD_AGENT_ID", "unknown")],
    );
    assert!(
        acquire.status.success(),
        "session acquire failed: {}",
        String::from_utf8_lossy(&acquire.stderr)
    );
    let password = String::from_utf8_lossy(&acquire.stdout)
        .lines()
        .find_map(|line| {
            line.strip_prefix("Password: ")
                .map(|s| s.trim().to_string())
        })
        .expect("password in session acquire output");

    (tmp, dir, password)
}

#[test]
fn workunit_init_creates_manifest_file() {
    let (_tmp, dir, password) = setup_repo();
    let out = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_001",
            "--intent-ref",
            "intent://test",
        ],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );
    assert!(
        out.status.success(),
        "workunit init failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    let payload: serde_json::Value = serde_json::from_slice(&out.stdout).expect("json");
    assert_eq!(payload["marker"], "WORKUNIT_INITIALIZED");
    let manifest_path = dir
        .join(".decapod")
        .join("governance")
        .join("workunits")
        .join("test_001.json");
    assert!(manifest_path.exists(), "manifest file should exist");
}

#[test]
fn workunit_get_returns_expected_manifest_shape() {
    let (_tmp, dir, password) = setup_repo();
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_002",
            "--intent-ref",
            "intent://shape",
        ],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );

    let out = run_decapod(
        &dir,
        &["govern", "workunit", "get", "--task-id", "test_002"],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );
    assert!(
        out.status.success(),
        "workunit get failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let payload: serde_json::Value = serde_json::from_slice(&out.stdout).expect("json");
    assert_eq!(payload["task_id"], "test_002");
    assert_eq!(payload["intent_ref"], "intent://shape");
    assert!(payload["spec_refs"].is_array());
    assert!(payload["state_refs"].is_array());
    assert!(payload["proof_plan"].is_array());
    assert!(payload["proof_results"].is_array());
    assert_eq!(payload["status"], "DRAFT");
}

#[test]
fn workunit_status_returns_deterministic_manifest_hash() {
    let (_tmp, dir, password) = setup_repo();
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_003",
            "--intent-ref",
            "intent://hash",
        ],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );

    let out = run_decapod(
        &dir,
        &["govern", "workunit", "status", "--task-id", "test_003"],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );
    assert!(
        out.status.success(),
        "workunit status failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let payload: serde_json::Value = serde_json::from_slice(&out.stdout).expect("json");
    assert_eq!(payload["task_id"], "test_003");
    assert_eq!(payload["workunit_status"], "DRAFT");
    let hash_cli = payload["manifest_hash"].as_str().expect("hash string");

    let manifest = workunit::load_workunit(&dir, "test_003").expect("load workunit");
    let hash_expected = manifest.canonical_hash_hex().expect("hash expected");
    assert_eq!(hash_cli, hash_expected);

    let path = workunit::workunit_path(&dir, "test_003").expect("path");
    let on_disk = fs::read_to_string(path).expect("read manifest");
    assert!(
        !on_disk.is_empty(),
        "manifest content should be present on disk"
    );
}

#[test]
fn workunit_attach_spec_and_state_are_persisted() {
    let (_tmp, dir, password) = setup_repo();
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_004",
            "--intent-ref",
            "intent://attach",
        ],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );

    for (subcmd, reference) in [
        ("attach-spec", "spec://a"),
        ("attach-spec", "spec://b"),
        ("attach-state", "state://1"),
        ("attach-state", "state://2"),
    ] {
        let out = run_decapod(
            &dir,
            &[
                "govern",
                "workunit",
                subcmd,
                "--task-id",
                "test_004",
                "--ref",
                reference,
            ],
            &[
                ("DECAPOD_AGENT_ID", "unknown"),
                ("DECAPOD_SESSION_PASSWORD", &password),
                ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
            ],
        );
        assert!(
            out.status.success(),
            "{} failed: {}",
            subcmd,
            String::from_utf8_lossy(&out.stderr)
        );
    }

    let manifest = workunit::load_workunit(&dir, "test_004").expect("load workunit");
    assert_eq!(manifest.spec_refs, vec!["spec://a", "spec://b"]);
    assert_eq!(manifest.state_refs, vec!["state://1", "state://2"]);
}

#[test]
fn workunit_set_proof_plan_replaces_and_canonicalizes_gates() {
    let (_tmp, dir, password) = setup_repo();
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_005",
            "--intent-ref",
            "intent://proofs",
        ],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );

    let out = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "set-proof-plan",
            "--task-id",
            "test_005",
            "--gate",
            "validate_passes",
            "--gate",
            "state_commit",
            "--gate",
            "validate_passes",
        ],
        &[
            ("DECAPOD_AGENT_ID", "unknown"),
            ("DECAPOD_SESSION_PASSWORD", &password),
            ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
        ],
    );
    assert!(
        out.status.success(),
        "set-proof-plan failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    let manifest = workunit::load_workunit(&dir, "test_005").expect("load workunit");
    assert_eq!(manifest.proof_plan, vec!["state_commit", "validate_passes"]);
}

#[test]
fn workunit_transition_to_verified_requires_passing_proofs() {
    let (_tmp, dir, password) = setup_repo();
    let envs = [
        ("DECAPOD_AGENT_ID", "unknown"),
        ("DECAPOD_SESSION_PASSWORD", &password),
        ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
    ];

    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_006",
            "--intent-ref",
            "intent://verified",
        ],
        &envs,
    );
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "set-proof-plan",
            "--task-id",
            "test_006",
            "--gate",
            "validate_passes",
        ],
        &envs,
    );
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "transition",
            "--task-id",
            "test_006",
            "--to",
            "executing",
        ],
        &envs,
    );
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "transition",
            "--task-id",
            "test_006",
            "--to",
            "claimed",
        ],
        &envs,
    );

    let out = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "transition",
            "--task-id",
            "test_006",
            "--to",
            "verified",
        ],
        &envs,
    );
    assert!(
        !out.status.success(),
        "transition should fail without passing proof results"
    );
    let combined = format!(
        "{}\n{}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );
    assert!(
        combined.contains("missing passing proof result"),
        "expected missing proof guard, got:\n{}",
        combined
    );
}

#[test]
fn workunit_record_proof_and_transition_happy_path() {
    let (_tmp, dir, password) = setup_repo();
    let envs = [
        ("DECAPOD_AGENT_ID", "unknown"),
        ("DECAPOD_SESSION_PASSWORD", &password),
        ("DECAPOD_VALIDATE_SKIP_GIT_GATES", "1"),
    ];

    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "init",
            "--task-id",
            "test_007",
            "--intent-ref",
            "intent://progress",
        ],
        &envs,
    );
    let _ = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "set-proof-plan",
            "--task-id",
            "test_007",
            "--gate",
            "validate_passes",
        ],
        &envs,
    );

    for to in ["executing", "claimed"] {
        let step = run_decapod(
            &dir,
            &[
                "govern",
                "workunit",
                "transition",
                "--task-id",
                "test_007",
                "--to",
                to,
            ],
            &envs,
        );
        assert!(
            step.status.success(),
            "transition to {} failed: {}",
            to,
            String::from_utf8_lossy(&step.stderr)
        );
    }

    let proof = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "record-proof",
            "--task-id",
            "test_007",
            "--gate",
            "validate_passes",
            "--status",
            "pass",
            "--artifact",
            "sha256:abc",
        ],
        &envs,
    );
    assert!(
        proof.status.success(),
        "record-proof failed: {}",
        String::from_utf8_lossy(&proof.stderr)
    );

    let final_step = run_decapod(
        &dir,
        &[
            "govern",
            "workunit",
            "transition",
            "--task-id",
            "test_007",
            "--to",
            "verified",
        ],
        &envs,
    );
    assert!(
        final_step.status.success(),
        "transition to verified failed: {}",
        String::from_utf8_lossy(&final_step.stderr)
    );

    let manifest = workunit::load_workunit(&dir, "test_007").expect("load workunit");
    assert_eq!(manifest.status, workunit::WorkUnitStatus::Verified);
}
