package activity

import (
	"bindbox-game/configs"
	"bindbox-game/internal/pkg/logger"
	"bindbox-game/internal/pkg/notify"
	paypkg "bindbox-game/internal/pkg/pay"
	"bindbox-game/internal/pkg/wechat"
	"bindbox-game/internal/repository/mysql"
	"bindbox-game/internal/repository/mysql/dao"
	"bindbox-game/internal/repository/mysql/model"
	strat "bindbox-game/internal/service/activity/strategy"
	usersvc "bindbox-game/internal/service/user"
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"
)

type scheduledConfig struct {
	PlayType              string  `json:"play_type"`
	DrawMode              string  `json:"draw_mode"`
	MinParticipants       int64   `json:"min_participants"`
	ScheduledTime         string  `json:"scheduled_time"`
	ScheduledDelayMinutes int64   `json:"scheduled_delay_minutes"`
	IntervalMinutes       int64   `json:"interval_minutes"`
	LastSettledAt         string  `json:"last_settled_at"`
	RefundCouponType      string  `json:"refund_coupon_type"`
	RefundCouponAmount    float64 `json:"refund_coupon_amount"`
	RefundCouponID        int64   `json:"refund_coupon_id"`
}

func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) {
	r := dao.Use(repo.GetDbR())
	w := dao.Use(repo.GetDbW())
	us := usersvc.New(l, repo)
	go func() {
		t := time.NewTicker(30 * time.Second)
		defer t.Stop()
		for range t.C {
			ctx := context.Background()
			now := time.Now()
			fmt.Printf("[定时开奖] ====== 开始检查 时间=%s ======\n", now.Format("2006-01-02 15:04:05"))
			var acts []struct {
				ID              int64
				PlayType        string
				DrawMode        string
				MinParticipants int64
				IntervalMinutes int64
				ScheduledTime   *time.Time
				RefundCouponID  int64
				LastSettledAt   *time.Time
			}
			_ = repo.GetDbR().Raw("SELECT id, play_type, draw_mode, min_participants, interval_minutes, scheduled_time, refund_coupon_id, last_settled_at FROM activities WHERE draw_mode='scheduled' AND (scheduled_time IS NOT NULL OR interval_minutes > 0)").Scan(&acts)
			fmt.Printf("[定时开奖] 查询到定时开奖活动数量=%d\n", len(acts))
			for _, a := range acts {
				fmt.Printf("[定时开奖] 检查活动 ID=%d PlayType=%s IntervalMinutes=%d ScheduledTime=%v LastSettledAt=%v\n",
					a.ID, a.PlayType, a.IntervalMinutes, a.ScheduledTime, a.LastSettledAt)

				// 计算开奖时间
				st := time.Time{}
				if a.ScheduledTime != nil {
					st = *a.ScheduledTime
				}

				// 【修复】如果 scheduled_time 是过去的时间且 interval_minutes > 0，
				// 跳过所有过期窗口，推进到最近的一个开奖时间
				if !st.IsZero() && now.After(st) && a.IntervalMinutes > 0 {
					// 计算需要跳过多少个周期
					elapsed := now.Sub(st)
					periods := int64(elapsed.Minutes()) / a.IntervalMinutes
					if periods > 0 {
						// 跳过所有完整的过期周期，st 变成最近的开奖时间点
						st = st.Add(time.Duration(periods*a.IntervalMinutes) * time.Minute)
						fmt.Printf("[定时开奖] 活动ID=%d 跳过过期周期数=%d 新开奖时间=%s\n", a.ID, periods, st.Format("2006-01-02 15:04:05"))
					}
				}

				if st.IsZero() && a.IntervalMinutes > 0 {
					if a.LastSettledAt != nil && !a.LastSettledAt.IsZero() {
						st = a.LastSettledAt.Add(time.Duration(a.IntervalMinutes) * time.Minute)
					} else {
						st = now
					}
				}

				fmt.Printf("[定时开奖] 活动ID=%d 计算开奖时间st=%s 当前时间now=%s 是否跳过=%t\n",
					a.ID, st.Format("2006-01-02 15:04:05"), now.Format("2006-01-02 15:04:05"), st.IsZero() || now.Before(st))

				if st.IsZero() || now.Before(st) {
					continue
				}

				aid := a.ID
				// 【修复】使用 last_settled_at 或很早的时间作为查询起点
				last := time.Time{}
				if a.LastSettledAt != nil && !a.LastSettledAt.IsZero() {
					last = *a.LastSettledAt
				}

				// 【修复】查询从 last 到 now 的所有订单（而非到 st），确保能找到最新订单
				fmt.Printf("[定时开奖] 活动ID=%d 查询订单范围: last=%s now=%s\n", aid, last.Format("2006-01-02 15:04:05"), now.Format("2006-01-02 15:04:05"))
				orders, _ := r.Orders.WithContext(ctx).ReadDB().Where(
					r.Orders.Status.Eq(2),
					r.Orders.SourceType.Eq(2),
					r.Orders.Remark.Like(fmt.Sprintf("lottery:activity:%d|%%", aid)),
					r.Orders.CreatedAt.Gte(last),
				).Find()
				count := int64(len(orders))
				fmt.Printf("[定时开奖] 活动ID=%d 查询到订单数=%d 最低参与人数=%d 是否满足=%t\n", aid, count, a.MinParticipants, count >= a.MinParticipants)

				if count < a.MinParticipants {
					fmt.Printf("[定时开奖] 活动ID=%d ❌ 人数不足，进行退款处理\n", aid)
					wc, err := paypkg.NewWechatPayClient(ctx)
					if err == nil {
						for _, o := range orders {
							// 先处理积分退款（如有）
							if o.PointsAmount > 0 {
								refundPts := o.PointsAmount / 100
								_, _ = us.RefundPoints(ctx, o.UserID, refundPts, o.OrderNo, "scheduled_not_enough")
							}
							// 微信支付部分退款（如有）
							if o.ActualAmount > 0 {
								refundNo := fmt.Sprintf("R%s-%d", o.OrderNo, time.Now().Unix())
								refundID, status, err := wc.RefundOrder(ctx, o.OrderNo, refundNo, o.ActualAmount, o.ActualAmount, "scheduled_not_enough")
								if err == nil {
									_ = w.PaymentRefunds.WithContext(ctx).Create(&model.PaymentRefunds{OrderID: o.ID, OrderNo: o.OrderNo, RefundNo: refundNo, Channel: "wechat_jsapi", Status: status, AmountRefund: o.ActualAmount, Reason: "scheduled_not_enough"})
									_ = w.UserPointsLedger.WithContext(ctx).Create(&model.UserPointsLedger{UserID: o.UserID, Action: "refund_amount", Points: o.ActualAmount / 100, RefTable: "payment_refund", RefID: refundID})
								}
							}
							// 标记订单退款
							_, _ = w.Orders.WithContext(ctx).Where(w.Orders.ID.Eq(o.ID)).Updates(map[string]any{w.Orders.Status.ColumnName().String(): 4})
							iss := extractIssueID(o.Remark)
							_ = repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, o.ID, o.UserID, o.ActualAmount, "", 0, "scheduled_not_enough", "done").Error
							if a.RefundCouponID > 0 {
								_ = us.AddCoupon(ctx, o.UserID, a.RefundCouponID)
							}
						}
					}
				} else {
					fmt.Printf("[定时开奖] 活动ID=%d ✅ 人数满足，开始开奖处理\n", aid)
					if a.PlayType == "ichiban" {
						fmt.Printf("[定时开奖] 活动ID=%d 一番赏模式开奖，订单数=%d\n", aid, len(orders))
						// 一番赏定时开奖逻辑
						ichibanSel := strat.NewIchiban(r, w)
						for _, o := range orders {
							uid := o.UserID
							iss := extractIssueID(o.Remark)
							fmt.Printf("[定时开奖-一番赏] 处理订单 OrderID=%d UserID=%d IssueID=%d\n", o.ID, uid, iss)

							// 检查是否已经处理过
							logs, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(o.ID)).Find()
							if len(logs) > 0 {
								fmt.Printf("[定时开奖-一番赏] 订单ID=%d 已处理过，跳过\n", o.ID)
								continue
							}

							// 查找该订单锁定的所有格位
							claims, _ := r.IssuePositionClaims.WithContext(ctx).ReadDB().Where(
								r.IssuePositionClaims.IssueID.Eq(iss),
								r.IssuePositionClaims.OrderID.Eq(o.ID),
							).Find()
							fmt.Printf("[定时开奖-一番赏] 订单ID=%d 找到格位占用数=%d\n", o.ID, len(claims))

							for claimIdx, claim := range claims {
								fmt.Printf("[定时开奖-一番赏] 处理格位 SlotIndex=%d (索引=%d)\n", claim.SlotIndex, claimIdx)

								// 【幂等检查】检查该订单的该格位是否已经发过奖
								existingLogCnt, _ := r.ActivityDrawLogs.WithContext(ctx).Where(
									r.ActivityDrawLogs.OrderID.Eq(o.ID),
									r.ActivityDrawLogs.IssueID.Eq(iss),
								).Count()
								if existingLogCnt > int64(claimIdx) {
									fmt.Printf("[定时开奖-一番赏] ⚠️ 格位 SlotIndex=%d 已处理过(日志数=%d，当前索引=%d)，跳过\n", claim.SlotIndex, existingLogCnt, claimIdx)
									continue
								}

								// 使用 claim 中的 slot_index 直接获取奖品
								rid, _, err := ichibanSel.SelectItemBySlot(ctx, aid, iss, claim.SlotIndex)
								if err != nil || rid <= 0 {
									fmt.Printf("[定时开奖-一番赏] ❌ SelectItemBySlot失败 err=%v rid=%d\n", err, rid)
									continue
								}

								rw, err := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(rid)).First()
								if err != nil || rw == nil {
									fmt.Printf("[定时开奖-一番赏] ❌ 奖品设置不存在 rid=%d\n", rid)
									continue
								}

								// 【先记录日志，再发奖】确保日志创建成功后再发奖，防止重复
								drawLog := &model.ActivityDrawLogs{
									UserID:       uid,
									IssueID:      iss,
									OrderID:      o.ID,
									RewardID:     rid,
									IsWinner:     1,
									Level:        rw.Level,
									CurrentLevel: 1,
								}
								if err := w.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil {
									fmt.Printf("[定时开奖-一番赏] ❌ 创建开奖日志失败 err=%v，可能已存在，跳过\n", err)
									continue
								}

								// 发放奖励（在原订单上添加中奖商品，不创建新订单）
								fmt.Printf("[定时开奖-一番赏] 发放奖励到原订单 OrderID=%d UserID=%d RewardID=%d 奖品名=%s\n", o.ID, uid, rid, rw.Name)
								_, err = us.GrantRewardToOrder(ctx, uid, usersvc.GrantRewardToOrderRequest{
									OrderID:    o.ID,
									ProductID:  rw.ProductID,
									Quantity:   1,
									ActivityID: &aid,
									RewardID:   &rid,
									Remark:     rw.Name,
								})
								if err != nil {
									fmt.Printf("[定时开奖-一番赏] ⚠️ 发放奖励失败 err=%v\n", err)
								}

								fmt.Printf("[定时开奖-一番赏] ✅ 开奖成功 UserID=%d OrderID=%d RewardID=%d\n", uid, o.ID, rid)
							}
							// 【开奖后虚拟发货】定时一番赏开奖后上传虚拟发货
							uploadVirtualShippingForScheduledDraw(ctx, r, o.ID, o.OrderNo, uid, func() string {
								act, _ := r.Activities.WithContext(ctx).Where(r.Activities.ID.Eq(aid)).First()
								if act != nil {
									return act.Name
								}
								return "活动"
							}())
						}
					} else {
						// 默认玩法逻辑
						sel := strat.NewDefault(r, w)
						for _, o := range orders {
							uid := o.UserID
							iss := extractIssueID(o.Remark)
							dc := extractCount(o.Remark)
							if dc <= 0 {
								dc = 1
							}
							logs, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(o.ID)).Find()
							done := int64(len(logs))
							for i := done; i < dc; i++ {
								rid, proof, err := sel.SelectItem(ctx, aid, iss, uid)
								if err != nil || rid <= 0 {
									break
								}
								rw, err := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(rid)).First()
								if err != nil || rw == nil {
									break
								}
								// 【先记录日志，再发奖】确保日志创建成功后再发奖，防止重复
								drawLog := &model.ActivityDrawLogs{UserID: uid, IssueID: iss, OrderID: o.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1}
								if err := w.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil {
									fmt.Printf("[定时开奖-默认] ❌ 创建开奖日志失败 err=%v，可能已存在，跳过\n", err)
									break
								}
								// 发放奖励（在原订单上添加中奖商品，不创建新订单）
								_, _ = us.GrantRewardToOrder(ctx, uid, usersvc.GrantRewardToOrderRequest{OrderID: o.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &aid, RewardID: &rid, Remark: rw.Name})
								// 保存可验证凭据
								_ = strat.SaveDrawReceipt(ctx, w, drawLog.ID, iss, uid, proof)
							}
							// 【开奖后虚拟发货】定时开奖后上传虚拟发货
							uploadVirtualShippingForScheduledDraw(ctx, r, o.ID, o.OrderNo, uid, func() string {
								act, _ := r.Activities.WithContext(ctx).Where(r.Activities.ID.Eq(aid)).First()
								if act != nil {
									return act.Name
								}
								return "活动"
							}())
						}
					}
				}

				// 【修复】无论成功开奖还是退款，都更新活动的结算时间与下次计划
				var next *time.Time
				if a.IntervalMinutes > 0 {
					t := now.Add(time.Duration(a.IntervalMinutes) * time.Minute)
					next = &t
				}
				var nextVal sql.NullTime
				if next != nil {
					nextVal = sql.NullTime{Time: next.UTC(), Valid: true}
				}
				fmt.Printf("[定时开奖] 活动ID=%d 更新时间: last_settled_at=%s scheduled_time=%s\n", aid, now.Format("2006-01-02 15:04:05"), next)
				_ = repo.GetDbW().WithContext(ctx).Exec("UPDATE activities SET last_settled_at=?, scheduled_time=? WHERE id= ?", now.UTC(), nextVal, aid).Error
			}

			// 即时开奖：处理所有已支付且未记录抽奖日志的订单
			var instantActs []struct{ ID int64 }
			_ = repo.GetDbR().WithContext(ctx).Raw("SELECT id FROM activities WHERE draw_mode='instant'").Scan(&instantActs)
			if len(instantActs) > 0 {
				sel2 := strat.NewDefault(r, w)
				for _, ia := range instantActs {
					orders2, _ := r.Orders.WithContext(ctx).ReadDB().Where(
						r.Orders.Status.Eq(2),
						r.Orders.SourceType.Eq(2),
						r.Orders.Remark.Like(fmt.Sprintf("lottery:activity:%d|%%", ia.ID)),
					).Find()
					for _, o2 := range orders2 {
						uid := o2.UserID
						iss := extractIssueID(o2.Remark)
						dc := extractCount(o2.Remark)
						if dc <= 0 {
							dc = 1
						}
						logs2, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(o2.ID)).Find()
						done2 := int64(len(logs2))
						for i := done2; i < dc; i++ {
							rid, proof, err := sel2.SelectItem(ctx, ia.ID, iss, uid)
							if err != nil || rid <= 0 {
								break
							}
							rw, err := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(rid)).First()
							if err != nil || rw == nil {
								break
							}
							// 【先记录日志，再发奖】确保日志创建成功后再发奖，防止重复
							drawLog := &model.ActivityDrawLogs{UserID: uid, IssueID: iss, OrderID: o2.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1}
							if err := w.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil {
								fmt.Printf("[即时开奖补偿] ❌ 创建开奖日志失败 err=%v，可能已存在，跳过\n", err)
								break
							}
							_, _ = us.GrantRewardToOrder(ctx, uid, usersvc.GrantRewardToOrderRequest{OrderID: o2.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &ia.ID, RewardID: &rid, Remark: rw.Name})
							// 保存可验证凭据
							_ = strat.SaveDrawReceipt(ctx, w, drawLog.ID, iss, uid, proof)
						}
					}
				}
			}
		}
	}()
}

