package pay

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"bindbox-game/internal/code"
	"bindbox-game/internal/pkg/core"
	"bindbox-game/internal/pkg/pay"
	"bindbox-game/internal/pkg/util/remark"
	"bindbox-game/internal/pkg/wechat"
	"bindbox-game/internal/repository/mysql/dao"
	"bindbox-game/internal/repository/mysql/model"
	"bindbox-game/internal/service/sysconfig"

	"go.uber.org/zap"

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

type notifyAck struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

// WechatNotify 微信支付回调通知处理
// 入参：微信官方通知，验签并解密resource，推进订单状态为已支付
// 幂等：若订单已为已支付则直接ACK
// @Summary 微信支付回调通知
// @Description 接收微信支付结果通知，验证签名并处理订单状态
// @Tags Pay
// @Accept json
// @Produce json
// @Success 200 {object} notifyAck "处理成功"
// @Failure 400 {object} notifyAck "请求参数错误"
// @Failure 500 {object} notifyAck "服务器内部错误"
// @Router /pay/wechat/notify [post]
func (h *handler) WechatNotify() core.HandlerFunc {
	return func(ctx core.Context) {
		// Use dynamic configurations exclusively
		dc := sysconfig.GetDynamicConfig()
		cfg := dc.GetWechatPay(ctx.RequestContext().Context)

		if cfg.ApiV3Key == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 150000, "wechat pay config (ApiV3Key) missing"))
			return
		}

		mchID := cfg.MchID
		serialNo := cfg.SerialNo
		apiV3Key := cfg.ApiV3Key
		publicKeyID := cfg.PublicKeyID

		var notifyHandler *notify.Handler
		if publicKeyID != "" {
			// 使用公钥验签模式
			if cfg.PublicKey == "" {
				ctx.AbortWithError(core.Error(http.StatusBadRequest, 150000, "wechat pay public key content missing"))
				return
			}
			pubKey, err := pay.LoadPublicKeyFromBase64(cfg.PublicKey)
			if err != nil {
				ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150001, "load public key err: "+err.Error()))
				return
			}
			notifyHandler = notify.NewNotifyHandler(apiV3Key, verifiers.NewSHA256WithRSAPubkeyVerifier(publicKeyID, *pubKey))
		} else {
			// 使用证书自动下载模式
			if mchID == "" || serialNo == "" {
				ctx.AbortWithError(core.Error(http.StatusBadRequest, 150000, "wechat pay mchid/serial_no missing for cert mode"))
				return
			}

			if cfg.PrivateKey == "" {
				ctx.AbortWithError(core.Error(http.StatusBadRequest, 150000, "wechat pay private key missing"))
				return
			}

			mchPrivateKey, err := pay.LoadPrivateKeyFromBase64(cfg.PrivateKey)
			if err != nil {
				ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150002, "load private key err: "+err.Error()))
				return
			}

			if err := downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx.RequestContext().Context, mchPrivateKey, serialNo, mchID, apiV3Key); err != nil {
				ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150003, "register downloader err: "+err.Error()))
				return
			}
			certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
			notifyHandler = notify.NewNotifyHandler(apiV3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
		}

		var transaction payments.Transaction
		notification, err := notifyHandler.ParseNotifyRequest(ctx.RequestContext().Context, ctx.Request(), &transaction)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error()))
			return
		}
		// 事件去重处理
		var existed *model.PaymentNotifyEvents
		if notification != nil && notification.ID != "" {
			existed, _ = h.readDB.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Where(h.readDB.PaymentNotifyEvents.NotifyID.Eq(notification.ID)).First()
			if existed != nil && existed.Processed {
				ctx.Payload(&notifyAck{Code: "SUCCESS", Message: "OK"})
				return
			}
		}
		if transaction.OutTradeNo == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "missing out_trade_no"))
			return
		}
		paidAt := time.Now()
		if transaction.SuccessTime != nil {
			if t, err := time.Parse(time.RFC3339, *transaction.SuccessTime); err == nil {
				paidAt = t
			}
		}
		rawStr := func() string { b, _ := json.Marshal(transaction); return string(b) }()
		order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.OrderNo.Eq(*transaction.OutTradeNo)).First()
		if err != nil || order == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "order not found for out_trade_no"))
			return
		}
		// 7. 使用全局事务处理后续业务逻辑
		err = h.writeDB.Transaction(func(tx *dao.Query) error {
			// 【审计快照】步骤0：采集消费前快照
			if h.snapshot != nil {
				beforeSnapshot, snapErr := h.snapshot.CaptureUserState(ctx.RequestContext(), order.UserID)
				if snapErr == nil && beforeSnapshot != nil {
					_ = h.snapshot.SaveSnapshot(ctx.RequestContext(), order.ID, order.OrderNo, order.UserID, 1, beforeSnapshot)
				}
			}

			// 1. 创建支付交易记录
			payTx := &model.PaymentTransactions{
				OrderID: order.ID,
				OrderNo: *transaction.OutTradeNo,
				Channel: "wechat_jsapi",
				TransactionID: func() string {
					if transaction.TransactionId != nil {
						return *transaction.TransactionId
					}
					return ""
				}(),
				AmountTotal: func() int64 {
					if transaction.Amount != nil && transaction.Amount.Total != nil {
						return *transaction.Amount.Total
					}
					return 0
				}(),
				SuccessTime: paidAt,
				Raw:         rawStr,
			}
			if err := tx.PaymentTransactions.WithContext(ctx.RequestContext()).Create(payTx); err != nil {
				return err
			}

			// 2. 只有在此事务中将订单状态更新为已支付(2)
			res, err := tx.Orders.WithContext(ctx.RequestContext()).Where(tx.Orders.OrderNo.Eq(*transaction.OutTradeNo), tx.Orders.Status.Eq(1)).Updates(map[string]any{
				tx.Orders.Status.ColumnName().String(): 2,
				tx.Orders.PaidAt.ColumnName().String(): paidAt,
			})
			if err != nil {
				return err
			}
			if res.RowsAffected == 0 {
				// 检查现有状态，如果是已支付，则可能是并发导致的，业务层面幂等跳过
				cur, _ := tx.Orders.WithContext(ctx.RequestContext()).Where(tx.Orders.OrderNo.Eq(*transaction.OutTradeNo)).First()
				if cur != nil && cur.Status >= 2 {
					return nil // 幂等处理
				}
				return fmt.Errorf("order status update failed or order already processed")
			}

			// 3. 优惠券扣减 (传递 tx 给 service 保证原子性)
			if err := h.user.DeductCouponsForPaidOrder(ctx.RequestContext(), tx, order.UserID, order.ID, paidAt); err != nil {
				h.logger.Error("优惠券扣减失败", zap.Error(err), zap.Int64("order_id", order.ID))
				// 优惠券扣减失败通常不应阻塞主支付流程，但既然用了事务，这里如果返回 err 会回滚。
				// 如果希望即便扣券失败也完成订单，可以记录日志后返回 nil。
				// 这里遵循严格模式：如果扣券配置了但失败，回滚并让微信重试可能更安全。
				return err
			}

			// 5. 积分奖励
			func() {
				cfg, _ := tx.SystemConfigs.WithContext(ctx.RequestContext()).Where(tx.SystemConfigs.ConfigKey.Eq("points_reward_per_cent")).First()
				rate := int64(0)
				if cfg != nil {
					var r int64
					_, _ = fmt.Sscanf(cfg.ConfigValue, "%d", &r)
					rate = r
				}
				if rate > 0 && order.ActualAmount > 0 {
					reward := order.ActualAmount * rate
					_ = h.user.AddPointsWithAction(ctx.RequestContext(), order.UserID, reward, "pay_reward", order.OrderNo, "pay_reward", nil, nil)
				}
			}()

			// 6. 一番赏占位 (事务内处理)
			if order.SourceType == 2 {
				rmk := remark.Parse(order.Remark)
				aid := rmk.ActivityID
				iss := rmk.IssueID
				dc := rmk.Count
				act, _ := tx.Activities.WithContext(ctx.RequestContext()).Where(tx.Activities.ID.Eq(aid)).First()
				if act != nil && act.PlayType == "ichiban" {
					rem := make([]int64, len(rmk.Slots))
					for i, s := range rmk.Slots {
						rem[i] = s.Count
					}
					cur := 0
					for i := int64(0); i < dc; i++ {
						slot := func() int64 {
							if len(rmk.Slots) > 0 {
								for cur < len(rem) && rem[cur] == 0 {
									cur++
								}
								if cur >= len(rem) {
									return -1
								}
								rem[cur]--
								return rmk.Slots[cur].SlotIndex
							}
							return -1
						}()
						if slot < 0 {
							break
						}
						// 检查是否已被占用 (事务内)
						cnt, _ := tx.IssuePositionClaims.WithContext(ctx.RequestContext()).Where(tx.IssuePositionClaims.IssueID.Eq(iss), tx.IssuePositionClaims.SlotIndex.Eq(slot)).Count()
						if cnt > 0 {
							return fmt.Errorf("slot_unavailable")
						}
						err := tx.IssuePositionClaims.WithContext(ctx.RequestContext()).Create(&model.IssuePositionClaims{IssueID: iss, SlotIndex: slot, UserID: order.UserID, OrderID: order.ID})
						if err != nil {
							return err
						}
					}
				}
			}

			// 7. 标记通知事件为已处理
			if notification != nil && notification.ID != "" {
				if existed == nil {
					_ = tx.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Create(&model.PaymentNotifyEvents{
						NotifyID:     notification.ID,
						ResourceType: notification.ResourceType,
						EventType:    notification.EventType,
						Summary:      notification.Summary,
						Raw:          rawStr,
						Processed:    true,
					})
				} else {
					_, _ = tx.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Where(tx.PaymentNotifyEvents.NotifyID.Eq(notification.ID)).Update(tx.PaymentNotifyEvents.Processed, true)
				}
			}

			// 【审计快照】步骤N：采集消费后快照
			if h.snapshot != nil {
				afterSnapshot, snapErr := h.snapshot.CaptureUserState(ctx.RequestContext(), order.UserID)
				if snapErr == nil && afterSnapshot != nil {
					_ = h.snapshot.SaveSnapshot(ctx.RequestContext(), order.ID, order.OrderNo, order.UserID, 2, afterSnapshot)
				}
			}

			return nil
		})

		// 处理事务结果
		if err != nil {
			if err.Error() == "slot_unavailable" {
				// 处理一番赏位置冲突退款 (事务外异步处理，避免长事务)
				go h.handleRefund(context.Background(), order, "ichiban_slot_conflict")
				ctx.Payload(&notifyAck{Code: "SUCCESS", Message: "OK"})
				return
			}
			ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150005, err.Error()))
			return
		}

		// 8. 异步触发外部同步逻辑 (无需事务)
		go func() {
			bgCtx := context.Background()
			// 触发任务中心逻辑
			_ = h.task.OnOrderPaid(bgCtx, order.UserID, order.ID)

			// 抽奖或发货逻辑
			ord, _ := h.readDB.Orders.WithContext(bgCtx).Where(h.readDB.Orders.ID.Eq(order.ID)).First()
			if ord == nil {
				return
			}
			rmk := remark.Parse(ord.Remark)
			act, _ := h.readDB.Activities.WithContext(bgCtx).Where(h.readDB.Activities.ID.Eq(rmk.ActivityID)).First()

			// 获取微信配置 (动态)
			var wxConfig *wechat.WechatConfig
			if dc := sysconfig.GetDynamicConfig(); dc != nil {
				cfg := dc.GetWechat(bgCtx)
				wxConfig = &wechat.WechatConfig{AppID: cfg.AppID, AppSecret: cfg.AppSecret}
			}

			if wxConfig == nil || wxConfig.AppID == "" {
				h.logger.Error("微信配置缺失(AppID为空)，跳过虚拟发货/抽奖", zap.String("order_no", order.OrderNo))
				return
			}

			if ord.SourceType == 2 && act != nil && act.DrawMode == "instant" {
				_ = h.activity.ProcessOrderLottery(bgCtx, ord.ID)
			} else if ord.SourceType == 4 {
				// 次数卡发放
				var pkgID int64
				var count int32 = 1
				parts := strings.Split(ord.Remark, "|")
				for _, p := range parts {
					if strings.HasPrefix(p, "pkg_id:") {
						_, _ = fmt.Sscanf(p, "pkg_id:%d", &pkgID)
					} else if strings.HasPrefix(p, "count:") {
						_, _ = fmt.Sscanf(p, "count:%d", &count)
					}
				}
				if pkgID > 0 {
					if err := h.user.GrantGamePass(bgCtx, ord.UserID, pkgID, count, ord.OrderNo); err != nil {
						h.logger.Error("Failed to grant game pass", zap.Error(err), zap.String("order_no", ord.OrderNo))
					}
				} else {
					h.logger.Error("Game pass package ID not found in remark", zap.String("order_no", ord.OrderNo), zap.String("remark", ord.Remark))
				}
				// 虚拟发货通知
				payerOpenid := ""
				if transaction.Payer != nil && transaction.Payer.Openid != nil {
					payerOpenid = *transaction.Payer.Openid
				}
				itemsDesc := "次数卡 " + ord.OrderNo
				if txID := func() string {
					if transaction.TransactionId != nil {
						return *transaction.TransactionId
					}
					return ""
				}(); txID != "" {
					if err := wechat.UploadVirtualShippingForBackground(bgCtx, wxConfig, txID, ord.OrderNo, payerOpenid, itemsDesc); err != nil {
						h.logger.Error("次数卡虚拟发货失败", zap.Error(err), zap.String("order_no", ord.OrderNo))
					} else {
						h.logger.Info("次数卡虚拟发货成功", zap.String("order_no", ord.OrderNo))
					}
				}
			} else if ord.SourceType == 3 {
				// 对对碰订单虚拟发货（初始支付成功通知）
				payerOpenid := ""
				if transaction.Payer != nil && transaction.Payer.Openid != nil {
					payerOpenid = *transaction.Payer.Openid
				}
				itemsDesc := "对对碰游戏 " + ord.OrderNo
				if txID := func() string {
					if transaction.TransactionId != nil {
						return *transaction.TransactionId
					}
					return ""
				}(); txID != "" {
					if err := wechat.UploadVirtualShippingForBackground(bgCtx, wxConfig, txID, ord.OrderNo, payerOpenid, itemsDesc); err != nil {
						h.logger.Error("对对碰虚拟发货失败", zap.Error(err), zap.String("order_no", ord.OrderNo))
					} else {
						h.logger.Info("对对碰虚拟发货成功", zap.String("order_no", ord.OrderNo))
					}
				}
			} else if ord.SourceType != 2 {
				// 普通商品虚拟发货
				payerOpenid := ""
				if transaction.Payer != nil && transaction.Payer.Openid != nil {
					payerOpenid = *transaction.Payer.Openid
				}
				itemsDesc := "订单" + ord.OrderNo
				if txID := func() string {
					if transaction.TransactionId != nil {
						return *transaction.TransactionId
					}
					return ""
				}(); txID != "" {
					if err := wechat.UploadVirtualShippingForBackground(bgCtx, wxConfig, txID, ord.OrderNo, payerOpenid, itemsDesc); err != nil {
						h.logger.Error("商户订单虚拟发货失败", zap.Error(err), zap.String("order_no", ord.OrderNo))
					} else {
						h.logger.Info("商户订单虚拟发货成功", zap.String("order_no", ord.OrderNo))
					}
				}
			}
		}()

		ctx.Payload(&notifyAck{Code: "SUCCESS", Message: "OK"})
	}
}

