use super::*;
use tempfile::tempdir;

#[test]
fn content_hash_deterministic() {
    let h1 = content_hash(b"hello world");
    let h2 = content_hash(b"hello world");
    assert_eq!(h1, h2);
    assert_eq!(h1.len(), 8, "hash should be 8 hex chars");
}

#[test]
fn content_hash_differs_for_different_input() {
    let h1 = content_hash(b"hello");
    let h2 = content_hash(b"world");
    assert_ne!(h1, h2);
}

#[test]
fn directory_hash_deterministic() {
    let dir = tempdir().unwrap();
    std::fs::create_dir_all(dir.path().join("sub")).unwrap();
    std::fs::write(dir.path().join("a.txt"), "aaa").unwrap();
    std::fs::write(dir.path().join("sub/b.txt"), "bbb").unwrap();
    let h1 = directory_hash(dir.path()).unwrap();
    let h2 = directory_hash(dir.path()).unwrap();
    assert_eq!(h1, h2);
    assert_eq!(h1.len(), 8);
}

#[test]
fn directory_hash_changes_on_content_change() {
    let dir = tempdir().unwrap();
    std::fs::write(dir.path().join("a.txt"), "version1").unwrap();
    let h1 = directory_hash(dir.path()).unwrap();
    std::fs::write(dir.path().join("a.txt"), "version2").unwrap();
    let h2 = directory_hash(dir.path()).unwrap();
    assert_ne!(h1, h2);
}

#[test]
fn directory_hash_includes_filenames() {
    let dir1 = tempdir().unwrap();
    std::fs::write(dir1.path().join("a.txt"), "content").unwrap();
    let dir2 = tempdir().unwrap();
    std::fs::write(dir2.path().join("b.txt"), "content").unwrap();
    let h1 = directory_hash(dir1.path()).unwrap();
    let h2 = directory_hash(dir2.path()).unwrap();
    assert_ne!(h1, h2);
}

#[test]
fn directory_hash_empty_dir() {
    let dir = tempdir().unwrap();
    let h = directory_hash(dir.path()).unwrap();
    assert_eq!(h.len(), 8);
}

#[test]
fn platform_name_for_file() {
    assert_eq!(
        platform_path("settings.json", "abcd1234"),
        "settings.json.abcd1234"
    );
}

#[test]
fn platform_name_for_directory() {
    assert_eq!(platform_path("right-mcp", "abcd1234"), "right-mcp.abcd1234");
}

#[test]
fn build_manifest_from_files() {
    let dir = tempdir().unwrap();
    let claude_dir = dir.path().join(".claude");
    std::fs::create_dir_all(claude_dir.join("skills/right-mcp")).unwrap();
    std::fs::write(claude_dir.join("settings.json"), r#"{"key":"val"}"#).unwrap();
    std::fs::write(claude_dir.join("skills/right-mcp/SKILL.md"), "# skill").unwrap();
    std::fs::write(dir.path().join("mcp.json"), "{}").unwrap();
    let manifest = build_manifest(dir.path(), &["right-mcp"]).unwrap();
    assert!(
        manifest
            .entries
            .iter()
            .any(|e| e.name == "settings.json" && !e.is_dir)
    );
    assert!(
        manifest
            .entries
            .iter()
            .any(|e| e.name == "mcp.json" && !e.is_dir)
    );
    assert!(
        manifest
            .entries
            .iter()
            .any(|e| e.name == "right-mcp" && e.is_dir)
    );
}

#[test]
fn build_manifest_excludes_agent_owned_md_files() {
    let dir = tempdir().unwrap();
    let claude_dir = dir.path().join(".claude");
    std::fs::create_dir_all(&claude_dir).unwrap();

    // Platform-managed files (allowlisted — should appear in manifest).
    std::fs::write(claude_dir.join("settings.json"), "{}").unwrap();
    std::fs::write(dir.path().join("mcp.json"), "{}").unwrap();

    // Agent-owned files at root and inside .claude/. None of these are in the
    // platform allowlist, so build_manifest must not include them.
    let agent_owned: &[&str] = &["IDENTITY.md", "SOUL.md", "USER.md", "TOOLS.md"];
    for &name in agent_owned {
        std::fs::write(dir.path().join(name), format!("# {name}\n")).unwrap();
        std::fs::write(claude_dir.join(name), format!("# claude/{name}\n")).unwrap();
    }

    let manifest = build_manifest(dir.path(), &[]).unwrap();

    for &name in agent_owned {
        assert!(
            !manifest.entries.iter().any(|e| e.name == name),
            "manifest must not include agent-owned file: {name}"
        );
    }

    // Sanity check: the platform-managed files are still picked up.
    assert!(manifest.entries.iter().any(|e| e.name == "settings.json"));
    assert!(manifest.entries.iter().any(|e| e.name == "mcp.json"));
}

#[test]
fn build_manifest_caches_file_content() {
    let dir = tempdir().unwrap();
    let claude_dir = dir.path().join(".claude");
    std::fs::create_dir_all(&claude_dir).unwrap();
    std::fs::write(claude_dir.join("settings.json"), r#"{"cached": true}"#).unwrap();
    let manifest = build_manifest(dir.path(), &[]).unwrap();
    let entry = manifest
        .entries
        .iter()
        .find(|e| e.name == "settings.json")
        .unwrap();
    assert!(entry.content.is_some(), "file entries must cache content");
    assert_eq!(entry.content.as_ref().unwrap(), br#"{"cached": true}"#);
}

/// Regression: every builtin skill on disk must end up in the deployment manifest.
/// Bug history: right-memory and right-reflect were added to the installer
/// (`right_codegen::install_builtin_skills`) but were never added to
/// `build_manifest`'s hardcoded list, so they silently never reached the sandbox.
#[test]
fn build_manifest_deploys_all_listed_builtin_skills() {
    let dir = tempdir().unwrap();
    let skills_root = dir.path().join(".claude/skills");
    let names = [
        "right-skills",
        "right-cron",
        "right-mcp",
        "right-memory",
        "right-reflect",
    ];
    for name in names {
        let path = skills_root.join(name);
        std::fs::create_dir_all(&path).unwrap();
        std::fs::write(path.join("SKILL.md"), format!("# {name}")).unwrap();
    }
    let manifest = build_manifest(dir.path(), &names).unwrap();
    for name in names {
        assert!(
            manifest.entries.iter().any(|e| e.name == name && e.is_dir),
            "manifest must include skill dir: {name}"
        );
    }
}

#[test]
fn build_manifest_dirs_have_no_cached_content() {
    let dir = tempdir().unwrap();
    let skills_dir = dir.path().join(".claude/skills/right-cron");
    std::fs::create_dir_all(&skills_dir).unwrap();
    std::fs::write(skills_dir.join("SKILL.md"), "# cron").unwrap();
    let manifest = build_manifest(dir.path(), &["right-cron"]).unwrap();
    let entry = manifest
        .entries
        .iter()
        .find(|e| e.name == "right-cron")
        .unwrap();
    assert!(entry.is_dir);
    assert!(
        entry.content.is_none(),
        "directory entries must not cache content"
    );
}
