use std::fs;
use std::path::{Path, PathBuf};

use clap::{Args, ValueEnum};
use serde_json::{Value, json};

use crate::util::{
    api_request, dry_run_enabled, print_json_stderr, print_json_stdout, raw_api_request_with_query,
};

pub const KURA_AGENT_SKILL_NAME: &str = "kura";
pub const KURA_AGENT_SKILL_SHIM_VERSION: &str = "kura-agent-skill-shim.v2";
pub const KURA_AGENT_SKILL_MARKER_PREFIX: &str = "<!-- kura-agent-skill-shim:";
pub const KURA_AGENT_SKILL_BRIEF_COMMAND: &str =
    "kura agent skillpacks brief --surface cli --format markdown";

#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum AgentSkillTarget {
    /// Pi native Agent Skills locations (`.pi/skills`, `~/.pi/agent/skills`)
    Pi,
    /// Claude Code skill locations (`.claude/skills`, `~/.claude/skills`)
    ClaudeCode,
    /// OpenAI Codex-style skill locations (`.codex/skills`, `~/.codex/skills`)
    Codex,
    /// Generic Agent Skills locations (`.agents/skills`, `~/.agents/skills`)
    Agents,
}

impl AgentSkillTarget {
    pub fn as_str(self) -> &'static str {
        match self {
            AgentSkillTarget::Pi => "pi",
            AgentSkillTarget::ClaudeCode => "claude-code",
            AgentSkillTarget::Codex => "codex",
            AgentSkillTarget::Agents => "agents",
        }
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum AgentSkillScope {
    /// Install into the current project
    Project,
    /// Install into the current user's global agent config
    User,
}

impl AgentSkillScope {
    pub fn as_str(self) -> &'static str {
        match self {
            AgentSkillScope::Project => "project",
            AgentSkillScope::User => "user",
        }
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum AgentSkillpackBriefFormat {
    Json,
    Markdown,
}

#[derive(Args, Clone, Debug)]
pub struct AgentSkillpackBriefArgs {
    /// Agent surface requesting the brief (default: cli)
    #[arg(long, default_value = "cli")]
    pub surface: String,

    /// Optional KURA tool/intent to focus the brief, e.g. record_activity
    #[arg(long)]
    pub tool: Option<String>,

    /// Output format for agent consumption
    #[arg(long, value_enum, default_value_t = AgentSkillpackBriefFormat::Markdown)]
    pub format: AgentSkillpackBriefFormat,
}

#[derive(Args, Clone, Debug)]
pub struct AgentSkillpackVerifyArgs {
    /// Agent surface requesting the brief (default: cli)
    #[arg(long, default_value = "cli")]
    pub surface: String,

    /// Optional KURA tool/intent to focus the server brief
    #[arg(long)]
    pub tool: Option<String>,

    /// Also verify an installed native skill for this target
    #[arg(long, value_enum)]
    pub target: Option<AgentSkillTarget>,

    /// Install scope to inspect when --target is used
    #[arg(long, value_enum, default_value_t = AgentSkillScope::Project)]
    pub scope: AgentSkillScope,

    /// Explicit SKILL.md path or skill directory to inspect instead of the target default
    #[arg(long, value_name = "PATH")]
    pub path: Option<PathBuf>,
}

#[derive(Args, Clone, Debug)]
pub struct AgentInstallSkillArgs {
    /// Native agent target to install for
    #[arg(long, value_enum, default_value_t = AgentSkillTarget::Pi)]
    pub target: AgentSkillTarget,

    /// Install into the current project or current user's global agent config
    #[arg(long, value_enum, default_value_t = AgentSkillScope::Project)]
    pub scope: AgentSkillScope,

    /// Explicit SKILL.md path or skill directory to install into
    #[arg(long, value_name = "PATH")]
    pub path: Option<PathBuf>,

