package order

import (
	"encoding/xml"
	"errors"
	"strconv"
	"strings"
	"time"

	"github.com/silenceper/wechat/v2/pay/config"
	"github.com/silenceper/wechat/v2/util"
)

// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"

// SUCCESS 表示支付成功
const SUCCESS = "SUCCESS"

// Order struct extends context
type Order struct {
	*config.Config
}

// NewOrder return an instance of order package
func NewOrder(cfg *config.Config) *Order {
	order := Order{cfg}
	return &order
}

// Params was NEEDED when request Unified order
// 传入的参数，用于生成 prepay_id 的必需参数
type Params struct {
	TotalFee   string
	CreateIP   string
	Body       string
	OutTradeNo string
	TimeExpire string // 订单失效时间，格式为yyyyMMddHHmmss，如2009年12月27日9点10分10秒表示为20091227091010。
	OpenID     string
	TradeType  string
	SignType   string
	Detail     string
	Attach     string
	GoodsTag   string
	NotifyURL  string
}

// Config 是传出用于 js sdk 用的参数
type Config struct {
	Timestamp string `json:"timestamp"`
	NonceStr  string `json:"nonceStr"`
	PrePayID  string `json:"prePayId"`
	SignType  string `json:"signType"`
	Package   string `json:"package"`
	PaySign   string `json:"paySign"`
}

// ConfigForApp 是传出用于 app sdk 用的参数
type ConfigForApp struct {
	AppID     string `json:"appid"`
	MchID     string `json:"partnerid"` // 微信支付分配的商户号
	PrePayID  string `json:"prepayid"`
	Package   string `json:"package"`
	NonceStr  string `json:"nonceStr"`
	Timestamp string `json:"timestamp"`
	Sign      string `json:"sign"`
}

// PreOrder 是 Unified order 接口的返回
type PreOrder struct {
	ReturnCode string `xml:"return_code"`
	ReturnMsg  string `xml:"return_msg"`
	AppID      string `xml:"appid,omitempty"`
	MchID      string `xml:"mch_id,omitempty"`
	NonceStr   string `xml:"nonce_str,omitempty"`
	Sign       string `xml:"sign,omitempty"`
	ResultCode string `xml:"result_code,omitempty"`
	TradeType  string `xml:"trade_type,omitempty"`
	PrePayID   string `xml:"prepay_id,omitempty"`
	CodeURL    string `xml:"code_url,omitempty"`
	MWebURL    string `xml:"mweb_url,omitempty"`
	ErrCode    string `xml:"err_code,omitempty"`
	ErrCodeDes string `xml:"err_code_des,omitempty"`
}

// payRequest 接口请求参数
type payRequest struct {
	AppID          string `xml:"appid"`                 // 公众账号ID
	MchID          string `xml:"mch_id"`                // 商户号
	DeviceInfo     string `xml:"device_info,omitempty"` // 设备号
	NonceStr       string `xml:"nonce_str"`             // 随机字符串
	Sign           string `xml:"sign"`                  // 签名
	SignType       string `xml:"sign_type,omitempty"`   // 签名类型
	Body           string `xml:"body"`                  // 商品描述
	Detail         string `xml:"detail,omitempty"`      // 商品详情
	Attach         string `xml:"attach,omitempty"`      // 附加数据
	OutTradeNo     string `xml:"out_trade_no"`          // 商户订单号
	FeeType        string `xml:"fee_type,omitempty"`    // 标价币种
	TotalFee       string `xml:"total_fee"`             // 标价金额
	SpbillCreateIP string `xml:"spbill_create_ip"`      // 终端IP
	TimeStart      string `xml:"time_start,omitempty"`  // 交易起始时间
	TimeExpire     string `xml:"time_expire,omitempty"` // 交易结束时间
	GoodsTag       string `xml:"goods_tag,omitempty"`   // 订单优惠标记
	NotifyURL      string `xml:"notify_url"`            // 通知地址
	TradeType      string `xml:"trade_type"`            // 交易类型
	ProductID      string `xml:"product_id,omitempty"`  // 商品ID
	LimitPay       string `xml:"limit_pay,omitempty"`   // 指定支付方式
	OpenID         string `xml:"openid,omitempty"`      // 用户标识
	SceneInfo      string `xml:"scene_info,omitempty"`  // 场景信息

	XMLName struct{} `xml:"xml"`
}

