package wepay

import (
	"context"
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	w "quyun/providers/wechat"

	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/wechat/v3"
	"github.com/go-pay/util/js"
	"github.com/gofiber/fiber/v3"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"go.ipao.vip/atom/container"
	"go.ipao.vip/atom/opt"
)

type Config struct{}

func Provide(opts ...opt.Option) error {
	o := opt.New(opts...)
	var config Config
	if err := o.UnmarshalConfig(&config); err != nil {
		return err
	}
	return container.Container.Provide(func(wechatConfig *w.Config) (*Client, error) {
		client, err := wechat.NewClientV3(
			wechatConfig.Pay.MchID,
			wechatConfig.Pay.SerialNo,
			wechatConfig.Pay.ApiV3Key,
			wechatConfig.Pay.PrivateKey,
		)
		if err != nil {
			return nil, err
		}

		client.DebugSwitch = gopay.DebugOff
		if wechatConfig.DevMode {
			client.DebugSwitch = gopay.DebugOn
		}

		err = client.AutoVerifySignByPublicKey([]byte(wechatConfig.Pay.PublicKey), wechatConfig.Pay.PublicKeyID)
		if err != nil {
			return nil, errors.Wrap(err, "AutoVerifySignByPublicKey")
		}

		return &Client{
			payClient: client,
			config:    wechatConfig,
		}, nil
	}, o.DiOptions()...)
}

type Client struct {
	payClient *wechat.ClientV3
	config    *w.Config
}

func (c *Client) GetClient() *wechat.ClientV3 {
	return c.payClient
}

// WxPublicKeyMap
func (c *Client) WxPublicKeyMap() map[string]*rsa.PublicKey {
	return c.payClient.WxPublicKeyMap()
}

type PrepayData struct {
	client *Client

	AppID    string `json:"app_id"`
	PrepayID string `json:"prepay_id"`
}

// PaySignOfJSAPI
func (pay *PrepayData) PaySignOfJSAPI() (*wechat.JSAPIPayParams, error) {
	return pay.client.payClient.PaySignOfJSAPI(pay.AppID, pay.PrepayID)
}

func (c *Client) Refund(ctx context.Context, f func(*BodyMap)) (*wechat.RefundOrderResponse, error) {
	bm := NewRefundBodyMap(c.config)
	f(bm)

	resp, err := c.payClient.V3Refund(ctx, bm.bm)
	if err != nil {
		return nil, err
	}

	if resp.Code != wechat.Success {
		log.Errorf("WePay Refund error: %s", resp.Error)
		return nil, errors.New(resp.Error)
	}

	return resp.Response, nil
}

func (c *Client) V3TransactionJsapi(ctx context.Context, f func(*BodyMap)) (*PrepayData, error) {
	bm := NewBodyMap(c.config)
	f(bm)

	resp, err := c.payClient.V3TransactionJsapi(ctx, bm.bm)
	if err != nil {
		return nil, err
	}

	if resp.Code != wechat.Success {
		b, _ := json.Marshal(resp)
		log.Errorf("WePay V3TransactionJsapi error: %s", b)
		return nil, errors.New(resp.Error)
	}

	return &PrepayData{
		client: c,

		AppID:    c.config.AppID,
		PrepayID: resp.Response.PrepayId,
	}, nil
}

func (c *Client) ParseNotify(
	ctx fiber.Ctx,
	payCallback func(fiber.Ctx, *wechat.V3DecryptPayResult) error,
	refundCallback func(fiber.Ctx, *wechat.V3DecryptRefundResult) error,
) error {
	body := ctx.Body()
	si := &wechat.SignInfo{
		HeaderTimestamp: ctx.Get(wechat.HeaderTimestamp),
		HeaderNonce:     ctx.Get(wechat.HeaderNonce),
		HeaderSignature: ctx.Get(wechat.HeaderSignature),
		HeaderSerial:    ctx.Get(wechat.HeaderSerial),
		SignBody:        string(body),
	}

	notifyReq := &wechat.V3NotifyReq{SignInfo: si}
	if err := js.UnmarshalBytes(body, notifyReq); err != nil {
		log.Errorf("json unmarshal error:%v", err)
		return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": fmt.Sprintf("json unmarshal error:%v", err)})
	}

	// 获取微信平台证书
	certMap := c.WxPublicKeyMap()

	// 验证异步通知的签名
	if err := notifyReq.VerifySignByPKMap(certMap); err != nil {
		log.Errorf("verify sign error:%v", err)
		return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid signature"})
	}

	// TRANSACTION.SUCCESS ：支付成功通知
	// REFUND.SUCCESS：退款成功通知
	// REFUND.ABNORMAL：退款异常通知
	// REFUND.CLOSED：退款关闭通知
	switch notifyReq.EventType {
	case "TRANSACTION.SUCCESS":
		var notifyData wechat.V3DecryptPayResult
		if err := notifyReq.DecryptCipherTextToStruct(c.config.Pay.ApiV3Key, &notifyData); err != nil {
			return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid cipher text"})
		}
		log.Infof("Successfully decrypted cipher text for pay notify data: %+v", notifyData)
		if err := payCallback(ctx, &notifyData); err != nil {
			log.Errorf("payCallback error:%v", err)
			return err
		}
	case "REFUND.SUCCESS", "REFUND.ABNORMAL", "REFUND.CLOSED":
		var notifyData wechat.V3DecryptRefundResult
		if err := notifyReq.DecryptCipherTextToStruct(c.config.Pay.ApiV3Key, &notifyData); err != nil {
			return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid cipher text"})
		}
		log.Infof("Successfully decrypted cipher text for refund notify data: %+v", notifyData)

		if err := refundCallback(ctx, &notifyData); err != nil {
			log.Errorf("refundCallback error:%v", err)
			return err
		}
	}

	return ctx.Status(http.StatusOK).JSON(&wechat.V3NotifyRsp{
		Code:    gopay.SUCCESS,
		Message: "成功",
	})
}