func parseTime(s string) time.Time {
	if s == "" {
		return time.Time{}
	}
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t
	}
	if t2, err2 := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local); err2 == nil {
		return t2
	}
	return time.Time{}
}

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
}

func extractIssueID(remark string) int64 {
	if remark == "" {
		return 0
	}
	parts := strings.Split(remark, "|")
	for _, p := range parts {
		if strings.HasPrefix(p, "issue:") {
			return parseInt64(p[6:])
		}
	}
	return 0
}

func extractCount(remark string) int64 {
	if remark == "" {
		return 1
	}
	parts := strings.Split(remark, "|")
	for _, p := range parts {
		if strings.HasPrefix(p, "count:") {
			return parseInt64(p[6:])
		}
	}
	return 1
}

// uploadVirtualShippingForScheduledDraw 定时开奖后上传虚拟发货
// 收集中奖产品名称并调用微信虚拟发货API
func uploadVirtualShippingForScheduledDraw(ctx context.Context, r *dao.Query, orderID int64, orderNo string, userID int64, actName string) {
	// 获取开奖记录
	drawLogs, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(orderID)).Find()
	if len(drawLogs) == 0 {
		fmt.Printf("[定时开奖-虚拟发货] 没有开奖记录，跳过 order_id=%d\n", orderID)
		return
	}
	// 收集赏品名称
	var rewardNames []string
	for _, lg := range drawLogs {
		if rw, _ := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(lg.RewardID)).First(); rw != nil {
			rewardNames = append(rewardNames, rw.Name)
		}
	}
	itemsDesc := actName + " " + orderNo + " 盲盒赏品: " + strings.Join(rewardNames, ", ")
	if len(itemsDesc) > 120 {
		itemsDesc = itemsDesc[:120]
	}
	// 获取支付交易信息
	tx, _ := r.PaymentTransactions.WithContext(ctx).Where(r.PaymentTransactions.OrderNo.Eq(orderNo)).First()
	if tx == nil || tx.TransactionID == "" {
		fmt.Printf("[定时开奖-虚拟发货] 没有支付交易记录，跳过 order_no=%s\n", orderNo)
		return
	}
	// 获取用户openid
	u, _ := r.Users.WithContext(ctx).Where(r.Users.ID.Eq(userID)).First()
	payerOpenid := ""
	if u != nil {
		payerOpenid = u.Openid
	}
	// 获取微信配置
	c := configs.Get()
	cfg := &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret}
	fmt.Printf("[定时开奖-虚拟发货] 上传 order_no=%s transaction_id=%s items_desc=%s\n", orderNo, tx.TransactionID, itemsDesc)
	if err := wechat.UploadVirtualShippingForBackground(ctx, cfg, tx.TransactionID, orderNo, payerOpenid, itemsDesc); err != nil {
		fmt.Printf("[定时开奖-虚拟发货] 上传失败: %v\n", err)
	}
	// 【定时开奖后推送通知】
	notifyCfg := &notify.WechatNotifyConfig{
		AppID:                   c.Wechat.AppID,
		AppSecret:               c.Wechat.AppSecret,
		LotteryResultTemplateID: c.Wechat.LotteryResultTemplateID,
	}
	_ = notify.SendLotteryResultNotification(ctx, notifyCfg, payerOpenid, actName, rewardNames, orderNo, time.Now())
}
