// Package payment 是充值渠道适配层（设计见 PAYMENT_DESIGN.md §3/§5）。
// P5.1 的兑换码不走这里（无「待支付」态，核销即入账）；本包面向真渠道：
// 下单出支付凭据 → 回调/查单确认 → 上层 MarkOrderPaid 幂等入账。
package payment

import (
	"context"
	"crypto/rsa"
	"errors"
	"fmt"
	"net/http"

	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
	"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
	"github.com/wechatpay-apiv3/wechatpay-go/core/option"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
	"github.com/wechatpay-apiv3/wechatpay-go/utils"
)

// Wechat 微信支付 Native（扫码）适配器。配置来源见 Config（DB 优先、env 兜底，由 Manager 装配）。
// 本地开发收不到公网回调没关系：前端轮询的 GET /billing/orders/:id 会主动查单确认，
// 回调只是生产环境更快的到账通道，两条路都汇入同一个幂等入账闸。
type Wechat struct {
	mchID     string
	appID     string
	notifyURL string
	apiv3Key  string
	client    *core.Client
	svc       native.NativeApiService
	pubKey    *rsa.PublicKey // 微信支付公钥（验签回调用；构造时必填）
	pubKeyID  string
}

// New 按完整配置装配微信渠道（商户私钥/微信支付公钥都从磁盘路径加载；调用方保证字段齐全）。
// 验签体系钉死「微信支付公钥」模式（WithWechatPayPublicKeyAuthCipher）：本项目商户
// 2025-09 开户，只有公钥体系；老商户的平台证书模式（AutoAuthCipher）不支持。
func New(ctx context.Context, c Config) (*Wechat, error) {
	priv, err := utils.LoadPrivateKeyWithPath(c.PrivateKeyPath)
	if err != nil {
		return nil, fmt.Errorf("商户私钥加载失败(%s): %w", c.PrivateKeyPath, err)
	}
	pub, err := utils.LoadPublicKeyWithPath(c.PublicKeyPath)
	if err != nil {
		return nil, fmt.Errorf("微信支付公钥加载失败(%s): %w", c.PublicKeyPath, err)
	}
	client, err := core.NewClient(ctx,
		option.WithWechatPayPublicKeyAuthCipher(c.MchID, c.CertSerial, priv, c.PublicKeyID, pub))
	if err != nil {
		return nil, fmt.Errorf("客户端初始化失败: %w", err)
	}
	return &Wechat{
		mchID: c.MchID, appID: c.AppID, notifyURL: c.NotifyURL, apiv3Key: c.APIv3Key,
		client: client, svc: native.NativeApiService{Client: client},
		pubKey: pub, pubKeyID: c.PublicKeyID,
	}, nil
}

// CreatePay Native 下单：返回 code_url（前端渲染成二维码）。金额取订单锁定值。
func (w *Wechat) CreatePay(ctx context.Context, orderID, description string, amountFen int64) (string, error) {
	resp, _, err := w.svc.Prepay(ctx, native.PrepayRequest{
		Appid:       core.String(w.appID),
		Mchid:       core.String(w.mchID),
		Description: core.String(description),
		OutTradeNo:  core.String(orderID),
		NotifyUrl:   core.String(w.notifyURL),
		Amount:      &native.Amount{Total: core.Int64(amountFen), Currency: core.String("CNY")},
	})
	if err != nil {
		return "", err
	}
	if resp.CodeUrl == nil || *resp.CodeUrl == "" {
		return "", errors.New("微信未返回 code_url")
	}
	return *resp.CodeUrl, nil
}

// QueryResult 查单/回调解析后的统一结果。
type QueryResult struct {
	OrderID    string // out_trade_no
	ChannelTxn string // transaction_id
	Paid       bool   // TradeState == SUCCESS
	Closed     bool   // CLOSED/REVOKED/PAYERROR 等终态失败
	AmountFen  int64  // 用户实付（分）；回调/查单都带，供金额核对
}

func fromTransaction(t *payments.Transaction) QueryResult {
	r := QueryResult{}
	if t.OutTradeNo != nil {
		r.OrderID = *t.OutTradeNo
	}
	if t.TransactionId != nil {
		r.ChannelTxn = *t.TransactionId
	}
	if t.Amount != nil && t.Amount.PayerTotal != nil {
		r.AmountFen = *t.Amount.PayerTotal
	} else if t.Amount != nil && t.Amount.Total != nil {
		r.AmountFen = *t.Amount.Total
	}
	if t.TradeState != nil {
		switch *t.TradeState {
		case "SUCCESS":
			r.Paid = true
		case "CLOSED", "REVOKED", "PAYERROR":
			r.Closed = true
		}
	}
	return r
}

// QueryOrder 主动查单（本地开发确认到账、生产掉单补偿共用）。
func (w *Wechat) QueryOrder(ctx context.Context, orderID string) (QueryResult, error) {
	t, _, err := w.svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{
		OutTradeNo: core.String(orderID),
		Mchid:      core.String(w.mchID),
	})
	if err != nil {
		return QueryResult{}, err
	}
	return fromTransaction(t), nil
}

// VerifyCallback 验签 + 解密支付回调（微信支付公钥验签 + APIv3 密钥 AES-GCM 解密资源）。
// 验签失败一律拒绝——回调路由是公开的，签名是唯一的门。
func (w *Wechat) VerifyCallback(req *http.Request) (QueryResult, error) {
	h, err := notify.NewRSANotifyHandler(w.apiv3Key,
		verifiers.NewSHA256WithRSAPubkeyVerifier(w.pubKeyID, *w.pubKey))
	if err != nil {
		return QueryResult{}, fmt.Errorf("回调处理器初始化失败: %w", err)
	}
	txn := new(payments.Transaction)
	if _, err := h.ParseNotifyRequest(req.Context(), req, txn); err != nil {
		return QueryResult{}, err
	}
	return fromTransaction(txn), nil
}
