Documentation
use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType};
use base64::engine::general_purpose::STANDARD;
use base64::{Engine};
use json::{object, JsonValue};
use std::fs;
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
use aes_gcm::aead::{Aead, Payload};

#[derive(Clone)]
pub struct Wechat {
    /// 服务商APPID
    pub appid: String,
    /// 密钥
    pub secret: String,
    /// 服务商户号
    pub mchid: String,
    /// 证书号
    pub serial_no: String,
    /// 证书路径
    pub cert_path: String,
    /// APIv3密钥
    pub apikey: String,
}

use chrono::{DateTime, Utc};
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::sign::Signer;
use rand::distr::Alphanumeric;
use rand::{rng, Rng};

impl Wechat {
    pub fn sign(&mut self, method: &str, url: &str, body: &str) -> String {
        let timestamp = Utc::now().timestamp(); // 秒级时间戳
        let random_string: String = rng().sample_iter(&Alphanumeric) // 生成随机字母+数字
                                         .take(10) // 指定长度
                                         .map(char::from).collect();

        let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
        // 读取私钥(PEM 格式)
        let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");

        // 加载 RSA 私钥
        let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
        let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
        // 创建签名器
        let mut signer = Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
        // 输入待签名数据
        signer.update(sign_txt.as_bytes()).expect("Failed to update signer");
        // 生成签名
        let signature = signer.sign_to_vec().expect("Failed to sign");
        let signature_b64 = STANDARD.encode(signature);
        let sign = format!(
            r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
            self.mchid.as_str(),
            self.serial_no
        );
        sign
    }

