//! `mcptest prompt` handler.
//!
//! Prints a copy paste ready grounding prompt that operators drop into
//! Claude, Codex, Cursor, or any other LLM assistant to have it generate
//! valid mcptest YAML tests. The prompt is bundled into the binary so a
//! user with the CLI installed and no network access can still pipe it
//! into their assistant of choice.

use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};

/// The bundled prompt content. Single source of truth; updating
/// `default_prompt.md` ships in the next build.
const DEFAULT_PROMPT: &str = include_str!("prompt/default_prompt.md");

/// Args for [`run_prompt`].
///
/// Flat plain-data shape so the CLI module can build it from clap
/// matches without pulling the clap derive surface into this module.
#[derive(Debug, Default, Clone)]
pub struct PromptRunArgs {
    /// Write the prompt to this path instead of stdout.
    pub output: Option<PathBuf>,
}

/// Handle `mcptest prompt`.
///
/// Default: write to stdout. With `--output PATH`: write the file
/// instead. Exit code is 0 on success, non zero on I/O failure.
pub fn run_prompt(args: PromptRunArgs) -> Result<i32> {
    if let Some(path) = args.output {
        fs::write(&path, DEFAULT_PROMPT)
            .with_context(|| format!("could not write prompt to {}", path.display()))?;
        return Ok(0);
    }
    // Use `print!` so existing trailing newline in the bundled file is
    // the only newline we emit. The file ends with one already.
    print!("{DEFAULT_PROMPT}");
    Ok(0)
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    #[test]
    fn default_prompt_is_non_empty() {
        assert!(
            DEFAULT_PROMPT.len() > 1000,
            "bundled prompt should be substantial"
        );
    }

    #[test]
    fn default_prompt_references_canonical_schema_url() {
        assert!(
            DEFAULT_PROMPT.contains("https://mcptest.sh/schema/v1.json"),
            "prompt should advertise the canonical schema URL"
        );
    }

    #[test]
    fn default_prompt_uses_brand_canonicals() {
        assert!(
            DEFAULT_PROMPT.contains("Soap Bucket LLC"),
            "prompt should name the legal entity"
        );
        assert!(
            DEFAULT_PROMPT.contains("download.mcptest.sh"),
            "prompt should point at the canonical download host"
        );
    }

    #[test]
    fn default_prompt_lists_every_shipped_matcher() {
        for matcher in [
            "exact",
            "contains",
            "regex",
            "schema",
            "snapshot",
            "llm-judge",
            "subset",
        ] {
            assert!(
                DEFAULT_PROMPT.contains(matcher),
                "prompt should mention matcher `{matcher}`"
            );
        }
    }

    #[test]
    fn default_prompt_lists_every_shipped_transport() {
        for transport in ["stdio", "streamable-http", "legacy-sse"] {
            assert!(
                DEFAULT_PROMPT.contains(transport),
                "prompt should mention transport `{transport}`"
            );
        }
    }

    #[test]
    fn default_prompt_contains_no_em_dashes() {
        // Belt and suspenders for the workspace em-dash linter; this
        // module's tests fail fast if a future edit slips one in.
        assert!(
            !DEFAULT_PROMPT.contains('\u{2014}'),
            "prompt must not contain em-dashes (AGENTS.md hard rule)"
        );
    }

    #[test]
    fn output_path_writes_the_full_prompt() {
        let dir = tempdir().expect("tempdir");
        let path = dir.path().join("prompt.md");
        let code = run_prompt(PromptRunArgs {
            output: Some(path.clone()),
        })
        .expect("run");
        assert_eq!(code, 0);
        let written = fs::read_to_string(&path).expect("read back");
        assert_eq!(written, DEFAULT_PROMPT);
    }

    #[test]
    fn output_path_in_missing_directory_returns_typed_error() {
        let result = run_prompt(PromptRunArgs {
            output: Some(PathBuf::from("/this/dir/does/not/exist/prompt.md")),
        });
        assert!(result.is_err(), "missing parent dir surfaces as an error");
    }
}