    /// Replace an existing non-KURA skill at the destination
    #[arg(long)]
    pub force: bool,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InstalledSkillInspection {
    pub exists: bool,
    pub valid_kura_shim: bool,
    pub shim_version: Option<String>,
}

pub async fn run_skillpack_brief(
    api_url: &str,
    token: Option<&str>,
    args: AgentSkillpackBriefArgs,
) -> i32 {
    let query = build_skillpack_brief_query(&args.surface, args.tool.as_deref());
    match args.format {
        AgentSkillpackBriefFormat::Json => {
            api_request(
                api_url,
                reqwest::Method::GET,
                "/v1/agent/skillpacks/brief",
                token,
                None,
                &query,
                &[],
                false,
                false,
            )
            .await
        }
        AgentSkillpackBriefFormat::Markdown => {
            match raw_api_request_with_query(
                api_url,
                reqwest::Method::GET,
                "/v1/agent/skillpacks/brief",
                token,
                &query,
            )
            .await
            {
                Ok((status, body)) if (200..=299).contains(&status) => {
                    println!("{}", format_skillpack_brief_markdown(&body));
                    0
                }
                Ok((status, body)) => {
                    print_json_stderr(&json!({
                        "error": "skillpack_brief_failed",
                        "status": status,
                        "body": body,
                    }));
                    if (400..=499).contains(&status) { 1 } else { 2 }
                }
                Err(message) => {
                    print_json_stderr(&json!({
                        "error": "connection_error",
                        "message": message,
                        "docs_hint": "Is the API server running? Check KURA_API_URL.",
                    }));
                    3
                }
            }
        }
    }
}

pub async fn run_skillpack_verify(
    api_url: &str,
    token: Option<&str>,
    args: AgentSkillpackVerifyArgs,
) -> i32 {
    let query = build_skillpack_brief_query(&args.surface, args.tool.as_deref());
    let server = raw_api_request_with_query(
        api_url,
        reqwest::Method::GET,
        "/v1/agent/skillpacks/brief",
        token,
        &query,
    )
    .await;

    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let installed_path = match (args.path.as_ref(), args.target) {
        (Some(path), _) => Some(normalize_custom_skill_path(path)),
        (None, Some(target)) => match default_skill_path(target, args.scope, &cwd) {
            Ok(path) => Some(path),
            Err(message) => {
                print_json_stderr(&json!({
                    "error": "skill_verify_failed",
                    "message": message,
                }));
                return 4;
            }
        },
        (None, None) => None,
    };

    let installed = installed_path.as_ref().map(|path| {
        let inspection = inspect_installed_skill(path);
        json!({
            "path": path.display().to_string(),
            "exists": inspection.exists,
            "valid_kura_shim": inspection.valid_kura_shim,
            "shim_version": inspection.shim_version,
        })
    });

    let (server_json, server_ok) = match server {
        Ok((status, body)) => (
            json!({
                "reachable": (200..=299).contains(&status),
                "http_status": status,
                "active_set_id": body.get("active_set_id").cloned().unwrap_or(Value::Null),
                "hash": body.get("hash").cloned().unwrap_or(Value::Null),
                "surface": body.get("surface").cloned().unwrap_or(Value::Null),
                "tool": body.get("tool").cloned().unwrap_or(Value::Null),
                "compatibility": body.get("compatibility").cloned().unwrap_or(Value::Null),
            }),
            (200..=299).contains(&status),
        ),
        Err(message) => (
            json!({
                "reachable": false,
                "error": "connection_error",
                "message": message,
            }),
            false,
        ),
    };

    let installed_ok = installed
        .as_ref()
        .and_then(|value| value.get("valid_kura_shim"))
        .and_then(Value::as_bool)
        .unwrap_or(true);
    let installed_exists = installed
        .as_ref()
        .and_then(|value| value.get("exists"))
        .and_then(Value::as_bool)
        .unwrap_or(true);

    let decision = if !server_ok {
        "server_unavailable"
    } else if !installed_exists {
        "install_missing"
    } else if !installed_ok {
        "install_invalid"
    } else {
        "ok"
    };

    print_json_stdout(&json!({
        "schema_version": "agent_skillpack_cli_verify.v1",
        "decision": decision,
        "server": server_json,
        "installed": installed.unwrap_or(Value::Null),
        "commands": {
            "brief": KURA_AGENT_SKILL_BRIEF_COMMAND,
            "install_pi_project": "kura agent install-skill --target pi --scope project"
        },
    }));

    if decision == "ok" { 0 } else { 1 }
}

pub async fn run_install_skill(args: AgentInstallSkillArgs) -> i32 {
    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    match install_native_agent_skill(args, &cwd, dry_run_enabled()) {
        Ok(output) => {
            print_json_stdout(&output);
            0
        }
        Err(error) => {
            print_json_stderr(&error);
            4
        }
    }
}

pub fn install_native_agent_skill(
    args: AgentInstallSkillArgs,
    cwd: &Path,
    preview: bool,
) -> Result<Value, Value> {
    let path = resolve_skill_path(args.target, args.scope, args.path.as_ref(), cwd)
        .map_err(|message| skill_install_error(message, None))?;
    let content = build_kura_agent_skill_markdown(args.target);
    let existing = fs::read_to_string(&path).ok();

    let status = match existing.as_deref() {
        Some(current) if current == content => "already_current",
        Some(current) if !existing_skill_is_kura_shim(current) && !args.force => {
            return Err(skill_install_error(
                "Refusing to overwrite an existing non-KURA skill. Re-run with --force if this is intentional.",
                Some(&path),
            ));
        }
        Some(current) if existing_skill_is_kura_shim(current) => {
            if preview {
                "would_update"
            } else {
                write_skill_file(&path, &content)
                    .map_err(|message| skill_install_error(message, Some(&path)))?;
                "updated"
            }
        }
        Some(_) => {
            if preview {
                "would_replace"
            } else {
                write_skill_file(&path, &content)
                    .map_err(|message| skill_install_error(message, Some(&path)))?;
                "replaced"
            }
        }
        None => {
            if preview {
                "would_install"
            } else {
                write_skill_file(&path, &content)
                    .map_err(|message| skill_install_error(message, Some(&path)))?;
                "installed"
            }
        }
    };

    Ok(json!({
        "schema_version": "agent_skill_install_result.v1",
        "status": status,
        "dry_run": preview,
        "target": args.target.as_str(),
        "scope": args.scope.as_str(),
        "skill_name": KURA_AGENT_SKILL_NAME,
        "shim_version": KURA_AGENT_SKILL_SHIM_VERSION,
        "path": path.display().to_string(),
        "server_brief_command": KURA_AGENT_SKILL_BRIEF_COMMAND,
        "verify_command": format!(
            "kura agent skillpacks verify --target {} --scope {}",
            args.target.as_str(),
            args.scope.as_str()
        ),
        "notes": [
            "The installed skill is a lightweight shim; KURA server skillpacks remain the source of truth.",
            "Agents should fetch the live skillpack brief before KURA actions.",
        ],
    }))
}

pub fn build_skillpack_brief_query(surface: &str, tool: Option<&str>) -> Vec<(String, String)> {
    let mut query = vec![("surface".to_string(), surface.trim().to_string())];
    if let Some(tool) = tool.map(str::trim).filter(|value| !value.is_empty()) {
        query.push(("tool".to_string(), tool.to_string()));
    }
    query
}

pub fn format_skillpack_brief_markdown(body: &Value) -> String {
    let schema_version = json_str(body.get("schema_version"));
    let active_set_id = json_str(body.get("active_set_id"));
    let hash = json_str(body.get("hash"));
    let surface = json_str(body.get("surface"));
    let tool = body.get("tool").and_then(Value::as_str).unwrap_or("all");
    let compatibility_status = body
        .pointer("/compatibility/status")
        .and_then(Value::as_str)
        .unwrap_or("unknown");
    let skill_ids = body
        .get("skill_ids")
        .and_then(Value::as_array)
        .map(|values| {
            values
                .iter()
                .filter_map(Value::as_str)
                .collect::<Vec<_>>()
                .join(", ")
        })
        .filter(|value| !value.is_empty())
        .unwrap_or_else(|| "none".to_string());
    let brief = body.get("brief").and_then(Value::as_str).unwrap_or("");

    let mut out = String::new();
    out.push_str("# KURA Agent Skillpack Brief\n\n");
    out.push_str(&format!("- schema_version: `{schema_version}`\n"));
    out.push_str(&format!("- active_set_id: `{active_set_id}`\n"));
    out.push_str(&format!("- hash: `{hash}`\n"));
    out.push_str(&format!("- surface: `{surface}`\n"));
    out.push_str(&format!("- tool: `{tool}`\n"));
    out.push_str(&format!(
        "- compatibility.status: `{compatibility_status}`\n"
    ));
    out.push_str(&format!("- skill_ids: `{skill_ids}`\n"));

    if let Some(notes) = body
        .pointer("/compatibility/notes")
        .and_then(Value::as_array)
    {
        let note_lines = notes.iter().filter_map(Value::as_str).collect::<Vec<_>>();
        if !note_lines.is_empty() {
            out.push_str("\n## Compatibility notes\n\n");
            for note in note_lines {
                out.push_str("- ");
                out.push_str(note);
                out.push('\n');
            }
        }
    }

    out.push_str("\n---\n\n");
    out.push_str(brief.trim());
    out.push('\n');
    out
}

pub fn build_kura_agent_skill_markdown(target: AgentSkillTarget) -> String {
    format!(
        r#"---
name: kura
description: KURA training and health-data workflows. Use when the user asks to log training, recovery, nutrition, measurements, benchmarks, read KURA records, analyze trends or decisions, plan/update training, update profile or plan data, or operate the KURA CLI/API.
---

<!-- kura-agent-skill-shim: {shim_version} -->
<!-- target: {target} -->

# KURA

This is a lightweight native Agent Skill shim. It does not duplicate KURA's
server-side skillpack. KURA server/tool schemas, validation, consent, and
returned tool results remain authoritative.

## Required first step

Before the first KURA action in a session, fetch the current server skillpack brief:

```bash
{brief_command}
```

If you know the exact KURA tool you will use, fetch the focused brief too:

```bash
kura agent skillpacks brief --surface cli --tool record_activity --format markdown
```

Use the live brief for task routing, evidence boundaries, analysis-before-advice
rules, recovery/health handling, and tool failure recovery. Do not rely on an old
brief after the active hash changes.

## Low-friction health check

Once per session, before the first KURA task only, run this quick non-blocking check:

```bash
kura agent skillpacks verify --target {target} --scope project --surface cli
```

Do not repeat it in the same session unless the CLI/API changed or a KURA command
fails. Do not interrupt the user for a successful check. If it warns or fails,
continue when the requested KURA task is still safe, and only mention the issue if
it affects the answer. Run full `kura doctor` only for setup/debugging, repeated
KURA command failures, or explicit user requests.

## Operating rules

- Do not claim data was saved unless a KURA write response or follow-up read shows it.
- For trends, stagnation, comparisons, influence, explanations, or training decisions, start with `kura analyze`.
- Use `kura read_query` for exact records, bounded lists, facts, and timelines.
- For logging, prefer the intent-native public CLI commands (`record_activity`,
  `record_recovery`, `record_benchmark`, `record_nutrition`,
  `record_measurement`, `report_health`, `plan_activity`, `update_profile`,
  `update_plan`) over raw API calls.
- If the brief endpoint is unavailable, continue with the KURA CLI/tool schemas
  and server validation. Be explicit that skillpack guidance was unavailable and
  avoid unsupported coaching claims.
- Never bypass consent, safety, confirmation, or schema errors. Ask the user for
  missing information or follow executable retry payloads returned by KURA.
"#,
        shim_version = KURA_AGENT_SKILL_SHIM_VERSION,
        target = target.as_str(),
        brief_command = KURA_AGENT_SKILL_BRIEF_COMMAND,
    )
}

pub fn resolve_skill_path(
    target: AgentSkillTarget,
    scope: AgentSkillScope,
    custom_path: Option<&PathBuf>,
    cwd: &Path,
) -> Result<PathBuf, String> {
    if let Some(path) = custom_path {
        return Ok(normalize_custom_skill_path(path));
    }
    default_skill_path(target, scope, cwd)
}

pub fn default_skill_path(
    target: AgentSkillTarget,
    scope: AgentSkillScope,
    cwd: &Path,
) -> Result<PathBuf, String> {
    match (target, scope) {
        (AgentSkillTarget::Pi, AgentSkillScope::Project) => {
            Ok(cwd.join(".pi").join("skills").join("kura").join("SKILL.md"))
        }
        (AgentSkillTarget::Pi, AgentSkillScope::User) => {
            if let Some(agent_dir) = std::env::var("PI_CODING_AGENT_DIR")
                .ok()
                .map(|value| value.trim().to_string())
                .filter(|value| !value.is_empty())
            {
                return Ok(expand_tilde_path(&agent_dir)
                    .join("skills")
                    .join("kura")
                    .join("SKILL.md"));
            }
            let home =
                dirs::home_dir().ok_or_else(|| "Unable to resolve home directory".to_string())?;
            Ok(home
                .join(".pi")
                .join("agent")
                .join("skills")
                .join("kura")
                .join("SKILL.md"))
        }
        (AgentSkillTarget::ClaudeCode, AgentSkillScope::Project) => Ok(cwd
            .join(".claude")
            .join("skills")
            .join("kura")
            .join("SKILL.md")),
        (AgentSkillTarget::ClaudeCode, AgentSkillScope::User) => Ok(home_dir()?
            .join(".claude")
            .join("skills")
            .join("kura")
            .join("SKILL.md")),
        (AgentSkillTarget::Codex, AgentSkillScope::Project) => Ok(cwd
            .join(".codex")
            .join("skills")
            .join("kura")
            .join("SKILL.md")),
        (AgentSkillTarget::Codex, AgentSkillScope::User) => Ok(home_dir()?
            .join(".codex")
            .join("skills")
            .join("kura")
            .join("SKILL.md")),
        (AgentSkillTarget::Agents, AgentSkillScope::Project) => Ok(cwd
            .join(".agents")
            .join("skills")
            .join("kura")
            .join("SKILL.md")),
        (AgentSkillTarget::Agents, AgentSkillScope::User) => Ok(home_dir()?
            .join(".agents")
            .join("skills")
            .join("kura")
            .join("SKILL.md")),
    }
}

pub fn inspect_installed_skill(path: &Path) -> InstalledSkillInspection {
    let Some(content) = fs::read_to_string(path).ok() else {
        return InstalledSkillInspection {
            exists: false,
            valid_kura_shim: false,
            shim_version: None,
        };
    };
    InstalledSkillInspection {
        exists: true,
        valid_kura_shim: existing_skill_is_kura_shim(&content),
        shim_version: extract_shim_version(&content),
    }
}

fn write_skill_file(path: &Path, content: &str) -> Result<(), String> {
    let parent = path
        .parent()
        .ok_or_else(|| "Skill destination has no parent directory".to_string())?;
    fs::create_dir_all(parent).map_err(|err| format!("Failed to create skill directory: {err}"))?;
    fs::write(path, content).map_err(|err| format!("Failed to write skill file: {err}"))
}

fn existing_skill_is_kura_shim(content: &str) -> bool {
    extract_shim_version(content).is_some()
        || (content.contains("name: kura") && content.contains(KURA_AGENT_SKILL_BRIEF_COMMAND))
}

fn extract_shim_version(content: &str) -> Option<String> {
    for line in content.lines() {
        let trimmed = line.trim();
        if let Some(rest) = trimmed.strip_prefix(KURA_AGENT_SKILL_MARKER_PREFIX) {
            return Some(rest.trim_end_matches("-->").trim().to_string());
        }
    }
    None
}

fn normalize_custom_skill_path(path: &Path) -> PathBuf {
    if path.file_name().and_then(|name| name.to_str()) == Some("SKILL.md") {
        path.to_path_buf()
    } else {
        path.join("SKILL.md")
    }
}

fn home_dir() -> Result<PathBuf, String> {
    dirs::home_dir().ok_or_else(|| "Unable to resolve home directory".to_string())
}

fn expand_tilde_path(raw: &str) -> PathBuf {
    if raw == "~" {
        return dirs::home_dir().unwrap_or_else(|| PathBuf::from(raw));
    }
    if let Some(rest) = raw.strip_prefix("~/") {
        return dirs::home_dir()
            .map(|home| home.join(rest))
            .unwrap_or_else(|| PathBuf::from(raw));
    }
    PathBuf::from(raw)
}

fn skill_install_error(message: impl Into<String>, path: Option<&Path>) -> Value {
    let mut error = json!({
        "error": "skill_install_failed",
        "message": message.into(),
    });
    if let Some(path) = path {
        error["path"] = json!(path.display().to_string());
    }
    error
}

fn json_str(value: Option<&Value>) -> String {
    value
        .and_then(Value::as_str)
        .unwrap_or("unknown")
        .to_string()
}

#[cfg(test)]
mod tests {
    use super::{
        AgentInstallSkillArgs, AgentSkillScope, AgentSkillTarget, KURA_AGENT_SKILL_BRIEF_COMMAND,
        KURA_AGENT_SKILL_SHIM_VERSION, build_kura_agent_skill_markdown,
        build_skillpack_brief_query, default_skill_path, format_skillpack_brief_markdown,
        inspect_installed_skill, install_native_agent_skill,
    };
    use serde_json::json;
    use std::fs;
    use std::path::PathBuf;
    use uuid::Uuid;

