package strategy

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/binary"
    "errors"
    "fmt"
    "context"
    "bindbox-game/internal/repository/mysql/dao"
)

type ichibanStrategy struct {
    read  *dao.Query
    write *dao.Query
}

func NewIchiban(read *dao.Query, write *dao.Query) *ichibanStrategy {
    return &ichibanStrategy{read: read, write: write}
}

func (s *ichibanStrategy) SelectItemBySlot(ctx context.Context, activityID int64, issueID int64, slotIndex int64) (int64, map[string]any, error) {
    act, err := s.read.Activities.WithContext(ctx).Where(s.read.Activities.ID.Eq(activityID)).First()
    if err != nil || act == nil || len(act.CommitmentSeedMaster) == 0 { return 0, nil, errors.New("commitment not found") }
    rewards, err := s.read.ActivityRewardSettings.WithContext(ctx).ReadDB().Where(s.read.ActivityRewardSettings.IssueID.Eq(issueID)).Order(
        s.read.ActivityRewardSettings.Level.Desc(),
        s.read.ActivityRewardSettings.Sort.Asc(),
        s.read.ActivityRewardSettings.ID.Asc(),
    ).Find()
    if err != nil || len(rewards) == 0 { return 0, nil, errors.New("no rewards") }
    var totalSlots int64
    for _, r := range rewards {
        if r.OriginalQty > 0 { totalSlots += r.OriginalQty }
    }
    if totalSlots <= 0 { return 0, nil, errors.New("no slots") }
    if slotIndex < 0 || slotIndex >= totalSlots { return 0, nil, errors.New("slot out of range") }
    // build list
    slots := make([]int64, 0, totalSlots)
    for _, r := range rewards {
        for i := int64(0); i < r.OriginalQty; i++ { slots = append(slots, r.ID) }
    }
    // deterministic shuffle by server seed
    mac := hmac.New(sha256.New, act.CommitmentSeedMaster)
    for i := int(totalSlots-1); i > 0; i-- {
        mac.Reset()
        mac.Write([]byte(fmt.Sprintf("shuffle:%d|issue:%d", i, issueID)))
        sum := mac.Sum(nil)
        rnd := int(binary.BigEndian.Uint64(sum[:8]) % uint64(i+1))
        slots[i], slots[rnd] = slots[rnd], slots[i]
    }
    picked := slots[slotIndex]
    proof := map[string]any{"total_slots": totalSlots, "slot_index": slotIndex}
    return picked, proof, nil
}

func (s *ichibanStrategy) GrantReward(ctx context.Context, userID int64, rewardID int64) error {
	result, err := s.write.ActivityRewardSettings.WithContext(ctx).Where(
		s.write.ActivityRewardSettings.ID.Eq(rewardID),
		s.write.ActivityRewardSettings.Quantity.Gt(0),
	).UpdateSimple(s.write.ActivityRewardSettings.Quantity.Add(-1))
	if err != nil {
		return err
	}
	if result.RowsAffected == 0 {
		return errors.New("sold out or reward not found")
	}
	return nil
}
