// src/alipay.rs — 支付宝 RSA2 签名与请求封装
use rsa::pkcs1v15::{Pkcs1v15Sign, SigningKey, VerifyingKey};
use rsa::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, LineEnding};
use rsa::signature::{Signer, Verifier};
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::env;

/// 支付宝配置（从环境变量读取）
pub struct AlipayConfig {
    pub app_id: String,
    pub private_key: String,
    pub alipay_public_key: String,
    pub gateway: String,
}

impl AlipayConfig {
    pub fn from_env() -> Option<Self> {
        let app_id = env::var("ALIPAY_APP_ID").ok()?;
        let private_key = env::var("ALIPAY_PRIVATE_KEY").ok()?;
        let alipay_public_key = env::var("ALIPAY_ALIPAY_PUBLIC_KEY").ok()?;
        let gateway = env::var("ALIPAY_GATEWAY")
            .unwrap_or_else(|_| "https://openapi.alipay.com/gateway.do".to_string());
        Some(Self {
            app_id,
            private_key,
            alipay_public_key,
            gateway,
        })
    }

    pub fn isConfigured() -> bool {
        Self::from_env().is_some()
    }
}

/// 对 map 按 key 排序后构建 query string（用于签名）
fn build_query_string(params: &BTreeMap<&str, &str>) -> String {
    params
        .iter()
        .filter(|(_, v)| !v.is_empty())
        .map(|(k, v)| format!("{}={}", k, urlencoding(v)))
        .collect::<Vec<_>>()
        .join("&")
}

/// URL 编码（简单实现）
fn urlencoding(s: &str) -> String {
    let mut result = String::new();
    for c in s.chars() {
        match c {
            'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => result.push(c),
            _ => {
                for b in c.to_string().as_bytes() {
                    result.push_str(&format!("%{:02X}", b));
                }
            }
        }
    }
    result
}

/// 使用 RSA2 (SHA256) 对内容签名
pub fn rsa2_sign(content: &str, private_key_pem: &str) -> Result<String, String> {
    // 解析 PKCS8 格式的私钥
    // 支持两种格式：
    // 1. PEM 格式字符串 (-----BEGIN PRIVATE KEY-----...)
    // 2. Base64 编码的 PEM 字符串 (用于环境变量配置)
    let private_key = match RsaPrivateKey::from_pkcs8_pem(private_key_pem) {
        Ok(key) => key,
        Err(_) => {
            // 尝试作为 base64 编码的 PEM 解码
            let decoded = base64::Engine::decode(
                &base64::engine::general_purpose::STANDARD,
                private_key_pem,
            )
            .map_err(|e| format!("Base64 解码失败: {}", e))?;

            let pem_str = String::from_utf8(decoded)
                .map_err(|e| format!("PEM 字符串格式错误: {}", e))?;

            RsaPrivateKey::from_pkcs8_pem(&pem_str)
                .map_err(|e| format!("私钥解析失败: {}", e))?
        }
    };

    let signing_key = SigningKey::<Sha256>::new(private_key);
    let signature = signing_key.sign(content.as_bytes());
    Ok(base64::Engine::encode(
        &base64::engine::general_purpose::STANDARD,
        signature.to_bytes().as_ref(),
    ))
}

/// 验证 RSA2 签名
pub fn rsa2_verify(content: &str, sign: &str, public_key_pem: &str) -> Result<bool, String> {
    use sha2::Digest;
    let public_key = RsaPublicKey::from_public_key_pem(public_key_pem)
        .map_err(|e| format!("支付宝公钥解析失败: {}", e))?;

    let sig_bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, sign)
        .map_err(|e| format!("签名 Base64 解码失败: {}", e))?;

    let hashed = Sha256::digest(content.as_bytes());
    public_key
        .verify(rsa::Pkcs1v15Sign::new::<Sha256>(), &hashed, &sig_bytes)
        .map_err(|e| format!("签名验证失败: {}", e))?;
    Ok(true)
}