    fn temp_dir(label: &str) -> PathBuf {
        let path = std::env::temp_dir().join(format!("kura-cli-{label}-{}", Uuid::now_v7()));
        fs::create_dir_all(&path).expect("temp dir");
        path
    }

    fn install_args(path: PathBuf) -> AgentInstallSkillArgs {
        AgentInstallSkillArgs {
            target: AgentSkillTarget::Pi,
            scope: AgentSkillScope::Project,
            path: Some(path),
            force: false,
        }
    }

    #[test]
    fn pi_project_default_path_matches_native_pi_skill_location() {
        let cwd = PathBuf::from("/tmp/example-project");
        let path = default_skill_path(AgentSkillTarget::Pi, AgentSkillScope::Project, &cwd)
            .expect("pi path");
        assert_eq!(path, cwd.join(".pi/skills/kura/SKILL.md"));
    }

    #[test]
    fn generic_agent_target_uses_dot_agents_skill_location() {
        let cwd = PathBuf::from("/tmp/example-project");
        let path = default_skill_path(AgentSkillTarget::Agents, AgentSkillScope::Project, &cwd)
            .expect("agents path");
        assert_eq!(path, cwd.join(".agents/skills/kura/SKILL.md"));
    }

    #[test]
    fn native_skill_markdown_is_agent_skills_compatible_shim() {
        let markdown = build_kura_agent_skill_markdown(AgentSkillTarget::Pi);
        assert!(markdown.starts_with("---\nname: kura\n"));
        assert!(markdown.contains("description: KURA training and health-data workflows."));
        assert!(markdown.contains(KURA_AGENT_SKILL_SHIM_VERSION));
        assert!(markdown.contains(KURA_AGENT_SKILL_BRIEF_COMMAND));
        assert!(markdown.contains("server/tool schemas, validation, consent"));
    }