type BodyMap struct {
	bm gopay.BodyMap
}

func NewRefundBodyMap(c *w.Config) *BodyMap {
	bm := make(gopay.BodyMap)
	bm.Set("notify_url", c.Pay.NotifyURL)
	return &BodyMap{
		bm: bm,
	}
}

func NewBodyMap(c *w.Config) *BodyMap {
	bm := make(gopay.BodyMap)
	bm.Set("appid", c.AppID).
		Set("mchid", c.Pay.MchID).
		Set("notify_url", c.Pay.NotifyURL)
		// .
		// SetBodyMap("amount", func(bm gopay.BodyMap) {
		// 	bm.Set("total", 1).
		// 		Set("currency", "CNY")
		// })
	return &BodyMap{
		bm: bm,
	}
}

func (b *BodyMap) Set(key string, value interface{}) *BodyMap {
	b.bm.Set(key, value)
	return b
}

func (b *BodyMap) SetBodyMap(key string, f func(bm gopay.BodyMap)) *BodyMap {
	b.bm.SetBodyMap(key, f)
	return b
}

// Expire time
func (b *BodyMap) Expire(t time.Duration) *BodyMap {
	return b.Set("time_expire", time.Now().Add(t).Format(time.RFC3339))
}

// Description
func (b *BodyMap) Description(desc string) *BodyMap {
	return b.Set("description", desc)
}

// OutTradeNo
func (b *BodyMap) OutTradeNo(outTradeNo string) *BodyMap {
	return b.Set("out_trade_no", outTradeNo)
}

// TransactionID
func (b *BodyMap) TransactionID(transactionID string) *BodyMap {
	return b.Set("transaction_id", transactionID)
}

// OutRefundNo
func (b *BodyMap) OutRefundNo(outRefundNo string) *BodyMap {
	return b.Set("out_refund_no", outRefundNo)
}

// RefundReason
func (b *BodyMap) RefundReason(refundReason string) *BodyMap {
	return b.Set("reason", refundReason)
}

// RefundAmount
func (b *BodyMap) RefundAmount(total, refund int64, currency CURRENCY) *BodyMap {
	return b.SetBodyMap("amount", func(bm gopay.BodyMap) {
		bm.
			Set("total", total).
			Set("refund", refund).
			Set("currency", currency.String())
	})
}

func (b *BodyMap) CNYRefundAmount(total, refund int64) *BodyMap {
	return b.RefundAmount(total, refund, CNY)
}

type RefundGoodsInfo struct {
	MerchantGoodsID string `json:"merchant_goods_id"`
	GoodsName       string `json:"goods_name"`
	RefundQuantity  int64  `json:"refund_quantity"`
	RefundAmount    int64  `json:"refund_amount"`
	UnitPrice       int64  `json:"unit_price"`
}

// RefundGoodsInfo
func (b *BodyMap) RefundGoods(goods []RefundGoodsInfo) *BodyMap {
	return b.Set("goods_detail", goods)
}

// Amount
func (b *BodyMap) Amount(total int64, currency CURRENCY) *BodyMap {
	return b.SetBodyMap("amount", func(bm gopay.BodyMap) {
		bm.
			Set("total", total).
			Set("currency", currency.String())
	})
}

func (b *BodyMap) CNYAmount(total int64) *BodyMap {
	return b.Amount(total, CNY)
}

type GoodsInfo struct {
	MerchantGoodsID string `json:"merchant_goods_id"`
	GoodsName       string `json:"goods_name"`
	Quantity        int64  `json:"quantity"`
	UnitPrice       int64  `json:"unit_price"`
}

func (b *BodyMap) Detail(goods []GoodsInfo) *BodyMap {
	return b.SetBodyMap("detail", func(bm gopay.BodyMap) {
		bm.Set("goods_detail", goods)
	})
}

// Payer
func (b *BodyMap) Payer(spOpenId string) *BodyMap {
	return b.SetBodyMap("payer", func(bm gopay.BodyMap) {
		bm.Set("openid", spOpenId)
	})
}

// SubMchId
func (b *BodyMap) SubMchId(subMchId string) *BodyMap {
	return b.Set("sub_mchid", subMchId)
}

type CURRENCY string

func (c CURRENCY) String() string {
	return string(c)
}

const (
	CNY CURRENCY = "CNY"
	USD CURRENCY = "USD"
	EUR CURRENCY = "EUR"
)
