package activity

import (
	"bindbox-game/internal/pkg/logger"
	paypkg "bindbox-game/internal/pkg/pay"
	"bindbox-game/internal/pkg/util/remark"
	"bindbox-game/internal/repository/mysql"
	"bindbox-game/internal/repository/mysql/dao"
	"bindbox-game/internal/repository/mysql/model"
	usersvc "bindbox-game/internal/service/user"
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

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, rdb *redis.Client) {
	r := dao.Use(repo.GetDbR())
	w := dao.Use(repo.GetDbW())
	us := usersvc.New(l, repo)
	activitySvc := New(l, repo, us, rdb)

	go func() {
		t := time.NewTicker(30 * time.Second)
		defer t.Stop()
		for range t.C {
			ctx := context.Background()
			now := time.Now()
			l.Debug("定时开奖: 开始检查", zap.Time("now", now))

			// 【独立检查】一番赏格位重置：每30秒检查所有售罄的一番赏期号
			checkAndResetIchibanSlots(ctx, l, repo, r)
			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)
			l.Debug("定时开奖: 查询到活动", zap.Int("count", len(acts)))
			for _, a := range acts {
				l.Debug("定时开奖: 检查活动",
					zap.Int64("id", a.ID),
					zap.String("play_type", a.PlayType),
					zap.Int64("interval", a.IntervalMinutes),
					zap.Reflect("scheduled_time", a.ScheduledTime),
					zap.Reflect("last_settled", 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)
						l.Info("定时开奖: 跳过过期周期",
							zap.Int64("id", a.ID),
							zap.Int64("periods", periods),
							zap.Time("new_st", st))
					}
				}

				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
					}
				}

				l.Debug("定时开奖: 计算开奖时间",
					zap.Int64("id", a.ID),
					zap.Time("st", st),
					zap.Time("now", now),
					zap.Bool("skip", 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），确保能找到最新订单
				l.Debug("定时开奖: 查询订单范围",
					zap.Int64("id", aid),
					zap.Time("last", last),
					zap.Time("now", now))
				orders, _ := r.Orders.WithContext(ctx).ReadDB().Where(
					r.Orders.Status.Eq(2),
					r.Orders.SourceType.Eq(2),
					r.Orders.IsConsumed.Eq(0), // 仅处理未履约的订单
					r.Orders.Remark.Like(fmt.Sprintf("lottery:activity:%d|%%", aid)),
					r.Orders.CreatedAt.Gte(last),
				).Find()
				count := int64(len(orders))
				l.Debug("定时开奖: 查询到订单",
					zap.Int64("id", aid),
					zap.Int64("count", count),
					zap.Int64("min", a.MinParticipants))

				// Initialize Wechat Client if needed
				wc, _ := paypkg.NewWechatPayClient(ctx)

				refundedIssues := make(map[int64]bool)

				// 【优化】一番赏定时退款：检查是否售罄
				if a.PlayType == "ichiban" {
					issueIDs := make(map[int64]struct{})
					for _, o := range orders {
						iss := remark.Parse(o.Remark).IssueID
						if iss > 0 {
							issueIDs[iss] = struct{}{}
						}
					}

					// 【Fix】Also check all active issues for this activity, regardless of recent orders
					// This ensures expired issues with no recent orders are also processed
					var activeIssues []struct {
						ID int64
					}
					// status=1 means active/processing
					_ = r.ActivityIssues.WithContext(ctx).ReadDB().Where(
						r.ActivityIssues.ActivityID.Eq(aid),
						r.ActivityIssues.Status.Eq(1),
					).Scan(&activeIssues)

					for _, ai := range activeIssues {
						issueIDs[ai.ID] = struct{}{}
					}

					for iss := range issueIDs {
						// Check Sales
						// 一番赏：每种奖品 = 1个格位
						totalSlots, _ := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.IssueID.Eq(iss)).Count()

						soldSlots, _ := r.IssuePositionClaims.WithContext(ctx).Where(r.IssuePositionClaims.IssueID.Eq(iss)).Count()

						l.Debug("定时开奖-一番赏: 检查售罄",
							zap.Int64("issue_id", iss),
							zap.Int64("sold", soldSlots),
							zap.Int64("total", totalSlots))

						if soldSlots < totalSlots {
							l.Info("定时开奖-一番赏: 未售罄，执行全额退款", zap.Int64("issue_id", iss))
							refundedIssues[iss] = true

							// Find ALL valid orders for this issue
							issueOrders, _ := r.Orders.WithContext(ctx).Where(
								r.Orders.Status.Eq(2),
								r.Orders.SourceType.Eq(2),
								r.Orders.Remark.Like(fmt.Sprintf("%%issue:%d|%%", iss)),
							).Find()

							for _, o := range issueOrders {
								// Check if the order has already been drawn (has logs)
								logCount, _ := r.ActivityDrawLogs.WithContext(ctx).Where(
									r.ActivityDrawLogs.OrderID.Eq(o.ID),
									r.ActivityDrawLogs.IssueID.Eq(iss),
								).Count()

								if logCount > 0 {
									l.Info("定时开奖-一番赏: 订单已开奖，跳过退款", zap.Int64("order_id", o.ID), zap.Int64("issue_id", iss))
									continue
								}

								refundOrder(ctx, l, o, "ichiban_not_sold_out", wc, r, w, us, a.RefundCouponID)
							}
						} else {
							// 【Fix】已售罄：处理所有未开奖的订单（包括旧时间窗口的订单）
							l.Info("定时开奖-一番赏: 已售罄，检查并处理所有未开奖订单", zap.Int64("issue_id", iss))

							// Step 1: Get all order IDs from claims for this issue
							var claimOrderIDs []int64
							errClaims := repo.GetDbR().Raw(`
						SELECT DISTINCT c.order_id FROM issue_position_claims c
						LEFT JOIN activity_draw_logs l ON l.order_id = c.order_id AND l.issue_id = c.issue_id
						WHERE c.issue_id = ? AND l.id IS NULL
					`, iss).Scan(&claimOrderIDs).Error

							if errClaims != nil {
								l.Error("定时开奖-一番赏: 查询未处理订单ID失败", zap.Int64("issue_id", iss), zap.Error(errClaims))
							}

							l.Debug("定时开奖-一番赏: 查询到未处理订单",
								zap.Int64("issue_id", iss),
								zap.Int("count", len(claimOrderIDs)),
								zap.Reflect("order_ids", claimOrderIDs))

							if len(claimOrderIDs) > 0 {
								// Step 2: Fetch the actual order records
								unprocessedOrders, errOrders := r.Orders.WithContext(ctx).Where(
									r.Orders.ID.In(claimOrderIDs...),
									r.Orders.Status.Eq(2),
								).Find()

								if errOrders != nil {
									l.Error("定时开奖-一番赏: 获取订单详情失败", zap.Error(errOrders))
								}

								l.Info("定时开奖-一番赏: 开始补录未处理订单",
									zap.Int64("issue_id", iss),
									zap.Int("count", len(unprocessedOrders)))
								for _, o := range unprocessedOrders {
									if err := activitySvc.ProcessOrderLottery(ctx, o.ID); err != nil {
										l.Error("定时开奖-一番赏-补录: ProcessOrderLottery 失败",
											zap.Int64("order_id", o.ID),
											zap.Error(err))
									}
								}
							}
						}

						// 【直接重置】无论是否有待处理订单，检查该期号是否可以重置
						var remainingUnprocessed int64
						_ = repo.GetDbR().Raw(`
							SELECT COUNT(1) FROM issue_position_claims c
							LEFT JOIN activity_draw_logs l ON l.order_id = c.order_id AND l.issue_id = c.issue_id
							WHERE c.issue_id = ? AND l.id IS NULL
						`, iss).Scan(&remainingUnprocessed)

						l.Debug("定时开奖-一番赏: 剩余未处理订单记录",
							zap.Int64("issue_id", iss),
							zap.Int64("count", remainingUnprocessed))

						if remainingUnprocessed == 0 {
							// 所有订单都已处理，执行重置
							if err := repo.GetDbW().Exec("DELETE FROM issue_position_claims WHERE issue_id = ?", iss).Error; err != nil {
								l.Error("定时开奖-一番赏: 重置格位失败", zap.Int64("issue_id", iss), zap.Error(err))
							} else {
								l.Info("定时开奖-一番赏: 格位已重置，新一轮可以开始", zap.Int64("issue_id", iss))
							}
						}
					}
				}

				shouldRefund := false
				if a.PlayType != "ichiban" {
					if count < a.MinParticipants {
						shouldRefund = true
					}
				}

				if shouldRefund {
					l.Info("定时开奖: 人数不足，执行退款完毕", zap.Int64("id", aid))
					for _, o := range orders {
						refundOrder(ctx, l, o, "scheduled_not_enough", wc, r, w, us, a.RefundCouponID)
					}
				} else {
					l.Info("定时开奖: 人数满足，开始开奖处理", zap.Int64("id", aid))
					for _, o := range orders {
						iss := remark.Parse(o.Remark).IssueID
						if a.PlayType == "ichiban" && refundedIssues[iss] {
							l.Debug("定时开奖-一番赏: 订单已退款，跳过开奖", zap.Int64("order_id", o.ID), zap.Int64("issue_id", iss))
							continue
						}
						if err := activitySvc.ProcessOrderLottery(ctx, o.ID); err != nil {
							l.Error("定时开奖: ProcessOrderLottery 失败", zap.Int64("order_id", o.ID), zap.Error(err))
						}
					}
				}

				// 【修复】无论成功开奖还是退款，都更新活动的结算时间与下次计划
				var nextVal sql.NullTime
				if a.IntervalMinutes > 0 {
					// 更新开奖时间戳
					next := now.Add(time.Duration(a.IntervalMinutes) * time.Minute)
					nextVal = sql.NullTime{Time: next.UTC(), Valid: true}
					l.Info("定时开奖: 更新活动下次结算时间",
						zap.Int64("id", aid),
						zap.Time("last", now),
						zap.Time("next", next))
				} else {
					// 如果没有间隔，则不设置下次计划时间
					nextVal = sql.NullTime{Valid: false}
					l.Info("定时开奖: 活动无间隔，不设置下次计划时间", zap.Int64("id", aid), zap.Time("last", now))
				}
				_ = 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 {
				for _, ia := range instantActs {
					orders2, _ := r.Orders.WithContext(ctx).ReadDB().Where(
						r.Orders.Status.Eq(2),
						r.Orders.SourceType.Eq(2),
						r.Orders.IsConsumed.Eq(0), // 仅处理未履约的订单
						r.Orders.Remark.Like(fmt.Sprintf("lottery:activity:%d|%%", ia.ID)),
						r.Orders.CreatedAt.Lt(now.Add(-5*time.Minute)),
						r.Orders.CreatedAt.Gt(now.Add(-24*time.Hour)), // 限制时间窗口为24小时，避免全表扫描
					).Find()
					for _, o2 := range orders2 {
						if err := activitySvc.ProcessOrderLottery(ctx, o2.ID); err != nil {
							l.Error("即时开奖补偿: ProcessOrderLottery 失败", zap.Int64("order_id", o2.ID), zap.Error(err))
						}
					}
				}
			}
		}
	}()
}

