package app

import (
	"bindbox-game/internal/code"
	"bindbox-game/internal/pkg/core"
	"bindbox-game/internal/pkg/validation"
	"bindbox-game/internal/repository/mysql/model"
	strat "bindbox-game/internal/service/activity/strategy"
	usersvc "bindbox-game/internal/service/user"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"time"
)

type orderResultQuery struct {
	OrderNo string `form:"order_no"`
}

type orderResultItem struct {
	RewardID   int64  `json:"reward_id"`
	RewardName string `json:"reward_name"`
	Level      int32  `json:"level"`
	DrawIndex  int32  `json:"draw_index"`
}

type orderResultResponse struct {
	Status       string            `json:"status"`
	DrawMode     string            `json:"draw_mode"`
	Count        int64             `json:"count"`
	Completed    int64             `json:"completed"`
	Results      []orderResultItem `json:"results"`
	Receipt      map[string]any    `json:"receipt,omitempty"`
	NextPollMs   int               `json:"nextPollMs"`
	SeedHex      string            `json:"seed_hex,omitempty"`
	NextDrawTime string            `json:"next_draw_time,omitempty"`
}

// LotteryResultByOrder 抽奖订单结果查询
// @Summary 抽奖订单结果查询
// @Description 根据订单号查询抽奖结果与进度，返回结果明细与可验证凭证；需登录
// @Tags APP端.抽奖
// @Accept json
// @Produce json
// @Security LoginVerifyToken
// @Param order_no query string true "订单号"
// @Success 200 {object} orderResultResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/lottery/result [get]
func (h *handler) LotteryResultByOrder() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(orderResultQuery)
		if err := ctx.ShouldBindQuery(req); err != nil || req.OrderNo == "" {
			ctx.AbortWithError(core.Error(400, code.ParamBindError, validation.Error(err)))
			return
		}
		userID := int64(ctx.SessionUserInfo().Id)
		ord, err := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.OrderNo.Eq(req.OrderNo), h.readDB.Orders.UserID.Eq(userID)).First()
		if err != nil || ord == nil {
			ctx.AbortWithError(core.Error(400, code.ParamBindError, "order not found"))
			return
		}
		iss := parseIssueIDFromRemark(ord.Remark)
		dc := parseCountFromRemark(ord.Remark)
		if dc <= 0 {
			dc = 1
		}
		logs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(ord.ID)).Find()
		completed := int64(len(logs))
		items := make([]orderResultItem, 0, len(logs))
		rewardNameCache := map[int64]string{}
		drawLogIDs := make([]int64, 0, len(logs))
		for i := 0; i < len(logs); i++ {
			lg := logs[i]
			drawLogIDs = append(drawLogIDs, lg.ID)
			rid := lg.RewardID
			name := rewardNameCache[rid]
			if name == "" && rid > 0 {
				rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First()
				if rw != nil {
					name = rw.Name
					rewardNameCache[rid] = name
				}
			}
			items = append(items, orderResultItem{RewardID: rid, RewardName: name, Level: lg.Level, DrawIndex: int32(i + 1)})
		}
		mode := "scheduled"
		if iss > 0 {
			issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First()
			aid := int64(0)
			if issue != nil {
				aid = issue.ActivityID
			}
			if aid > 0 {
				act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(aid)).First()
				if act != nil && act.DrawMode != "" {
					mode = act.DrawMode
				}
			}
		}
		st := "pending"
		if ord.Status == 4 {
			st = "refunded"
		} else if ord.Status == 2 {
			if completed < dc {
				// 即时模式支持在结果查询中补开奖
				if mode == "instant" && iss > 0 {
					// 执行一次开奖
					// 读取活动与策略
					act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(func() int64 {
						if iss > 0 {
							issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First()
							if issue != nil {
								return issue.ActivityID
							}
						}
						return 0
					}())).First()
					if act != nil {
						// 一次补抽
						rid := int64(0)
						var e2 error
						if act.PlayType == "ichiban" {
							slot := parseSlotFromRemark(ord.Remark)
							if slot >= 0 {
								var cnt int64
								_ = h.repo.GetDbR().Raw("SELECT COUNT(*) FROM issue_position_claims WHERE issue_id=? AND slot_index=?", iss, slot).Scan(&cnt).Error
								if cnt > 0 {
									st = "slot_unavailable"
								} else if err := h.repo.GetDbW().Exec("INSERT INTO issue_position_claims (issue_id, slot_index, user_id, order_id, created_at) VALUES (?,?,?,?,NOW(3))", iss, slot, userID, ord.ID).Error; err == nil {
									rid, _, e2 = strat.NewIchiban(h.readDB, h.writeDB).SelectItemBySlot(ctx.RequestContext(), act.ID, iss, slot)
									if e2 == nil && rid > 0 {
										rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First()
										if rw != nil {
											_ = strat.NewIchiban(h.readDB, h.writeDB).GrantReward(ctx.RequestContext(), userID, rid)
											_ = h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(&model.ActivityDrawLogs{UserID: userID, IssueID: iss, OrderID: ord.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1})
											completed++
											items = append(items, orderResultItem{RewardID: rid, RewardName: rw.Name, Level: rw.Level, DrawIndex: int32(completed)})
										}
									}
								}
							}
						} else {
							sel := strat.NewDefault(h.readDB, h.writeDB)
							rid, _, e2 = sel.SelectItem(ctx.RequestContext(), act.ID, iss, userID)
							if e2 == nil && rid > 0 {
								rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First()
								if rw != nil {
									_, _ = h.user.GrantRewardToOrder(ctx.RequestContext(), userID, usersvc.GrantRewardToOrderRequest{OrderID: ord.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &act.ID, RewardID: &rid, Remark: rw.Name})
									_ = h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(&model.ActivityDrawLogs{UserID: userID, IssueID: iss, OrderID: ord.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1})
									completed++
									items = append(items, orderResultItem{RewardID: rid, RewardName: rw.Name, Level: rw.Level, DrawIndex: int32(completed)})
								}
							}
						}
					}
				}
				if st == "pending" {
					st = "paid_waiting"
				}
			} else {
				st = "settled"
			}
		}
		var receipt map[string]any
		var randomReceipts []map[string]any
		if len(drawLogIDs) > 0 {
			if recs, _ := h.readDB.ActivityDrawReceipts.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawReceipts.DrawLogID.In(drawLogIDs...)).Find(); len(recs) > 0 {
				recMap := make(map[int64]*model.ActivityDrawReceipts, len(recs))
				for _, r := range recs {
					recMap[r.DrawLogID] = r
				}
				randomReceipts = make([]map[string]any, 0, len(recs))
				for i := 0; i < len(logs); i++ {
					lg := logs[i]
					if r, ok := recMap[lg.ID]; ok {
						randomReceipts = append(randomReceipts, map[string]any{
							"draw_log_id":      lg.ID,
							"reward_id":        lg.RewardID,
							"draw_index":       i + 1,
							"algo_version":     r.AlgoVersion,
							"round_id":         r.RoundID,
							"draw_id":          r.DrawID,
							"client_id":        r.ClientID,
							"timestamp":        r.Timestamp,
							"server_seed_hash": r.ServerSeedHash,
							"server_sub_seed":  r.ServerSubSeed,
							"client_seed":      r.ClientSeed,
							"nonce":            r.Nonce,
							"items_root":       r.ItemsRoot,
							"weights_total":    r.WeightsTotal,
							"selected_index":   r.SelectedIndex,
							"rand_proof":       r.RandProof,
							"signature":        r.Signature,
							"items_snapshot":   r.ItemsSnapshot,
						})
					}
				}
			}
		}
		aid := int64(0)
		if iss > 0 {
			issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First()
			if issue != nil {
				aid = issue.ActivityID
			}
		}
		if aid > 0 {
			act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(aid)).First()
			if act != nil && len(act.CommitmentSeedMaster) > 0 {
				ts := time.Now().UnixMilli()
				nonce := ts
				mac := hmac.New(sha256.New, act.CommitmentSeedMaster)
				mac.Write([]byte(h.joinSigPayload(userID, iss, ts, nonce)))
				sig := base64.StdEncoding.EncodeToString(mac.Sum(nil))
				receipt = map[string]any{
					"issue_id":     iss,
					"seed_version": act.CommitmentStateVersion,
					"timestamp":    ts,
					"nonce":        nonce,
					"signature":    sig,
					"algorithm":    "HMAC-SHA256",
					"inputs":       map[string]any{"user_id": userID, "issue_id": iss, "order_id": ord.ID, "timestamp": ts, "nonce": nonce},
				}
			}
		}
		if len(randomReceipts) > 0 {
			if receipt == nil {
				receipt = map[string]any{}
			}
			receipt["draw_receipts"] = randomReceipts
		}
		// 构建响应，包含随机种子和下次开奖时间
		var seedHex string
		var nextDrawTime string
		if aid > 0 {
			act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(aid)).First()
			if act != nil {
				// 问题2: 返回随机种子（十六进制格式）
				if len(act.CommitmentSeedMaster) > 0 {
					seedHex = hex.EncodeToString(act.CommitmentSeedMaster)
				}
				// 问题3: 定时抽奖返回下一次开奖时间
				if mode == "scheduled" && !act.ScheduledTime.IsZero() {
					nextDrawTime = act.ScheduledTime.Format(time.RFC3339)
				}
			}
		}
		rsp := &orderResultResponse{Status: st, DrawMode: mode, Count: dc, Completed: completed, Results: items, Receipt: receipt, NextPollMs: 2000, SeedHex: seedHex, NextDrawTime: nextDrawTime}
		ctx.Payload(rsp)
	}
}

func parseIssueIDFromRemark(remark string) int64 {
	if remark == "" {
		return 0
	}
	p := 0
	for i := 0; i < len(remark); i++ {
		if remark[i] == '|' {
			seg := remark[p:i]
			if len(seg) > 6 && seg[:6] == "issue:" {
				var n int64
				for j := 6; j < len(seg); j++ {
					c := seg[j]
					if c < '0' || c > '9' {
						break
					}
					n = n*10 + int64(c-'0')
				}
				return n
			}
			p = i + 1
		}
	}
	return 0
}

func parseCountFromRemark(remark string) int64 {
	if remark == "" {
		return 1
	}
	p := 0
	for i := 0; i < len(remark); i++ {
		if remark[i] == '|' {
			seg := remark[p:i]
			if len(seg) > 6 && seg[:6] == "count:" {
				var n int64
				for j := 6; j < len(seg); j++ {
					c := seg[j]
					if c < '0' || c > '9' {
						break
					}
					n = n*10 + int64(c-'0')
				}
				if n <= 0 {
					return 1
				}
				return n
			}
			p = i + 1
		}
	}
	return 1
}