func (req *payRequest) BridgePayRequest(p *Params, AppID, MchID, nonceStr, sign string) *payRequest {
	request := payRequest{
		AppID:          AppID,
		MchID:          MchID,
		NonceStr:       nonceStr,
		Sign:           sign,
		Body:           p.Body,
		OutTradeNo:     p.OutTradeNo,
		TotalFee:       p.TotalFee,
		SpbillCreateIP: p.CreateIP,
		NotifyURL:      p.NotifyURL,
		TradeType:      p.TradeType,
		OpenID:         p.OpenID,
		SignType:       p.SignType,
		Detail:         p.Detail,
		Attach:         p.Attach,
		GoodsTag:       p.GoodsTag,
	}
	return &request
}

// BridgeConfig get js bridge config
func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
	var (
		buffer    strings.Builder
		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
	)
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}
	buffer.WriteString("appId=")
	buffer.WriteString(order.AppID)
	buffer.WriteString("&nonceStr=")
	buffer.WriteString(order.NonceStr)
	buffer.WriteString("&package=")
	buffer.WriteString("prepay_id=" + order.PrePayID)
	buffer.WriteString("&signType=")
	buffer.WriteString(p.SignType)
	buffer.WriteString("&timeStamp=")
	buffer.WriteString(timestamp)
	buffer.WriteString("&key=")
	buffer.WriteString(o.Key)

	sign, err := util.CalculateSign(buffer.String(), p.SignType, o.Key)
	if err != nil {
		return
	}
	// 签名
	cfg.PaySign = sign
	cfg.NonceStr = order.NonceStr
	cfg.Timestamp = timestamp
	cfg.PrePayID = order.PrePayID
	cfg.SignType = p.SignType
	cfg.Package = "prepay_id=" + order.PrePayID
	return
}

// BridgeAppConfig get app bridge config
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
	var (
		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
		noncestr  = util.RandomStr(32)
		_package  = "Sign=WXPay"
	)
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}

	result := map[string]string{
		"appid":     order.AppID,
		"partnerid": order.MchID,
		"prepayid":  order.PrePayID,
		"package":   _package,
		"noncestr":  noncestr,
		"timestamp": timestamp,
	}
	// 签名
	sign, err := util.ParamSign(result, o.Key)
	if err != nil {
		return
	}
	result["sign"] = sign
	cfg = ConfigForApp{
		AppID:     result["appid"],
		MchID:     result["partnerid"],
		PrePayID:  result["prepayid"],
		Package:   result["package"],
		NonceStr:  result["noncestr"],
		Timestamp: result["timestamp"],
		Sign:      result["sign"],
	}
	return
}

// PrePayOrder return data for invoke wechat payment
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
	nonceStr := util.RandomStr(32)

	// 通知地址
	if len(p.NotifyURL) == 0 {
		p.NotifyURL = o.NotifyURL // 默认使用order.NotifyURL
	}

	// 签名类型
	if p.SignType == "" {
		p.SignType = util.SignTypeMD5
	}

	param := map[string]string{
		"appid":            o.AppID,
		"body":             p.Body,
		"mch_id":           o.MchID,
		"nonce_str":        nonceStr,
		"out_trade_no":     p.OutTradeNo,
		"spbill_create_ip": p.CreateIP,
		"total_fee":        p.TotalFee,
		"trade_type":       p.TradeType,
		"openid":           p.OpenID,
		"sign_type":        p.SignType,
		"detail":           p.Detail,
		"attach":           p.Attach,
		"goods_tag":        p.GoodsTag,
		"notify_url":       p.NotifyURL,
	}

	if p.TimeExpire != "" {
		// 如果有传入交易结束时间
		param["time_expire"] = p.TimeExpire
	}

	sign, err := util.ParamSign(param, o.Key)
	if err != nil {
		return
	}
	request := new(payRequest).BridgePayRequest(p, o.AppID, o.MchID, nonceStr, sign)
	if len(p.TimeExpire) > 0 {
		// 如果有传入交易结束时间
		request.TimeExpire = p.TimeExpire
	}
	rawRet, err := util.PostXML(payGateway, request)
	if err != nil {
		return
	}
	err = xml.Unmarshal(rawRet, &payOrder)
	if err != nil {
		return
	}
	if payOrder.ReturnCode == SUCCESS {
		// pay success
		if payOrder.ResultCode == SUCCESS {
			err = nil
			return
		}
		err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
		return
	}
	err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
	return
}

// PrePayID will request wechat merchant api and request for a pre payment order id
func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}
	if order.PrePayID == "" {
		err = errors.New("empty prepayid")
	}
	prePayID = order.PrePayID
	return
}