// checkAndResetIchibanSlots 检查并重置所有售罄且已完成的一番赏期号
func checkAndResetIchibanSlots(ctx context.Context, l logger.CustomLogger, repo mysql.Repo, r *dao.Query) {
	// 查找所有一番赏活动下的活跃期号
	var issuesWithClaims []struct {
		IssueID    int64
		TotalSlots int64
		SoldSlots  int64
	}

	err := repo.GetDbR().Raw(`
		SELECT 
			ai.id as issue_id,
			(SELECT COUNT(*) FROM activity_reward_settings WHERE issue_id = ai.id) as total_slots,
			(SELECT COUNT(*) FROM issue_position_claims WHERE issue_id = ai.id) as sold_slots
		FROM activity_issues ai
		INNER JOIN activities a ON a.id = ai.activity_id
		WHERE a.play_type = 'ichiban' AND a.status = 1 AND ai.status = 1
		HAVING sold_slots > 0 AND sold_slots >= total_slots
	`).Scan(&issuesWithClaims).Error

	if err != nil {
		l.Error("一番赏重置检查: 查询失败", zap.Error(err))
		return
	}

	for _, iss := range issuesWithClaims {
		// 检查是否所有订单都已处理（有draw logs）
		var unprocessedCnt int64
		_ = repo.GetDbR().Raw(`
			SELECT COUNT(1) FROM issue_position_claims c
			LEFT JOIN activity_draw_logs l ON l.order_id = c.order_id AND l.issue_id = c.issue_id
			WHERE c.issue_id = ? AND l.id IS NULL
		`, iss.IssueID).Scan(&unprocessedCnt)

		if unprocessedCnt == 0 {
			// 所有订单都已处理，执行重置
			if err := repo.GetDbW().Exec("DELETE FROM issue_position_claims WHERE issue_id = ?", iss.IssueID).Error; err != nil {
				l.Error("一番赏重置检查: 重置失败", zap.Int64("issue_id", iss.IssueID), zap.Error(err))
			} else {
				l.Info("一番赏重置检查: 重置通过", zap.Int64("issue_id", iss.IssueID))
			}
		} else {
			l.Debug("一番赏重置检查: 仍有未处理订单", zap.Int64("issue_id", iss.IssueID), zap.Int64("unprocessed", unprocessedCnt))
		}
	}
}

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{}
}