    pub fn paysign(&mut self, prepay_id: &str) -> JsonValue {
        let timestamp = Utc::now().timestamp(); // 秒级时间戳
        let random_string: String = rng().sample_iter(&Alphanumeric) // 生成随机字母+数字
                                         .take(10) // 指定长度
                                         .map(char::from).collect();

        let sign_txt = format!(
            "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
            self.appid
        );
        // 读取私钥(PEM 格式)
        let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");

        // 加载 RSA 私钥
        let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
        let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
        // 创建签名器
        let mut signer = Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
        // 输入待签名数据
        signer.update(sign_txt.as_bytes()).expect("Failed to update signer");
        // 生成签名
        let signature = signer.sign_to_vec().expect("Failed to sign");
        let signature_b64 = STANDARD.encode(signature);
        let sign = signature_b64;
        object! {
            timeStamp:timestamp,
            nonceStr:random_string,
            package:prepay_id,
            signType:"RSA",
            paySign:sign
        }
    }
}
impl PayMode for Wechat {
    fn login(&self, code: &str) -> Result<JsonValue, String> {
        let mut http = br_http::Http::new();
        match http.get(
            "https://api.weixin.qq.com/sns/jscode2session".to_string().as_str(),
        ).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").query(object! {
                appid: self.appid.as_str(),
                secret: self.secret.as_str(),
                js_code:code,
                grant_type:"authorization_code",
            }).json() {
            Ok(e) => Ok(e),
            Err(e) => Err(e),
        }
    }

    fn jsapi(
        &mut self,
        sub_mchid: &str,
        out_trade_no: &str,
        description: &str,
        total_fee: f64,
        notify_url: &str,
        sp_openid: &str,
    ) -> Result<JsonValue, String> {
        let url = "/v3/pay/partner/transactions/jsapi";
        let total = (total_fee * 100.0) as u64;
        let body = object! {
            "sp_appid" => self.appid.clone(),
            "sp_mchid"=> self.mchid.clone(),
            "sub_mchid"=> sub_mchid,
            "description"=>description,
            "out_trade_no"=>out_trade_no,
            "notify_url"=>notify_url,
            "support_fapiao"=>true,
            "amount"=>object! {
                total: total,
                currency:"CNY"
            },
            "payer"=>object! {
                sp_openid:sp_openid
            }
        };
        let sign = self.sign("POST", url, body.to_string().as_str());
        let mut http = br_http::Http::new();
        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).json() {
            Ok(e) => {
                if e.has_key("prepay_id") {
                    let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str());
                    Ok(signinfo)
                } else {
                    Err(e["message"].to_string())
                }
            }
            Err(e) => Err(e),
        }
    }

    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
        let url = format!(
            "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
            out_trade_no, sub_mchid, self.mchid
        );
        let sign = self.sign("GET", url.as_str(), "");
        let mut http = br_http::Http::new();
        match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).json() {
            Ok(e) => {
                if e.has_key("message") {
                    return Err(e["message"].to_string());
                }

                let mut pay_time = 0.0;
                if e.has_key("success_time") {
                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
                    if !success_time.is_empty() {
                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
                        pay_time = datetime.timestamp() as f64;
                    }
                }

                let mut info = object! {
                    pay_time:pay_time,
                    sp_appid:e["sp_appid"].clone(),
                    transaction_id:e["transaction_id"].clone(),
                    sp_mchid:e["sp_mchid"].clone(),
                    sub_mchid:e["sub_mchid"].clone(),
                    order_no:e["out_trade_no"].clone(),
                    status:"",
                    sp_openid:e["payer"]["sp_openid"].clone(),
                    sub_openid:e["payer"]["sub_openid"].clone(),
                    amount_currency:e["amount"]["currency"].clone(),
                    amount_total:e["amount"]["total"].clone(),
                    trade_state_desc:e["trade_state_desc"].clone(),
                    trade_type:e["trade_type"].clone(),
                };
                info["status"] = match e["trade_state"].as_str().unwrap() {
                    "SUCCESS" => "已支付",
                    "REFUND" => "退款",
                    _ => e["trade_state"].as_str().unwrap(),
                }.into();
                Ok(info)
            }
            Err(e) => Err(e),
        }
    }

    fn refund(
        &mut self,
        sub_mchid: &str,
        out_trade_no: &str,
        transaction_id: &str,
        out_refund_no: &str,
        refund: f64,
        total: f64,
        currency: &str,
    ) -> Result<JsonValue, String> {
        let url = "/v3/refund/domestic/refunds";
        let refund = (refund * 100.0) as u64;
        let total = (total * 100.0) as u64;
        let body = object! {
            "sub_mchid"=> sub_mchid,
            "transaction_id"=>transaction_id,
            "out_trade_no"=>out_trade_no,
            "out_refund_no"=>out_refund_no,
            "amount"=>object! {
                refund: refund,
                total: total,
                currency:currency
            }
        };
        let sign = self.sign("POST", url, body.to_string().as_str());
        let mut http = br_http::Http::new();
        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).json() {
            Ok(e) => {
                if e.has_key("message") {
                    return Err(e["message"].to_string());
                }
                let mut refund_time = 0.0;
                if e.has_key("success_time") {
                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
                    if !success_time.is_empty() {
                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
                        refund_time = datetime.timestamp() as f64;
                    }
                }

                let status = match e["status"].as_str().unwrap() {
                    "PROCESSING" => "退款中",
                    "SUCCESS" => "已退款",
                    _ => "无退款",
                };
                let info = object! {
                    refund_id: e["refund_id"].clone(),
                    user_received_account:e["user_received_account"].clone(),
                    status:status,
                    refund_time:refund_time,
                    out_refund_no: e["out_refund_no"].clone(),
                };
                Ok(info)
            }
            Err(e) => Err(e),
        }
    }

    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
        let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
        let cipher = Aes256Gcm::new(key);
        let nonce = Nonce::from_slice(nonce.as_bytes());
        let data = match STANDARD.decode(ciphertext) {
            Ok(e) => e,
            Err(e) => return Err(format!("Invalid data received from API :{}", e))
        };
        // 组合 Payload(带 aad)
        let payload = Payload {
            msg: &data,
            aad: associated_data.as_bytes(),
        };

        // 解密
        let plaintext = match cipher.decrypt(nonce, payload) {
            Ok(e) => e,
            Err(e) => {
                return Err(format!("解密 API:{}", e));
            }
        };
        let rr = match String::from_utf8(plaintext) {
            Ok(d) => d,
            Err(_) => return Err("utf8 error".to_string())
        };
        let json = match json::parse(rr.as_str()) {
            Ok(e) => e,
            Err(_) => return Err("json error".to_string())
        };
        let res = PayNotify {
            trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
            out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
            sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
            sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
            sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
            transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
            success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
            sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
            sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
            total: json["amount"]["total"].as_u64().unwrap() as usize,
            payer_total: json["amount"]["payer_total"].as_u64().unwrap() as usize,
            currency: json["amount"]["currency"].to_string(),
            payer_currency: json["amount"]["payer_currency"].to_string(),
            trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
        };
        Ok(res.json())
    }

    fn refund_query(&mut self, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
        let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={}", sub_mchid);
        let sign = self.sign("GET", url.as_str(), "");
        let mut http = br_http::Http::new();
        match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).json() {
            Ok(e) => {
                if e.has_key("message") {
                    return Err(e["message"].to_string());
                }

                let mut refund_time = 0.0;
                if e.has_key("success_time") {
                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
                    if !success_time.is_empty() {
                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
                        refund_time = datetime.timestamp() as f64;
                    }
                }
                let mut info = object! {
                    refund_time:refund_time,
                    refund_id:e["refund_id"].clone(),
                    refund_no: e["out_refund_no"].clone(),
                    order_no:e["out_trade_no"].clone(),
                    refund_amount: e["amount"]["refund"].clone(),
                    status:"",
                    transaction_id:e["transaction_id"].clone(),
                    amount_currency:e["amount"]["currency"].clone(),
                    amount_total:e["amount"]["total"].clone(),
                    note:e["user_received_account"].clone(),
                };
                info["status"] = match e["status"].as_str().unwrap() {
                    "SUCCESS" => "已退款",
                    "PROCESSING" => "退款中",
                    _ => e["status"].as_str().unwrap(),
                }.into();
                Ok(info)
            }
            Err(e) => Err(e),
        }
    }
    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
        let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
        let cipher = Aes256Gcm::new(key);
        let nonce = Nonce::from_slice(nonce.as_bytes());
        let data = match STANDARD.decode(ciphertext) {
            Ok(e) => e,
            Err(e) => return Err(format!("Invalid data received from API :{}", e))
        };
        // 组合 Payload(带 aad)
        let payload = Payload {
            msg: &data,
            aad: associated_data.as_bytes(),
        };

        // 解密
        let plaintext = match cipher.decrypt(nonce, payload) {
            Ok(e) => e,
            Err(e) => {
                return Err(format!("解密 API:{}", e));
            }
        };
        let rr = match String::from_utf8(plaintext) {
            Ok(d) => d,
            Err(_) => return Err("utf8 error".to_string())
        };
        let json = match json::parse(rr.as_str()) {
            Ok(e) => e,
            Err(_) => return Err("json error".to_string())
        };
        println!("{:#}", json);
        let res = RefundNotify {
            out_trade_no: json["out_trade_no"].to_string(),
            refund_no:  json["out_refund_no"].to_string(),
            refund_id:  json["refund_id"].to_string(),
            sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
            sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
            transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
            success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
            total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
            refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
            payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
            payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
            refund_status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
        };
        Ok(res.json())
    }
}