    #[test]
    fn install_native_agent_skill_writes_skill_marker() {
        let root = temp_dir("install");
        let skill_dir = root.join("skill-dir");
        let output = install_native_agent_skill(install_args(skill_dir.clone()), &root, false)
            .expect("install");
        let skill_path = skill_dir.join("SKILL.md");
        assert_eq!(output["status"], "installed");
        let inspection = inspect_installed_skill(&skill_path);
        assert!(inspection.exists);
        assert!(inspection.valid_kura_shim);
        assert_eq!(
            inspection.shim_version.as_deref(),
            Some(KURA_AGENT_SKILL_SHIM_VERSION)
        );
    }

    #[test]
    fn install_native_agent_skill_refuses_non_kura_skill_without_force() {
        let root = temp_dir("refuse");
        let skill_dir = root.join("skill-dir");
        fs::create_dir_all(&skill_dir).expect("skill dir");
        fs::write(
            skill_dir.join("SKILL.md"),
            "---\nname: other\ndescription: Other skill\n---\n",
        )
        .expect("existing skill");
        let error = install_native_agent_skill(install_args(skill_dir), &root, false)
            .expect_err("should refuse overwrite");
        assert_eq!(error["error"], "skill_install_failed");
        assert!(error["message"].as_str().unwrap().contains("--force"));
    }

    #[test]
    fn skillpack_brief_query_includes_surface_and_optional_tool() {
        assert_eq!(
            build_skillpack_brief_query("cli", Some("record_activity")),
            vec![
                ("surface".to_string(), "cli".to_string()),
                ("tool".to_string(), "record_activity".to_string())
            ]
        );
    }

    #[test]
    fn skillpack_brief_markdown_preserves_hash_and_brief() {
        let markdown = format_skillpack_brief_markdown(&json!({
            "schema_version": "agent_skillpack_brief.v1",
            "active_set_id": "kura-core@1",
            "hash": "sha256:abc",
            "surface": "cli",
            "tool": "record_activity",
            "skill_ids": ["record-activity"],
            "brief": "Use the server as source of truth.",
            "compatibility": {"status": "compatible", "notes": ["ok"]}
        }));
        assert!(markdown.contains("active_set_id: `kura-core@1`"));
        assert!(markdown.contains("hash: `sha256:abc`"));
        assert!(markdown.contains("Use the server as source of truth."));
    }
}
