mod support;

use serde_json::{Value, json};
use std::{
    fs,
    path::{Path, PathBuf},
    process::{Command, Output},
};

struct Fixture(PathBuf);

impl Fixture {
    fn path(&self) -> &Path {
        &self.0
    }
}

impl Drop for Fixture {
    fn drop(&mut self) {
        support::remove_dir_all(&self.0);
    }
}

fn fixture() -> Fixture {
    let path = std::env::temp_dir().join(format!("shepherd-run-adoption-{}", uuid::Uuid::now_v7()));
    fs::create_dir(&path).expect("fixture");
    let temp = Fixture(fs::canonicalize(path).unwrap());
    assert!(
        Command::new("git")
            .args(["init", "--quiet"])
            .current_dir(temp.path())
            .status()
            .unwrap()
            .success()
    );
    fs::create_dir_all(temp.path().join(".shepherd/runs/v657/dispatch")).unwrap();
    fs::write(
        temp.path().join(".shepherd/runs/v657/run.json"),
        serde_json::to_vec(&json!({
            "schema_version": 1, "run": "v657", "kind": "patch-arc", "branch": "v6.5.7",
            "base": "main", "seed": "seed.md", "plan": "", "status": "planted", "lanes": [],
            "updated_at": 42, "operator_note": {"preserve": true}
        }))
        .unwrap(),
    )
    .unwrap();
    fs::write(
        temp.path().join(".shepherd/runs/v657/seed.md"),
        b"legacy seed, preserve exactly\n",
    )
    .unwrap();
    temp
}

fn invoke(root: &Path, args: &[&str]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_shepherd"))
        .args(args)
        .current_dir(root)
        .env("SHEPHERD_HOME", root.join("isolated-home"))
        .output()
        .unwrap()
}

fn state(root: &Path) -> Value {
    serde_json::from_slice(&fs::read(root.join(".shepherd/runs/v657/run.json")).unwrap()).unwrap()
}

fn registry(root: &Path) -> Value {
    serde_json::from_slice(
        &fs::read(root.join(".shepherd/native-orientation-registry.json")).unwrap(),
    )
    .unwrap()
}

#[test]
fn explicit_legacy_adoption_preserves_artifacts_and_replays_one_native_identity() {
    let temp = fixture();
    let root = temp.path();
    let original = state(root);
    let seed = fs::read(root.join(".shepherd/runs/v657/seed.md")).unwrap();
    let first = invoke(root, &["run", "migrate", "v657", "--adopt-native"]);
    assert!(
        first.status.success(),
        "{}",
        String::from_utf8_lossy(&first.stderr)
    );
    let adopted = state(root);
    assert_eq!(adopted["status"], "planted");
    assert_eq!(adopted["operator_note"], original["operator_note"]);
    assert_eq!(adopted["updated_at"], original["updated_at"]);
    assert_eq!(adopted["seed"], original["seed"]);
    assert_eq!(adopted["orientation_epoch"], 0);
    assert_eq!(adopted["run_incarnation"].as_str().unwrap().len(), 32);
    let native = registry(root);
    assert_eq!(
        native["runs"]["v657"]["run_incarnation"],
        adopted["run_incarnation"]
    );
    assert_eq!(
        native["runs"]["v657"]["legacy_adoption_sha256"]
            .as_str()
            .unwrap()
            .len(),
        64
    );
    assert!(native["runs"]["v657"]["pre"].is_null());
    assert!(native["runs"]["v657"]["post"].is_null());
    assert_eq!(
        seed,
        fs::read(root.join(".shepherd/runs/v657/seed.md")).unwrap()
    );
    let second = invoke(root, &["run", "migrate", "v657", "--adopt-native"]);
    assert!(
        second.status.success(),
        "{}",
        String::from_utf8_lossy(&second.stderr)
    );
    assert_eq!(adopted, state(root));
    assert_eq!(native, registry(root));
}

#[test]
fn adoption_cannot_import_execution_checkpoints_or_child_authority() {
    for (field, value) in [
        ("status", json!("executing")),
        ("orientation_epoch", json!(1)),
        ("run_incarnation", json!("caller-chosen-incarnation")),
        ("lanes", json!([{"id":"old", "state":"pending"}])),
    ] {
        let temp = fixture();
        let mut document = state(temp.path());
        document[field] = value;
        let path = temp.path().join(".shepherd/runs/v657/run.json");
        fs::write(&path, serde_json::to_vec(&document).unwrap()).unwrap();
        let before = fs::read(&path).unwrap();
        let result = invoke(temp.path(), &["run", "migrate", "v657", "--adopt-native"]);
        assert!(!result.status.success(), "accepted {field}");
        assert_eq!(before, fs::read(path).unwrap());
        assert!(
            !temp
                .path()
                .join(".shepherd/native-orientation-registry.json")
                .exists()
        );
    }
    for name in ["child.json", "pending-invalid.json"] {
        let temp = fixture();
        fs::write(
            temp.path().join(".shepherd/runs/v657/dispatch").join(name),
            b"{}",
        )
        .unwrap();
        let before = state(temp.path());
        let result = invoke(temp.path(), &["run", "migrate", "v657", "--adopt-native"]);
        assert!(!result.status.success(), "accepted {name}");
        assert_eq!(before, state(temp.path()));
        assert!(
            !temp
                .path()
                .join(".shepherd/native-orientation-registry.json")
                .exists()
        );
    }
}

#[test]
fn ordinary_migration_does_not_adopt_and_bulk_adoption_is_refused() {
    let temp = fixture();
    let result = invoke(temp.path(), &["run", "migrate", "v657"]);
    assert!(
        result.status.success(),
        "{}",
        String::from_utf8_lossy(&result.stderr)
    );
    assert_eq!(state(temp.path())["run_incarnation"], "");
    assert!(
        !temp
            .path()
            .join(".shepherd/native-orientation-registry.json")
            .exists()
    );
    let before = state(temp.path());
    let bulk = invoke(temp.path(), &["run", "migrate", "--all", "--adopt-native"]);
    assert!(!bulk.status.success());
    assert_eq!(before, state(temp.path()));
    assert!(
        !temp
            .path()
            .join(".shepherd/native-orientation-registry.json")
            .exists()
    );
}

#[test]
fn native_adoption_intent_cannot_be_reused_for_changed_or_unrelated_authority() {
    let temp = fixture();
    let result = invoke(temp.path(), &["run", "migrate", "v657", "--adopt-native"]);
    assert!(
        result.status.success(),
        "{}",
        String::from_utf8_lossy(&result.stderr)
    );
    let before_native = registry(temp.path());
    let path = temp.path().join(".shepherd/runs/v657/run.json");
    let mut changed = state(temp.path());
    changed["run_incarnation"] = json!("");
    changed["branch"] = json!("different-source");
    fs::write(&path, serde_json::to_vec(&changed).unwrap()).unwrap();
    let result = invoke(temp.path(), &["run", "migrate", "v657", "--adopt-native"]);
    assert!(!result.status.success());
    assert_eq!(changed, state(temp.path()));
    assert_eq!(before_native, registry(temp.path()));
    let mut unrelated = before_native.clone();
    unrelated["runs"]["v657"]
        .as_object_mut()
        .unwrap()
        .remove("legacy_adoption_sha256");
    let registry_path = temp
        .path()
        .join(".shepherd/native-orientation-registry.json");
    fs::write(&registry_path, serde_json::to_vec(&unrelated).unwrap()).unwrap();
    let result = invoke(temp.path(), &["run", "migrate", "v657", "--adopt-native"]);
    assert!(!result.status.success());
    assert_eq!(unrelated, registry(temp.path()));
}