// uploadVirtualShippingForScheduledDraw 定时开奖后上传虚拟发货
// 收集中奖产品名称并调用微信虚拟发货API
// playType: 活动玩法类型，只有 ichiban 时才发送开奖结果通知
func refundOrder(ctx context.Context, l logger.CustomLogger, o *model.Orders, reason string, wc *paypkg.WechatPayClient, r *dao.Query, w *dao.Query, us usersvc.Service, refundCouponID int64) {
	// 1. Refund Points
	if o.PointsAmount > 0 {
		refundPts := o.PointsAmount / 100
		_, _ = us.RefundPoints(ctx, o.UserID, refundPts, o.OrderNo, reason)
	}

	// 2. Refund WeChat
	if o.ActualAmount > 0 && wc != nil {
		refundNo := fmt.Sprintf("R%s-%d", o.OrderNo, time.Now().Unix())
		refundID, status, err := wc.RefundOrder(ctx, o.OrderNo, refundNo, o.ActualAmount, o.ActualAmount, reason)
		if err == nil {
			// 修复：raw 字段需要有效的 JSON 值，不能为空
			rawJSON := fmt.Sprintf(`{"refund_id":"%s","status":"%s"}`, refundID, status)
			_ = w.PaymentRefunds.WithContext(ctx).Create(&model.PaymentRefunds{
				OrderID:      o.ID,
				OrderNo:      o.OrderNo,
				RefundNo:     refundNo,
				Channel:      "wechat_jsapi",
				Status:       status,
				AmountRefund: o.ActualAmount,
				Reason:       reason,
				SuccessTime:  time.Now(),
				Raw:          rawJSON,
			})
		}
		if err != nil {
			l.Error("Refund: WeChat refund failed", zap.String("order_no", o.OrderNo), zap.Error(err))
		}
	}

	// 3. Refund Used Coupons
	ocs, _ := r.OrderCoupons.WithContext(ctx).Where(r.OrderCoupons.OrderID.Eq(o.ID)).Find()
	for _, oc := range ocs {
		// Restore user coupon status to 1 (Unused) and clear usage info
		_, err := w.UserCoupons.WithContext(ctx).Where(w.UserCoupons.ID.Eq(oc.UserCouponID)).Updates(map[string]interface{}{
			"status":        1,
			"used_order_id": 0,
			"used_at":       nil,
		})
		if err != nil {
			l.Error("Refund: Failed to restore coupon", zap.Int64("ucID", oc.UserCouponID), zap.String("order_no", o.OrderNo), zap.Error(err))
		} else {
			l.Info("Refund: Restored coupon", zap.Int64("ucID", oc.UserCouponID), zap.String("order_no", o.OrderNo))
		}
	}

	// 3.5. 一番赏退款：删除 issue_position_claims 记录，恢复格位
	iss := remark.Parse(o.Remark).IssueID
	if iss > 0 {
		result, err := w.IssuePositionClaims.WithContext(ctx).Where(
			w.IssuePositionClaims.OrderID.Eq(o.ID),
		).Delete()
		if err != nil {
			l.Error("Refund: Failed to delete position claims", zap.Int64("order_id", o.ID), zap.Error(err))
		} else {
			l.Info("Refund: Restored slot positions", zap.Int64("order_id", o.ID), zap.Int64("rows", result.RowsAffected))
		}
	}

	// 3.6. 退还道具卡（支持两种记录方式）
	// 方式1：从 activity_draw_effects 表查询（无限赏等游戏类型）
	var itemCardIDs []int64
	_ = r.Orders.WithContext(ctx).UnderlyingDB().Raw("SELECT user_item_card_id FROM activity_draw_effects WHERE draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?)", o.ID).Scan(&itemCardIDs).Error
	// 方式2：从 user_item_cards 表的 used_draw_log_id 直接查询（对对碰等游戏类型）
	var itemCardIDsFromItemCards []int64
	_ = r.Orders.WithContext(ctx).UnderlyingDB().Raw("SELECT id FROM user_item_cards WHERE used_draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?) AND status=2", o.ID).Scan(&itemCardIDsFromItemCards).Error
	// 合并去重
	idSet := make(map[int64]struct{})
	for _, icID := range itemCardIDs {
		if icID > 0 {
			idSet[icID] = struct{}{}
		}
	}
	for _, icID := range itemCardIDsFromItemCards {
		if icID > 0 {
			idSet[icID] = struct{}{}
		}
	}
	// 执行退还
	for icID := range idSet {
		_ = w.Orders.WithContext(ctx).UnderlyingDB().Exec("UPDATE user_item_cards SET status=1, used_at=NULL, used_draw_log_id=0, used_activity_id=0, used_issue_id=0, updated_at=NOW(3) WHERE id=?", icID).Error
		l.Info("Refund: Restored item card", zap.Int64("icID", icID), zap.String("order_no", o.OrderNo))
	}

	// 4. Update Order Status
	_, _ = w.Orders.WithContext(ctx).Where(w.Orders.ID.Eq(o.ID)).Updates(map[string]any{w.Orders.Status.ColumnName().String(): 4})

	// 5. Log Refund
	iss = remark.Parse(o.Remark).IssueID
	_ = w.Orders.WithContext(ctx).UnderlyingDB().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, reason, "done").Error

	// 6. Compensation
	if refundCouponID > 0 {
		_ = us.AddCoupon(ctx, o.UserID, refundCouponID)
	}
}
