package pay

import (
	"bindbox-game/internal/service/sysconfig"
	"context"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"sync"

	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/core/option"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
	refundsvc "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
)

type WechatPayClient struct {
	client *core.Client
}

// 单例模式：避免每次请求都重新初始化客户端
var (
	clientInstance *WechatPayClient
	clientOnce     sync.Once
	clientErr      error
)

// LoadPrivateKeyFromBase64 从 Base64 编码的私钥内容创建 RSA 私钥
func LoadPrivateKeyFromBase64(base64Key string) (*rsa.PrivateKey, error) {
	// 解码 Base64
	keyBytes, err := base64.StdEncoding.DecodeString(base64Key)
	if err != nil {
		return nil, errors.New("failed to decode base64 private key: " + err.Error())
	}

	// 解析 PEM
	block, _ := pem.Decode(keyBytes)
	if block == nil {
		return nil, errors.New("invalid private key PEM format")
	}

	// 尝试 PKCS8 格式
	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		// 尝试 PKCS1 格式
		rsaKey, err2 := x509.ParsePKCS1PrivateKey(block.Bytes)
		if err2 != nil {
			return nil, errors.New("failed to parse private key: " + err.Error())
		}
		return rsaKey, nil
	}

	rsaKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, errors.New("private key is not RSA type")
	}
	return rsaKey, nil
}

// LoadPublicKeyFromBase64 从 Base64 编码的公钥内容创建 RSA 公钥
func LoadPublicKeyFromBase64(base64Key string) (*rsa.PublicKey, error) {
	keyBytes, err := base64.StdEncoding.DecodeString(base64Key)
	if err != nil {
		return nil, errors.New("failed to decode base64 public key: " + err.Error())
	}

	block, _ := pem.Decode(keyBytes)
	if block == nil {
		return nil, errors.New("invalid public key PEM format")
	}

	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, errors.New("failed to parse public key: " + err.Error())
	}

	rsaPub, ok := pub.(*rsa.PublicKey)
	if !ok {
		return nil, errors.New("public key is not RSA type")
	}
	return rsaPub, nil
}

// NewWechatPayClient 获取微信支付客户端（单例模式）
// 首次调用会初始化客户端，后续调用直接返回缓存的实例
func NewWechatPayClient(ctx context.Context) (*WechatPayClient, error) {
	clientOnce.Do(func() {
		clientInstance, clientErr = initWechatPayClient(ctx)
	})
	if clientErr != nil {
		return nil, clientErr
	}
	return clientInstance, nil
}

// initWechatPayClient 初始化微信支付客户端（内部实现）
// 优先使用动态配置中的 Base64 私钥内容，fallback 到静态配置的文件路径
func initWechatPayClient(ctx context.Context) (*WechatPayClient, error) {
	// 必须从动态配置获取
	var dynamicCfg *sysconfig.WechatPayConfig
	if dc := sysconfig.GetDynamicConfig(); dc != nil {
		cfg := dc.GetWechatPay(ctx)
		dynamicCfg = &cfg
	}

	if dynamicCfg == nil {
		return nil, errors.New("wechat pay dynamic config missing")
	}

	mchID := dynamicCfg.MchID
	serialNo := dynamicCfg.SerialNo
	apiV3Key := dynamicCfg.ApiV3Key

	if apiV3Key == "" {
		return nil, errors.New("wechat pay config incomplete: api_v3_key missing")
	}

	if mchID == "" || serialNo == "" {
		return nil, errors.New("wechat pay config incomplete: mchid or serial_no missing")
	}

	// 加载私钥：动态配置 Base64 内容
	var mchPrivateKey *rsa.PrivateKey
	var err error
	if dynamicCfg.PrivateKey != "" {
		mchPrivateKey, err = LoadPrivateKeyFromBase64(dynamicCfg.PrivateKey)
		if err != nil {
			return nil, errors.New("read private key from dynamic config err:" + err.Error())
		}
	} else {
		return nil, errors.New("wechat pay private key not configured")
	}

	// 构建客户端选项
	var opts []core.ClientOption

	// 检查是否有公钥配置（新版验签方式）
	publicKeyID := dynamicCfg.PublicKeyID

	if publicKeyID != "" {
		// 使用公钥验签模式
		var pubKey *rsa.PublicKey
		if dynamicCfg.PublicKey != "" {
			pubKey, err = LoadPublicKeyFromBase64(dynamicCfg.PublicKey)
			if err != nil {
				return nil, errors.New("read public key from dynamic config err:" + err.Error())
			}
		} else {
			return nil, errors.New("wechat pay public key not configured")
		}
		opts = []core.ClientOption{option.WithWechatPayPublicKeyAuthCipher(mchID, serialNo, mchPrivateKey, publicKeyID, pubKey)}
	} else {
		// 使用自动证书模式
		opts = []core.ClientOption{option.WithWechatPayAutoAuthCipher(mchID, serialNo, mchPrivateKey, apiV3Key)}
	}

	client, err := core.NewClient(ctx, opts...)
	if err != nil {
		return nil, err
	}
	return &WechatPayClient{client: client}, nil
}

// JSAPIPrepay 直连商户JSAPI预下单，返回prepay_id
// 入参：appid、mchid、描述、商户订单号、总金额(分)、openid、回调URL
// 返回：prepay_id 或错误
func (c *WechatPayClient) JSAPIPrepay(ctx context.Context, appid, mchid, description, outTradeNo string, total int64, openid, notifyURL string) (string, error) {
	svc := jsapi.JsapiApiService{Client: c.client}
	resp, _, err := svc.Prepay(ctx, jsapi.PrepayRequest{
		Appid:       core.String(appid),
		Mchid:       core.String(mchid),
		Description: core.String(description),
		OutTradeNo:  core.String(outTradeNo),
		NotifyUrl:   core.String(notifyURL),
		Amount: &jsapi.Amount{
			Total: core.Int64(total),
		},
		Payer: &jsapi.Payer{
			Openid: core.String(openid),
		},
	})
	if err != nil {
		return "", err
	}
	if resp == nil || resp.PrepayId == nil {
		return "", errors.New("missing prepay_id in response")
	}
	return *resp.PrepayId, nil
}

// RefundOrder 直连商户退款
// 入参：outTradeNo(商户订单号)、refundNo(商户退款单号)、amountRefund(退款金额分)、total(原订单金额分)、reason(退款原因，可空)
// 返回：微信退款单ID与状态，或错误
func (c *WechatPayClient) RefundOrder(ctx context.Context, outTradeNo, refundNo string, amountRefund, total int64, reason string) (string, string, error) {
	svc := refundsvc.RefundsApiService{Client: c.client}
	req := refundsvc.CreateRequest{
		OutTradeNo:  core.String(outTradeNo),
		OutRefundNo: core.String(refundNo),
		Amount: &refundsvc.AmountReq{
			Refund:   core.Int64(amountRefund),
			Total:    core.Int64(total),
			Currency: core.String("CNY"),
		},
	}
	if reason != "" {
		req.Reason = core.String(reason)
	}
	resp, _, err := svc.Create(ctx, req)
	if err != nil {
		return "", "", err
	}
	var refundID, status string
	if resp != nil {
		if resp.RefundId != nil {
			refundID = *resp.RefundId
		}
		if resp.Status != nil {
			status = string(*resp.Status)
		}
	}
	if refundID == "" {
		return "", status, errors.New("missing refund_id in response")
	}
	return refundID, status, nil
}
