use std::path::PathBuf;
use std::sync::Mutex;

use squeez::config::Config;
use squeez::hosts::{find, HostCaps, OpenCodeAdapter};
use squeez::hosts::HostAdapter;

// XDG_CONFIG_HOME is process-global — serialise tests that mutate it so
// parallel `cargo test` doesn't race.
static ENV_GUARD: Mutex<()> = Mutex::new(());

fn tmp_home() -> PathBuf {
    let uniq = format!(
        "{}-{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos(),
        std::process::id()
    );
    let path = std::env::temp_dir().join(format!("squeez-opencode-test-{uniq}"));
    std::fs::create_dir_all(&path).unwrap();
    path
}

fn with_xdg<F: FnOnce(&PathBuf) -> R, R>(f: F) -> R {
    let guard = ENV_GUARD.lock().unwrap_or_else(|e| e.into_inner());
    let home = tmp_home();
    std::env::set_var("XDG_CONFIG_HOME", &home);
    let r = f(&home);
    std::env::remove_var("XDG_CONFIG_HOME");
    drop(guard);
    r
}

#[test]
fn opencode_capabilities_full_parity() {
    let a = find("opencode").expect("opencode adapter");
    let caps = a.capabilities();
    assert!(caps.contains(HostCaps::BASH_WRAP));
    assert!(caps.contains(HostCaps::SESSION_MEM));
    assert!(caps.contains(HostCaps::BUDGET_HARD));
}

#[test]
fn opencode_data_dir_respects_xdg_config_home() {
    with_xdg(|home| {
        let a = OpenCodeAdapter;
        let d = a.data_dir();
        assert!(d.starts_with(home), "data_dir {:?} not under {:?}", d, home);
        assert!(d.ends_with("opencode/squeez"), "unexpected suffix: {:?}", d);
    });
}

#[test]
fn opencode_install_drops_plugin_file() {
    with_xdg(|home| {
        let a = OpenCodeAdapter;
        a.install(&PathBuf::from("/usr/local/bin/squeez"))
            .expect("install should succeed");
        let plugin = home.join("opencode/plugins/squeez.js");
        assert!(plugin.exists(), "plugin file missing at {:?}", plugin);
        let body = std::fs::read_to_string(&plugin).unwrap();
        // The plugin must use the PluginModule shape (default export w/ `id`
        // + async `server` returning a handler map). See issue #69.
        assert!(body.contains("export default"));
        assert!(body.contains("id: \"squeez\""));
        assert!(body.contains("server: async"));
        assert!(body.contains("tool.execute.before"));
        assert!(body.contains("tool.execute.after"));
        assert!(body.contains("session.created"));
        // The bash-rewrite path MUST single-quote the command before
        // prepending `squeez wrap`, otherwise multi-line `python3 -c`,
        // `bash -c`, and quoted `git commit -m "..."` are split into
        // separate argv tokens by the host shell and break.
        assert!(
            body.contains("command.replace(/'/g"),
            "plugin must shell-quote the command before wrapping; \
             raw template-literal concat splits multi-line / quoted args"
        );
    });
}

#[test]
fn opencode_inject_memory_writes_marker_block() {
    with_xdg(|home| {
        let a = OpenCodeAdapter;
        let cfg = Config::default();
        a.inject_memory(&cfg, &[]).expect("inject_memory should succeed");
        let agents = home.join("opencode/AGENTS.md");
        assert!(agents.exists(), "AGENTS.md not created");
        let body = std::fs::read_to_string(&agents).unwrap();
        assert!(body.contains("<!-- squeez:start -->"));
        assert!(body.contains("<!-- squeez:end -->"));
        assert!(body.contains("squeez — session context"));
    });
}

#[test]
fn opencode_inject_memory_is_idempotent() {
    with_xdg(|home| {
        let a = OpenCodeAdapter;
        let cfg = Config::default();
        a.inject_memory(&cfg, &[]).expect("first run");
        a.inject_memory(&cfg, &[]).expect("second run");
        let agents = home.join("opencode/AGENTS.md");
        let body = std::fs::read_to_string(&agents).unwrap();
        assert_eq!(
            body.matches("<!-- squeez:start -->").count(),
            1,
            "duplicate squeez block after idempotent re-run"
        );
    });
}

#[test]
fn opencode_inject_memory_preserves_existing_content() {
    with_xdg(|home| {
        std::fs::create_dir_all(home.join("opencode")).unwrap();
        let agents_path = home.join("opencode/AGENTS.md");
        std::fs::write(&agents_path, "# My existing rules\nuse tabs\n").unwrap();
        let a = OpenCodeAdapter;
        let cfg = Config::default();
        a.inject_memory(&cfg, &[]).expect("inject");
        let body = std::fs::read_to_string(&agents_path).unwrap();
        assert!(body.contains("<!-- squeez:start -->"));
        assert!(body.contains("# My existing rules"));
        assert!(body.contains("use tabs"));
    });
}

#[test]
fn opencode_uninstall_strips_marker_block_and_removes_plugin() {
    with_xdg(|home| {
        let a = OpenCodeAdapter;
        a.install(&PathBuf::from("/usr/local/bin/squeez")).unwrap();
        a.inject_memory(&Config::default(), &[]).unwrap();
        let plugin = home.join("opencode/plugins/squeez.js");
        let agents = home.join("opencode/AGENTS.md");
        assert!(plugin.exists());
        assert!(agents.exists());
        a.uninstall().unwrap();
        assert!(!plugin.exists(), "plugin should be removed");
        let body = std::fs::read_to_string(&agents).unwrap();
        assert!(!body.contains("<!-- squeez:start -->"));
    });
}
