// {{ display_name }} MCP server — generated by mcpify. Do not hand-edit.

use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};

use async_trait::async_trait;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
use rsa::RsaPrivateKey;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs1v15::Pkcs1v15Sign;
use rsa::pkcs8::DecodePrivateKey;
use sha1::{Digest, Sha1};
use uuid::Uuid;

use super::super::auth_strategy::{AuthConfig, AuthStrategy, Credentials};
use super::super::errors::AuthError;

/// RFC 3986 unreserved characters (`-`, `.`, `_`, `~`) are the only ones
/// OAuth1 percent-encoding (RFC 5849 §3.6) leaves unescaped beyond
/// alphanumerics.
const OAUTH_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
    .remove(b'-')
    .remove(b'.')
    .remove(b'_')
    .remove(b'~');

fn oauth_encode(input: &str) -> String {
    utf8_percent_encode(input, OAUTH_ENCODE_SET).to_string()
}

/// The RSA-SHA1 signing key is supplied as a PEM string via config/stored
/// credentials; accepts either PKCS#1 (`BEGIN RSA PRIVATE KEY`) or PKCS#8
/// (`BEGIN PRIVATE KEY`) framing, since both appear in the wild for the
/// application-owned signing certificates this scheme is typically paired
/// with.
fn parse_private_key(pem: &str) -> anyhow::Result<RsaPrivateKey> {
    RsaPrivateKey::from_pkcs1_pem(pem)
        .or_else(|_| RsaPrivateKey::from_pkcs8_pem(pem))
        .map_err(|_| anyhow::anyhow!("private key is not valid PKCS#1 or PKCS#8 PEM"))
}

/// Computes the per-request `Authorization` header for OAuth1 RSA-SHA1
/// (RFC 5849 §3.4). Unlike the other strategies, OAuth1 signs the method +
/// URL + params on every call, so this can't be precomputed once in
/// `authenticate()` — callers (the generated API client, Story R6) call
/// this immediately before dispatch.
pub fn build_authorization_header(
    consumer_key: &str,
    private_key_pem: &str,
    token: Option<&str>,
    method: &str,
    url: &str,
) -> anyhow::Result<String> {
    let parsed_url = url::Url::parse(url)?;
    let base_url = format!(
        "{}://{}{}",
        parsed_url.scheme(),
        parsed_url
            .host_str()
            .ok_or_else(|| anyhow::anyhow!("URL has no host"))?,
        parsed_url.path()
    );

    let nonce = Uuid::new_v4().simple().to_string();
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
        .to_string();

    let mut params: BTreeMap<String, String> = BTreeMap::new();
    for (key, value) in parsed_url.query_pairs() {
        params.insert(key.to_string(), value.to_string());
    }
    params.insert("oauth_consumer_key".to_string(), consumer_key.to_string());
    params.insert("oauth_nonce".to_string(), nonce.clone());
    params.insert("oauth_signature_method".to_string(), "RSA-SHA1".to_string());
    params.insert("oauth_timestamp".to_string(), timestamp.clone());
    params.insert("oauth_version".to_string(), "1.0".to_string());
    if let Some(token) = token {
        params.insert("oauth_token".to_string(), token.to_string());
    }

    let normalized_params = params
        .iter()
        .map(|(key, value)| format!("{}={}", oauth_encode(key), oauth_encode(value)))
        .collect::<Vec<_>>()
        .join("&");

    let base_string = format!(
        "{}&{}&{}",
        method.to_uppercase(),
        oauth_encode(&base_url),
        oauth_encode(&normalized_params)
    );

    let private_key = parse_private_key(private_key_pem)?;
    let hashed = Sha1::digest(base_string.as_bytes());
    let signature_bytes = private_key.sign(Pkcs1v15Sign::new::<Sha1>(), &hashed)?;
    let signature = STANDARD.encode(signature_bytes);

    let mut header_params: Vec<(&str, String)> = vec![
        ("oauth_consumer_key", consumer_key.to_string()),
        ("oauth_nonce", nonce),
        ("oauth_signature", signature),
        ("oauth_signature_method", "RSA-SHA1".to_string()),
        ("oauth_timestamp", timestamp),
        ("oauth_version", "1.0".to_string()),
    ];
    if let Some(token) = token {
        header_params.push(("oauth_token", token.to_string()));
    }

    let header_value = header_params
        .iter()
        .map(|(key, value)| format!("{key}=\"{}\"", oauth_encode(value)))
        .collect::<Vec<_>>()
        .join(", ");

    Ok(format!("OAuth {header_value}"))
}

#[derive(Debug, Default)]
pub struct OAuth1Strategy;

#[async_trait]
impl AuthStrategy for OAuth1Strategy {
    async fn authenticate(&self, config: &AuthConfig) -> anyhow::Result<Credentials> {
        let consumer_key = config.get("consumer_key").ok_or_else(|| {
            AuthError::MissingCredentials("consumer_key, private_key".to_string())
        })?;
        let private_key = config.get("private_key").ok_or_else(|| {
            AuthError::MissingCredentials("consumer_key, private_key".to_string())
        })?;

        let mut credentials = Credentials::new();
        credentials.insert("consumer_key".to_string(), consumer_key.clone());
        credentials.insert("private_key".to_string(), private_key.clone());
        if let Some(token) = config.get("token") {
            credentials.insert("token".to_string(), token.clone());
        }
        Ok(credentials)
    }

    fn validate_credentials(&self, credentials: &Credentials) -> bool {
        credentials.contains_key("consumer_key") && credentials.contains_key("private_key")
    }
}

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

    fn test_key_pem() -> String {
        use rsa::pkcs1::EncodeRsaPrivateKey;
        let mut rng = rand::thread_rng();
        let key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
        key.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)
            .unwrap()
            .to_string()
    }

    #[test]
    fn oauth_encode_leaves_unreserved_characters_untouched() {
        assert_eq!(oauth_encode("abc123-._~"), "abc123-._~");
        assert_eq!(oauth_encode("a b"), "a%20b");
        assert_eq!(oauth_encode("a/b"), "a%2Fb");
    }

    #[test]
    fn builds_a_well_formed_oauth_authorization_header() {
        let pem = test_key_pem();
        let header = build_authorization_header(
            "consumer-key",
            &pem,
            Some("token-value"),
            "GET",
            "https://api.example.com/widgets?foo=bar",
        )
        .unwrap();

        assert!(header.starts_with("OAuth "));
        assert!(header.contains("oauth_consumer_key=\"consumer-key\""));
        assert!(header.contains("oauth_signature_method=\"RSA-SHA1\""));
        assert!(header.contains("oauth_token=\"token-value\""));
        assert!(header.contains("oauth_signature="));
    }

    #[test]
    fn rejects_a_malformed_private_key() {
        let result = build_authorization_header(
            "consumer-key",
            "not a real key",
            None,
            "GET",
            "https://api.example.com/widgets",
        );
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn authenticate_requires_consumer_key_and_private_key() {
        let strategy = OAuth1Strategy;
        assert!(strategy.authenticate(&AuthConfig::new()).await.is_err());
    }
}
