use chrono::{DateTime, FixedOffset, Local, NaiveDateTime};
use crate::wechat::{Wechat};
use json::{object, JsonValue};
use crate::alipay::AliPay;
use crate::ccbc::Ccbc;
use crate::yrcc::Yrcc;

pub mod alipay;
pub mod wechat;
pub mod ccbc;
pub mod yrcc;

#[derive(Clone, Debug)]
pub enum Pay {
    Wechat(Wechat),
    Alipay(AliPay),
    /// 建设银行
    Ccbc(Ccbc),
    /// 农村商业银行
    Yrcc(Yrcc),
    None,
}

impl Pay {
    pub fn new(data: JsonValue) -> Self {
        match data["mode"].as_str().unwrap_or("") {
            "wechat" => {
                Pay::Wechat(Wechat {
                    appid: data["appid"].to_string(),
                    sp_mchid: data["sp_mchid"].to_string(),
                    serial_no: data["serial_no"].to_string(),
                    app_private: data["app_private"].to_string(),
                    apikey: data["apikey"].to_string(),
                    apiv2: data["apiv2"].to_string(),
                    notify_url: data["notify_url"].to_string(),
                })
            }
            "alipay" => Pay::Alipay(AliPay {
                appid: data["appid"].to_string(),
                sp_mchid: data["sp_mchid"].to_string(),
                app_private: data["app_private"].to_string(),
                app_auth_token: data["app_auth_token"].as_str().unwrap_or("").to_string(),
                content_encryp: data["content_encryp"].to_string(),
                alipay_public_key: data["alipay_public_key"].to_string(),
                notify_url: data["notify_url"].to_string(),
                appid_mini: data["appid_mini"].to_string(),
            }),
            "yrcc" => Pay::Yrcc(Yrcc {
                appid: "".to_string(),
                secret: "".to_string(),
                terminal_number: data["terminal_number"].to_string(),
                sp_mchid: data["sp_mchid"].to_string(),
                app_private: data["app_private"].to_string(),
                app_public: data["app_public"].to_string(),
                notify_url: data["notify_url"].as_str().unwrap_or("").to_string(),
                key_name: data["key_name"].to_string(),
                service_provider: data["service_provider"].to_string(),
            }),
            "ccbc" => Pay::Ccbc(Ccbc {
                appid: data["appid"].as_str().unwrap_or("").to_string(),
                branchid: data["branchid"].as_str().unwrap_or("").to_string(),
                appid_subscribe: data["appid_subscribe"].as_str().unwrap_or("").to_string(),
                sp_mchid: data["sp_mchid"].as_str().unwrap_or("").to_string(),
                sp_user_id: data["sp_user_id"].as_str().unwrap_or("").to_string(),
                sp_pass: data["sp_pass"].as_str().unwrap_or("").to_string(),
                sp_posid: data["sp_posid"].as_str().unwrap_or("").to_string(),
                notify_url: data["notify_url"].as_str().unwrap_or("").to_string(),
                sub_posid: data["sub_posid"].as_str().unwrap_or("").to_string(),
                public_key: data["public_key"].as_str().unwrap_or("").to_string(),
                client_ip: data["client_ip"].as_str().unwrap_or("").to_string(),
                retry: 0,
                debug: data["debug"].as_bool().unwrap_or(false),
            }),
            _ => Pay::None
        }
    }
}
impl PayMode for Pay {
    fn check(&mut self) -> Result<bool, String> {
        match self {
            Self::Wechat(e) => e.check(),
            Self::Alipay(e) => e.check(),
            Self::Ccbc(_) => todo!(),
            Self::Yrcc(_) => todo!(),
            Self::None => Err("No login data".to_owned()),
        }
    }

    fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.get_sub_mchid(sub_mchid),
            Self::Alipay(e) => e.get_sub_mchid(sub_mchid),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Ccbc(_) => todo!(),
            &mut Pay::Yrcc(_) => todo!()
        }
    }


    fn config(&mut self) -> JsonValue {
        match self {
            Self::Wechat(e) => object! {
                sp_mchid:e.sp_mchid.clone(),
                appid:e.appid.clone(),
            },
            Self::Alipay(e) => object! {
                appid:e.appid.clone(),
                sp_mchid:e.sp_mchid.clone()
            },
            Pay::None => object! {
                appid:"",
                sp_mchid:""
            },
            Pay::Ccbc(e) => object! {
                appid:e.appid.clone(),
                sp_mchid:e.sp_mchid.clone()
            },
            Pay::Yrcc(e) => object! {
                appid:e.appid.clone(),
                sp_mchid:e.sp_mchid.clone()
            },
        }
    }
    fn pay(&mut self, channel: &str, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.pay(channel,
                                     types,
                                     sub_mchid,
                                     out_trade_no,
                                     description,
                                     total_fee,
                                     sp_openid,
            ),
            Self::Alipay(e) => e.pay(channel,
                                     types,
                                     sub_mchid,
                                     out_trade_no,
                                     description,
                                     total_fee,
                                     sp_openid,
            ),
            Pay::None => Err("No login data".to_owned()),
            Pay::Ccbc(e) => e.pay(
                channel,
                types,
                sub_mchid,
                out_trade_no,
                description,
                total_fee,
                sp_openid,
            ),
            Pay::Yrcc(e) => e.pay(
                channel,
                types,
                sub_mchid,
                out_trade_no,
                description,
                total_fee,
                sp_openid,
            )
        }
    }

    fn micropay(&mut self, channel: &str, auth_code: &str, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, org_openid: &str, ip: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.micropay(
                channel,
                auth_code,
                sub_mchid,
                out_trade_no,
                description,
                total_fee,
                org_openid,
                ip,
            ),
            Self::Alipay(e) => e.micropay(
                channel,
                auth_code,
                sub_mchid,
                out_trade_no,
                description,
                total_fee,
                org_openid,
                ip,
            ),
            Pay::Ccbc(e) => e.micropay(
                channel,
                auth_code,
                sub_mchid,
                out_trade_no,
                description,
                total_fee,
                org_openid,
                ip,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.close(
                out_trade_no, sub_mchid,
            ),
            Self::Alipay(e) => e.close(
                out_trade_no, sub_mchid,
            ),
            Pay::Ccbc(e) => e.close(
                out_trade_no, sub_mchid,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.pay_query(
                out_trade_no,
                sub_mchid,
            ),
            Self::Alipay(e) => e.pay_query(
                out_trade_no,
                sub_mchid,
            ),
            Self::Ccbc(e) => e.pay_query(
                out_trade_no,
                sub_mchid,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Yrcc(_) => todo!()
        }
    }
    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.pay_micropay_query(
                out_trade_no,
                sub_mchid,
                channel,
            ),
            Self::Alipay(e) => e.pay_query(
                out_trade_no,
                sub_mchid,
            ),
            Pay::Ccbc(e) => e.pay_micropay_query(
                out_trade_no,
                sub_mchid,
                channel,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.pay_notify(
                nonce,
                ciphertext,
                associated_data,
            ),
            Self::Alipay(e) => e.pay_notify(
                nonce,
                ciphertext,
                associated_data,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn refund(&mut self, sub_mchid: &str, out_trade_no: &str, transaction_id: &str, out_refund_no: &str, amount: f64, total: f64, currency: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.refund(
                sub_mchid,
                out_trade_no,
                transaction_id,
                out_refund_no,
                amount,
                total,
                currency,
            ),
            Self::Alipay(e) => e.refund(
                sub_mchid,
                out_trade_no,
                transaction_id,
                out_refund_no,
                amount,
                total,
                currency,
            ),
            Self::Ccbc(e) => e.refund(
                sub_mchid,
                out_trade_no,
                transaction_id,
                out_refund_no,
                amount,
                total,
                currency,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn micropay_refund(&mut self, sub_mchid: &str, out_trade_no: &str, transaction_id: &str, out_refund_no: &str, amount: f64, total: f64, currency: &str, refund_text: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.micropay_refund(
                sub_mchid,
                out_trade_no,
                transaction_id,
                out_refund_no,
                amount,
                total,
                currency,
                refund_text,
            ),
            Self::Alipay(e) => e.refund(
                sub_mchid,
                out_trade_no,
                transaction_id,
                out_refund_no,
                amount,
                total,
                currency,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.refund_notify(
                nonce,
                ciphertext,
                associated_data,
            ),
            Self::Alipay(e) => e.refund_notify(
                nonce,
                ciphertext,
                associated_data,
            ),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.refund_query(
                trade_no,
                out_refund_no,
                sub_mchid,
            ),
            Self::Alipay(e) => e.refund_query(
                trade_no,
                out_refund_no,
                sub_mchid,
            ),
            Pay::Ccbc(e) => e.refund_query(
                trade_no,
                out_refund_no,
                sub_mchid),
            Pay::None => Err("No login data".to_owned()),
            &mut Pay::Yrcc(_) => todo!()
        }
    }

    fn incoming(&mut self, business_code: &str, contact_info: JsonValue, subject_info: JsonValue, business_info: JsonValue, settlement_info: JsonValue, bank_account_info: JsonValue) -> Result<JsonValue, String> {
        match self {
            Self::Wechat(e) => e.incoming(
                business_code,
                contact_info,
                subject_info,
                business_info,
                settlement_info,
                bank_account_info,
            ),
            Self::Alipay(e) => e.incoming(
                business_code,
                contact_info,
                subject_info,
                business_info,
                settlement_info,
                bank_account_info,
            ),
            Pay::None => Err("No incoming data".to_owned()),
            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
        }
    }
}

pub trait PayMode {
    /// 检查账户是否通畅
    fn check(&mut self) -> Result<bool, String>;
    /// 获取商户号和状态
    fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String>;
    fn config(&mut self) -> JsonValue;
    #[allow(clippy::too_many_arguments)]
    /// 支付
    /// * channel 渠道 wechat alipay
    /// * out_trade_no 商户订单号
    /// * sub_mchid 子商户号
    /// * description 商品名称
    /// * total_fee 支付金额
    /// * notify_url 通知地址
    /// * sp_openid 支付客户ID
    fn pay(&mut self, channel: &str, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String>;

    #[allow(clippy::too_many_arguments)]
    fn micropay(&mut self, channel: &str, auth_code: &str, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, org_openid: &str, ip: &str) -> Result<JsonValue, String>;
    /// 关闭订单
    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String>;
    /// 订单查询
    /// * out_trade_no 商户订单号
    /// * sub_mchid 子商户号
    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String>;
    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String>;
    /// 支付成功通知
    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String>;

    #[allow(clippy::too_many_arguments)]
    /// 订单退款
    fn refund(&mut self, sub_mchid: &str, out_trade_no: &str, transaction_id: &str, out_refund_no: &str, amount: f64, total: f64, currency: &str) -> Result<JsonValue, String>;
    #[allow(clippy::too_many_arguments)]
    fn micropay_refund(&mut self, sub_mchid: &str, out_trade_no: &str, transaction_id: &str, out_refund_no: &str, amount: f64, total: f64, currency: &str, refund_text: &str) -> Result<JsonValue, String>;
    /// 退款通知
    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String>;
    /// 退款查询
    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String>;
    /// 进件
    fn incoming(&mut self, business_code: &str, contact_info: JsonValue, subject_info: JsonValue, business_info: JsonValue, settlement_info: JsonValue, bank_account_info: JsonValue) -> Result<JsonValue, String>;
}

/// 支付回调数据
#[derive(Debug)]
pub struct PayNotify {
    /// 支付方式
    trade_type: TradeType,
    /// 商户订单号
    out_trade_no: String,
    /// 服务商商户号
    sp_mchid: String,
    /// 子商户号
    sub_mchid: String,
    /// 应用APPID
    sp_appid: String,
    /// 微信内部订单号
    transaction_id: String,
    /// 成功时间
    success_time: i64,
    /// 服务商用户ID
    sp_openid: String,
    /// 子商户用户ID
    sub_openid: String,
    /// 支付金额
    total: f64,
    /// 实际支付金额
    payer_total: f64,
    /// 币种
    currency: String,
    /// 实际支付币种
    payer_currency: String,
    /// 状态
    trade_state: TradeState,
}
impl PayNotify {
    pub fn json(&self) -> JsonValue {
        object! {
            "trade_type" => self.trade_type.to_string(),
            "out_trade_no" => self.out_trade_no.to_string(),
            "sp_mchid" => self.sp_mchid.to_string(),
            "sub_mchid" => self.sub_mchid.to_string(),
            "sp_appid" => self.sp_appid.to_string(),
            "transaction_id" => self.transaction_id.to_string(),
            "success_time" => self.success_time,
            "sp_openid" => self.sp_openid.to_string(),
            "sub_openid" => self.sub_openid.to_string(),
            "total" => self.total,
            "payer_total" => self.payer_total,
            "currency" => self.currency.clone(),
            "payer_currency" => self.payer_currency.clone(),
            "trade_state" => self.trade_state.to_string(),
        }
    }
    pub fn success_time(datetime: &str) -> i64 {
        if datetime.is_empty() {
            return 0;
        }
        let datetime = DateTime::parse_from_rfc3339(datetime).unwrap();
        datetime.timestamp()
    }
    pub fn alipay_time(datetime: &str) -> i64 {
        if datetime.is_empty() {
            return 0;
        }
        let t = NaiveDateTime::parse_from_str(datetime, "%Y-%m-%d %H:%M:%S").unwrap();
        t.and_utc().timestamp()
    }
    pub fn datetime_to_timestamp(datetime: &str, fmt: &str) -> i64 {
        let t = NaiveDateTime::parse_from_str(datetime, fmt).unwrap();
        let tz = FixedOffset::east_opt(Local::now().offset().local_minus_utc()).unwrap();
        t.and_local_timezone(tz).unwrap().timestamp()
    }
}
#[derive(Debug)]
pub enum TradeType {
    /// 小程序与JSAPI
    JSAPI,
    MICROPAY,
    /// 支付方式未知
    None,
}
impl TradeType {
    fn from(code: &str) -> TradeType {
        match code {
            "JSAPI" => TradeType::JSAPI,
            "MICROPAY" => TradeType::MICROPAY,
            _ => TradeType::None
        }
    }
    fn to_string(&self) -> &'static str {
        match self {
            TradeType::JSAPI => "JSAPI",
            TradeType::MICROPAY => "MICROPAY",
            TradeType::None => ""
        }
    }
}
#[derive(Debug)]
pub enum TradeState {
    /// 支付成功
    SUCCESS,
    /// 转入退款
    REFUND,
    /// 未支付
    NOTPAY,
    /// 已关闭
    CLOSED,
    /// 已撤销（仅付款码支付会返回）
    REVOKED,
    /// 用户支付中（仅付款码支付会返回）
    USERPAYING,
    /// 支付失败（仅付款码支付会返回）
    PAYERROR,
    None,
}
impl TradeState {
    fn from(code: &str) -> TradeState {
        match code {
            "SUCCESS" | "TRADE_SUCCESS" | "成功" => TradeState::SUCCESS,
            "REFUND" | "已全额退款" | "已部分退款" => TradeState::REFUND,
            "NOTPAY" | "WAIT_BUYER_PAY" => TradeState::NOTPAY,
            "CLOSED" | "TRADE_CLOSED" => TradeState::CLOSED,
            "REVOKED" => TradeState::REVOKED,
            "USERPAYING" | "待银行确认" => TradeState::USERPAYING,
            "PAYERROR" | "TRADE_FINISHED" | "失败" => TradeState::PAYERROR,
            _ => TradeState::None
        }
    }

    fn to_string(&self) -> &'static str {
        match self {
            TradeState::SUCCESS => "已支付",
            TradeState::REFUND => "已退款",
            TradeState::NOTPAY => "待支付",
            TradeState::CLOSED => "已关闭",
            TradeState::REVOKED => "已关闭",
            TradeState::USERPAYING => "待支付",
            TradeState::PAYERROR => "支付失败",
            TradeState::None => "待支付"
        }
    }
}

#[derive(Debug)]
pub enum RefundStatus {
    /// 退款成功
    SUCCESS,
    /// 退款关闭
    CLOSED,
    /// 退款处理中
    PROCESSING,
    /// 退款异常,手动处理此笔退款
    ABNORMAL,
    None,
}
impl RefundStatus {
    fn from(code: &str) -> RefundStatus {
        match code {
            "SUCCESS" | "Y" | "REFUND_SUCCESS" => RefundStatus::SUCCESS,
            "CLOSED" => RefundStatus::CLOSED,
            "PROCESSING" | "N" => RefundStatus::PROCESSING,
            "ABNORMAL" => RefundStatus::ABNORMAL,
            _ => RefundStatus::None
        }
    }
    fn to_string(&self) -> &'static str {
        match self {
            RefundStatus::SUCCESS => "已退款",
            RefundStatus::None => "未退款",
            RefundStatus::CLOSED => "已关闭",
            RefundStatus::PROCESSING => "退款中",
            RefundStatus::ABNORMAL => "退款异常"
        }
    }
}
/// 退款回调数据
#[derive(Debug)]
pub struct RefundNotify {
    /// 商户订单号
    out_trade_no: String,
    refund_no: String,
    /// 服务商商户号
    sp_mchid: String,
    /// 子商户号
    sub_mchid: String,
    /// 微信内部订单号
    transaction_id: String,
    /// 退款订单号
    refund_id: String,
    /// 成功时间
    success_time: i64,
    /// 支付金额
    total: f64,
    /// 退款金额
    refund: f64,
    /// 实际支付金额
    payer_total: f64,
    /// 实际退款金额
    payer_refund: f64,
    /// 状态
    status: RefundStatus,
}

impl RefundNotify {
    pub fn json(&self) -> JsonValue {
        object! {
            "out_trade_no" => self.out_trade_no.clone(),
            "sp_mchid" => self.sp_mchid.clone(),
            "sub_mchid" => self.sub_mchid.clone(),
            "transaction_id" => self.transaction_id.clone(),
            "success_time" => self.success_time,
            "total" => self.total,
            "refund" => self.refund,
            "payer_total" => self.payer_total,
            "payer_refund" => self.payer_refund,
            "status" => self.status.to_string(),
            "refund_no"=>self.refund_no.clone(),
            "refund_id"=>self.refund_id.clone()
        }
    }
}

#[derive(Debug)]
pub enum Types {
    /// JS支付
    Jsapi,
    /// 扫码支付
    Native,
    /// H5页面
    H5,
    /// 小程序
    MiniJsapi,
    /// App
    App,
    /// 付款码
    Micropay,
}
impl Types {
    pub fn str(self) -> &'static str {
        match self {
            Types::Jsapi => "jsapi",
            Types::Native => "native",
            Types::H5 => "h5",
            Types::MiniJsapi => "minijsapi",
            Types::App => "app",
            Types::Micropay => "micropay"
        }
    }
    pub fn from(name: &str) -> Self {
        match name {
            "jsapi" => Types::Jsapi,
            "native" => Types::Native,
            "h5" => Types::H5,
            "minijsapi" => Types::MiniJsapi,
            "app" => Types::App,
            "micropay" => Types::Micropay,
            _ => Types::Jsapi
        }
    }
}