//! Handler for `mcptest web-bot-auth`.
//!
//! The only subcommand prints the public-key directory document for a
//! configured signing key. mcptest is a client; this helps an operator who
//! runs a bot publish the `.well-known/http-message-signatures-directory`
//! document so verifiers find the bot's PUBLIC key. The private key is read,
//! the public key derived, and only the public material printed.

use anyhow::{Context, Result};
use mcptest_core::auth::{web_bot_auth_directory_json, SignatureAlgorithm, WebBotAuthConfig};

use crate::cli::args::{DirectoryArgs, WebBotAuthArgs, WebBotAuthCommand};

/// Dispatch a `mcptest web-bot-auth` subcommand. Returns the process exit
/// code (`0` on success).
pub(crate) fn web_bot_auth_command(args: WebBotAuthArgs) -> Result<i32> {
    match args.command {
        WebBotAuthCommand::Directory(dir_args) => print_directory(dir_args),
    }
}

/// Build the directory JSON for the supplied key and print it to stdout.
fn print_directory(args: DirectoryArgs) -> Result<i32> {
    let algorithm = parse_algorithm(&args.algorithm)?;
    let config = build_config(&args, algorithm)?;
    let json = web_bot_auth_directory_json(&config)
        .context("could not build the Web Bot Auth public-key directory")?;
    println!("{json}");
    Ok(0)
}

/// Assemble the config the directory builder validates. The `directory_url`
/// is a placeholder here because the JWK Set does not embed it; the builder
/// only needs a key source, an algorithm, and a non-empty agent + URL to
/// pass its shared validator.
fn build_config(args: &DirectoryArgs, algorithm: SignatureAlgorithm) -> Result<WebBotAuthConfig> {
    if args.key.is_some() == args.key_env.is_some() {
        anyhow::bail!("set exactly one of --key or --key-env");
    }
    Ok(WebBotAuthConfig {
        key_path: args.key.clone(),
        key_env: args.key_env.clone(),
        directory_url: "https://mcptest.sh/.well-known/http-message-signatures-directory"
            .to_string(),
        signature_agent: args.agent.clone(),
        algorithm,
        components: vec!["@authority".to_string()],
        expires_in_seconds: 30,
    })
}

/// Map the `--algorithm` flag to the typed enum, mirroring the YAML spelling.
fn parse_algorithm(name: &str) -> Result<SignatureAlgorithm> {
    match name {
        "ed25519" => Ok(SignatureAlgorithm::Ed25519),
        "rsa-pss" => Ok(SignatureAlgorithm::RsaPss),
        other => anyhow::bail!(
            "--algorithm `{other}` is not recognized; expected `ed25519` or `rsa-pss`"
        ),
    }
}

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

    fn args_with_env(env_name: &str, algorithm: &str) -> DirectoryArgs {
        DirectoryArgs {
            key: None,
            key_env: Some(env_name.to_string()),
            algorithm: algorithm.to_string(),
            agent: "https://mcptest.sh".to_string(),
        }
    }

    #[test]
    fn rejects_both_key_sources() {
        let args = DirectoryArgs {
            key: Some("/tmp/a.key".into()),
            key_env: Some("KEY_ENV".into()),
            algorithm: "ed25519".into(),
            agent: "https://mcptest.sh".into(),
        };
        let err = build_config(&args, SignatureAlgorithm::Ed25519).unwrap_err();
        assert!(err.to_string().contains("exactly one"));
    }

    #[test]
    fn rejects_no_key_source() {
        let args = DirectoryArgs {
            key: None,
            key_env: None,
            algorithm: "ed25519".into(),
            agent: "https://mcptest.sh".into(),
        };
        let err = build_config(&args, SignatureAlgorithm::Ed25519).unwrap_err();
        assert!(err.to_string().contains("exactly one"));
    }

    #[test]
    fn rejects_unknown_algorithm() {
        let err = parse_algorithm("nonsense").unwrap_err();
        assert!(err.to_string().contains("not recognized"));
    }

    #[test]
    fn ed25519_directory_prints_okp_jwk() {
        use ed25519_dalek::pkcs8::EncodePrivateKey;
        let key = ed25519_dalek::SigningKey::from_bytes(&[3u8; 32]);
        let pem = key
            .to_pkcs8_pem(ed25519_dalek::pkcs8::spki::der::pem::LineEnding::LF)
            .unwrap()
            .to_string();
        let env_name = format!("MCPTEST_HANDLER_DIR_{}", std::process::id());
        std::env::set_var(&env_name, pem);
        let args = args_with_env(&env_name, "ed25519");
        let config = build_config(&args, SignatureAlgorithm::Ed25519).unwrap();
        let json = web_bot_auth_directory_json(&config);
        std::env::remove_var(&env_name);
        let json = json.expect("directory renders");
        assert!(json.contains("\"kty\": \"OKP\""));
        assert!(json.contains("\"crv\": \"Ed25519\""));
        assert!(!json.contains("PRIVATE KEY"));
    }
}
