package wxpayapi

import (
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"git.rosy.net.cn/baseapi/utils"
	"github.com/clbanning/mxj"
	"github.com/nanjishidu/gomini/gocrypto"
)

type CData string

func (c CData) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	return e.EncodeElement(struct {
		string `xml:",cdata"`
	}{string(c)}, start)
}

const (
	MsgTypeUnkown = 0 // 未知
	MsgTypePay    = 1 // 支付结果回调
	MsgTypeRefund = 2 // 退款结果回调
)

type CallbackResponse struct {
	XMLName xml.Name `json:"-" xml:"xml"`

	ReturnCode string `json:"return_code" xml:"return_code"`
	ReturnMsg  CData  `json:"return_msg" xml:"return_msg"`
}

type BaseResultMsg struct {
	ReturnCode string `json:"return_code" xml:"return_code"`
	ReturnMsg  string `json:"return_msg" xml:"return_msg"`

	AppID      string `json:"appid" xml:"appid"`
	DeviceInfo string `json:"device_info,omitempty" xml:"device_info,omitempty"`
	MchID      string `json:"mch_id" xml:"mch_id"`
	NonceStr   string `json:"nonce_str" xml:"nonce_str"`
	Sign       string `json:"sign" xml:"sign"`
	ResultCode string `json:"result_code" xml:"result_code"`
	ResultMsg  string `json:"result_msg" xml:"result_msg"`
	ErrCode    string `json:"err_code,omitempty" xml:"err_code,omitempty"`
	ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"`
}

type PayResultMsg struct {
	ReturnCode string `json:"return_code" xml:"return_code"`
	ReturnMsg  string `json:"return_msg" xml:"return_msg"`

	AppID      string `json:"appid" xml:"appid"`
	DeviceInfo string `json:"device_info,omitempty" xml:"device_info,omitempty"`
	MchID      string `json:"mch_id" xml:"mch_id"`
	NonceStr   string `json:"nonce_str" xml:"nonce_str"`
	Sign       string `json:"sign" xml:"sign"`
	ResultCode string `json:"result_code" xml:"result_code"`
	ResultMsg  string `json:"result_msg" xml:"result_msg"`
	ErrCode    string `json:"err_code,omitempty" xml:"err_code,omitempty"`
	ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"`

	BankType      string `json:"bank_type"`
	CashFee       string `json:"cash_fee"`
	FeeType       string `json:"fee_type"`
	IsSubscribe   string `json:"is_subscribe"`
	OpenID        string `json:"openid"`
	OutTradeNo    string `json:"out_trade_no"`
	TimeEnd       string `json:"time_end"`
	TotalFee      string `json:"total_fee"`
	TradeType     string `json:"trade_type"`
	TransactionID string `json:"transaction_id"`
}

type RefundReqInfo struct {
	OutRefundNo         string `json:"out_refund_no"`
	OutTradeNo          string `json:"out_trade_no"`
	RefundAccount       string `json:"refund_account"`
	RefundFee           string `json:"refund_fee" xml:"refund_fee"`
	RefundID            string `json:"refund_id"`
	RefundRecvAccout    string `json:"refund_recv_accout"`
	RefundRequestSource string `json:"refund_request_source"`
	RefundStatus        string `json:"refund_status"`
	SettlementRefundFee string `json:"settlement_refund_fee"`
	SettlementTotalFee  string `json:"settlement_total_fee"`
	SuccessTime         string `json:"success_time"`
	TotalFee            string `json:"total_fee"`
	TransactionID       string `json:"transaction_id"`
}

type RefundResultMsg struct {
	ReturnCode string `json:"return_code" xml:"return_code"`
	ReturnMsg  string `json:"return_msg" xml:"return_msg"`

	AppID      string `json:"appid" xml:"appid"`
	DeviceInfo string `json:"device_info,omitempty" xml:"device_info,omitempty"`
	MchID      string `json:"mch_id" xml:"mch_id"`
	NonceStr   string `json:"nonce_str" xml:"nonce_str"`
	Sign       string `json:"sign" xml:"sign"`
	ResultCode string `json:"result_code" xml:"result_code"`
	ResultMsg  string `json:"result_msg" xml:"result_msg"`
	ErrCode    string `json:"err_code,omitempty" xml:"err_code,omitempty"`
	ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"`

	ReqInfoStr string         `json:"req_info,omitempty"`
	ReqInfoObj *RefundReqInfo `json:"req_info_obj,omitempty"`
}