/// 构建支付宝请求 URL（含签名）
/// 返回 (url, sign)，sign 已 URL 编码
pub fn build_signed_request(
    config: &AlipayConfig,
    biz_content: &str,
    other_params: Option<BTreeMap<&str, &str>>,
) -> Result<(String, String), String> {
    let mut params: BTreeMap<&str, &str> = BTreeMap::new();

    params.insert("app_id", &config.app_id);
    params.insert("method", "alipay.trade.page.pay");
    params.insert("format", "JSON");
    params.insert("charset", "utf-8");
    params.insert("sign_type", "RSA2");
    params.insert(
        "timestamp",
        &chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
    );
    params.insert("version", "1.0");
    params.insert("biz_content", biz_content);

    // 加入其他参数（如 return_url, notify_url）
    if let Some(ref extra) = other_params {
        for (k, v) in extra {
            params.insert(k, v);
        }
    }

    // 按 RFC 3986 编码后拼接待签名内容
    let sign_source: String = params
        .iter()
        .map(|(k, v)| format!("{}={}", k, urlencoding(v)))
        .collect::<Vec<_>>()
        .join("&");

    let sign = rsa2_sign(&sign_source, &config.private_key)?;

    // 构建最终 URL
    let query = build_query_string(&params)
        + "&sign="
        + &urlencoding(&sign);

    let url = config.gateway.clone() + "?" + &query;

    Ok((url, sign))
}

/// 调用支付宝接口并解析响应
pub async fn call_alipay(
    config: &AlipayConfig,
    biz_content: &str,
    other_params: Option<BTreeMap<&str, &str>>,
) -> Result<serde_json::Value, String> {
    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .build()
        .map_err(|e| format!("HTTP 客户端创建失败: {}", e))?;

    let mut params: BTreeMap<&str, &str> = BTreeMap::new();
    params.insert("app_id", &config.app_id);
    params.insert("method", "alipay.trade.page.pay");
    params.insert("format", "JSON");
    params.insert("charset", "utf-8");
    params.insert("sign_type", "RSA2");
    params.insert(
        "timestamp",
        &chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
    );
    params.insert("version", "1.0");
    params.insert("biz_content", biz_content);

    if let Some(ref extra) = other_params {
        for (k, v) in extra {
            params.insert(k, v);
        }
    }

    let sign_source: String = params
        .iter()
        .map(|(k, v)| format!("{}={}", k, urlencoding(v)))
        .collect::<Vec<_>>()
        .join("&");

    let sign = rsa2_sign(&sign_source, &config.private_key)?;

    let query = build_query_string(&params)
        + "&sign="
        + &urlencoding(&sign);

    let url = config.gateway.clone();

    let resp = client
        .post(&url)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .body(query)
        .send()
        .await
        .map_err(|e| format!("请求支付宝失败: {}", e))?;

    let body = resp.text().await.map_err(|e| format!("读取响应失败: {}", e))?;

    // 支付宝返回格式: alipay_trade_page_pay_response={...}&sign=xxx
    let parts: Vec<&str> = body.splitn(2, "&sign=").collect();
    if parts.len() != 2 {
        return Err(format!("支付宝响应格式异常: {}", body));
    }

    let json_str = parts[0]
        .strip_prefix("alipay_trade_page_pay_response=")
        .unwrap_or(parts[0]);

    let sign_from_alipay = parts[1];

    // 验签（确保响应来自支付宝）
    if !rsa2_verify(json_str, sign_from_alipay, &config.alipay_public_key)? {
        return Err("支付宝响应验签失败".to_string());
    }

    let json: serde_json::Value =
        serde_json::from_str(json_str).map_err(|e| format!("JSON 解析失败: {}", e))?;

    if json.get("code").and_then(|v| v.as_str()) != Some("10000") {
        let sub_msg = json.get("sub_msg").and_then(|v| v.as_str()).unwrap_or("");
        return Err(format!(
            "支付宝接口错误: {} - {}",
            json.get("msg").and_then(|v| v.as_str()).unwrap_or("未知"),
            sub_msg
        ));
    }

    Ok(json)
}

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

    #[test]
    fn test_url_encoding() {
        assert_eq!(urlencoding("hello"), "hello");
        assert_eq!(urlencoding("hello world"), "hello%20world");
        assert_eq!(urlencoding("中文"), "%E4%B8%AD%E6%96%87");
    }
}

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

    #[test]
    fn test_openssl_signature_consistency() {
        // This test verifies our signing matches OpenSSL
        // Key from .env (same key as Python test)
        let private_key_pem = std::env::var("TEST_ALIPAY_PRIVATE_KEY")
            .unwrap_or_else(|_| {
                // Default test key - the one we've been using
                "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2lBZ0VBQW9JQ0FRREFRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQovZ2dFQQBFBX".to_string()
            });
        
        let content = "test content for signature comparison";
        
        // Sign with our function
        let result = rsa2_sign(content, &private_key_pem);
        assert!(result.is_ok(), "Signing failed: {:?}", result.err());
        let signature = result.unwrap();
        
        println!("Our signature: {}", &signature[..signature.len().min(50)]);
        
        // To verify this matches OpenSSL, you would need to run:
        // echo -n "test content..." | openssl dgst -sha256 -sign key.pem | base64
    }
}