// handleRefund 处理一番赏冲突退款
func (h *handler) handleRefund(ctx context.Context, ord *model.Orders, reason string) {
	wc, e := pay.NewWechatPayClient(ctx)
	if e != nil {
		h.logger.Error("Failed to create wechat pay client for refund", zap.Error(e))
		return
	}
	refundNo := fmt.Sprintf("R%s-%d", ord.OrderNo, time.Now().Unix())
	rmk := remark.Parse(ord.Remark)
	iss := rmk.IssueID
	_, status, e2 := wc.RefundOrder(ctx, ord.OrderNo, refundNo, ord.ActualAmount, ord.ActualAmount, reason)
	if e2 != nil {
		h.logger.Error("Refund failed", zap.Error(e2), zap.String("order_no", ord.OrderNo))
		return
	}
	_ = h.writeDB.PaymentRefunds.WithContext(ctx).Create(&model.PaymentRefunds{OrderID: ord.ID, OrderNo: ord.OrderNo, RefundNo: refundNo, Channel: "wechat_jsapi", Status: status, AmountRefund: ord.ActualAmount, Reason: reason})
	_, _ = h.writeDB.Orders.WithContext(ctx).Where(h.writeDB.Orders.ID.Eq(ord.ID)).Update(h.writeDB.Orders.Status, 4)
	_ = h.repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, ord.ID, ord.UserID, ord.ActualAmount, "", 0, reason, status).Error
}

func parseInt64(s string) int64 {
	var n int64
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c < '0' || c > '9' {
			break
		}
		n = n*10 + int64(c-'0')
	}
	return n
}