type CallbackMsg struct {
	MsgType int
	MapData map[string]interface{}
	Data    interface{}
}

var (
	SuccessResponse = &CallbackResponse{
		ReturnCode: ResponseCodeSuccess,
		ReturnMsg:  "OK",
	}
)

func Err2CallbackResponse(err error, data string) *CallbackResponse {
	if err == nil {
		return SuccessResponse
	}
	returnMsg := err.Error()
	if data != "" {
		returnMsg = fmt.Sprintf("%s,%s", returnMsg, data)
	}
	return &CallbackResponse{
		ReturnCode: ResponseCodeFail,
		ReturnMsg:  CData(returnMsg),
	}
}

func (a *API) decodeReqInfo(msg string) (decryptedMsg string, err error) {
	binMsg, err := base64.StdEncoding.DecodeString(msg)
	if err == nil {
		// aesKey := []byte(fmt.Sprintf("%x", md5.Sum([]byte(a.appKey))))
		// binResult, err2 := utils.AESCBCDecpryt(binMsg, aesKey, aesKey[:16])
		gocrypto.SetAesKey(strings.ToLower(gocrypto.Md5(a.appKey)))
		binResult, err2 := gocrypto.AesECBDecrypt(binMsg)
		if err = err2; err == nil {
			decryptedMsg = string(binResult)
		}
	}
	return decryptedMsg, err
}

func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackResponse *CallbackResponse) {
	data, err := ioutil.ReadAll(request.Body)
	if err != nil {
		return nil, Err2CallbackResponse(err, "")
	}
	return a.getCallbackMsg(string(data))
}

func (a *API) getCallbackMsg(msgBody string) (msg *CallbackMsg, callbackResponse *CallbackResponse) {
	mapData, _, err := a.parseXmlStrAsMap(msgBody)
	if err != nil {
		return nil, Err2CallbackResponse(err, "")
	}
	returnCode := utils.Interface2String(mapData["return_code"])
	if returnCode != ResponseCodeSuccess { // 如果return_code出错，直接返回
		return nil, SuccessResponse
	}

	reqInfo := utils.Interface2String(mapData["req_info"])
	transactionID := utils.Interface2String(mapData["transaction_id"])
	if reqInfo == "" && transactionID != "" { // 对于支付结果通知进行签名验证（退款结果通知不支持验证）
		sigType := utils.Interface2String(mapData[sigTypeKey])
		sign := utils.Interface2String(mapData[sigKey])
		desiredSign := a.signParam(sigType, mapData)
		if desiredSign != sign {
			return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", desiredSign, sign), "")
		}
	}

	msg = &CallbackMsg{
		MapData: mapData,
	}
	if reqInfo != "" {
		msg.MsgType = MsgTypeRefund
		var refundResult *RefundResultMsg
		if err = utils.Map2StructByJson(mapData, &refundResult, false); err == nil {
			if reqInfo, err = a.decodeReqInfo(reqInfo); err == nil {
				mv, err2 := mxj.NewMapXml([]byte(reqInfo))
				if err = err2; err == nil {
					reqInfoMap := mv["root"].(map[string]interface{})
					if err = utils.Map2StructByJson(reqInfoMap, &refundResult.ReqInfoObj, false); err == nil {
						msg.Data = refundResult
					}
				}
			}
		}
	} else if transactionID != "" {
		msg.MsgType = MsgTypePay
		var payResult *PayResultMsg
		if err = utils.Map2StructByJson(mapData, &payResult, false); err == nil {
			msg.Data = payResult
		}
	}
	if err != nil {
		callbackResponse = Err2CallbackResponse(err, "")
	}
	return msg, callbackResponse
}
