package activity

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

type IchibanSlotsService struct {
	read *dao.Query
	repo mysql.Repo
}

func NewIchibanSlotsService(read *dao.Query, repo mysql.Repo) *IchibanSlotsService {
	return &IchibanSlotsService{read: read, repo: repo}
}

func (s *IchibanSlotsService) buildMapping(ctx context.Context, activityID int64, issueID int64) ([]int64, error) {
	var seedHex string
	if err := s.repo.GetDbR().Raw("SELECT HEX(commitment_seed_master) FROM activities WHERE id=?", activityID).Scan(&seedHex).Error; err != nil || seedHex == "" {
		return nil, errors.New("commitment not found")
	}
	seed, _ := hex.DecodeString(seedHex)
	rewards, err := s.read.ActivityRewardSettings.WithContext(ctx).ReadDB().Where(s.read.ActivityRewardSettings.IssueID.Eq(issueID)).Order(
		s.read.ActivityRewardSettings.IsBoss.Desc(),
		s.read.ActivityRewardSettings.Level.Asc(),
		s.read.ActivityRewardSettings.Sort.Asc(),
		s.read.ActivityRewardSettings.ID.Asc(),
	).Find()
	if err != nil || len(rewards) == 0 {
		return nil, errors.New("no rewards")
	}
	// 一番赏：每种奖品 = 1个格位
	total := int64(len(rewards))
	if total <= 0 {
		return nil, errors.New("no slots")
	}
	slots := make([]int64, total)
	for i, r := range rewards {
		slots[i] = r.ID
	}
	mac := hmac.New(sha256.New, seed)
	for i := int(total - 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]
	}
	return slots, nil
}

type SlotItem struct {
	SlotIndex    int64
	RewardID     int64
	RewardName   string
	Level        int32
	ProductImage string
	Claimed      bool
}

func (s *IchibanSlotsService) Page(ctx context.Context, activityID int64, issueID int64, page int, pageSize int, claimedFilter *bool) (int64, []SlotItem, error) {
	mapping, err := s.buildMapping(ctx, activityID, issueID)
	if err != nil {
		return 0, nil, err
	}
	total := int64(len(mapping))
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 50
	}
	start := int64((page - 1) * pageSize)
	end := start + int64(pageSize)
	if start > total {
		return total, []SlotItem{}, nil
	}
	if end > total {
		end = total
	}
	var claimedIdx []int64
	if err := s.repo.GetDbR().Raw("SELECT slot_index FROM issue_position_claims WHERE issue_id = ?", issueID).Scan(&claimedIdx).Error; err != nil {
		claimedIdx = []int64{}
	}
	used := map[int64]struct{}{}
	for _, x := range claimedIdx {
		used[x] = struct{}{}
	}
	items := make([]SlotItem, 0, end-start)
	for i := start; i < end; i++ {
		rid := mapping[i]
		rw, _ := s.read.ActivityRewardSettings.WithContext(ctx).Where(s.read.ActivityRewardSettings.ID.Eq(rid)).First()
		if rw == nil {
			continue
		}
		var img string
		var productName string
		if rw.ProductID > 0 {
			p, _ := s.read.Products.WithContext(ctx).Where(s.read.Products.ID.Eq(rw.ProductID)).First()
			if p != nil {
				productName = p.Name
				if p.ImagesJSON != "" {
					var arr []string
					_ = json.Unmarshal([]byte(p.ImagesJSON), &arr)
					if len(arr) > 0 {
						img = arr[0]
					}
				}
			}
		}
		c := false
		if _, ok := used[i]; ok {
			c = true
		}
		if claimedFilter != nil {
			if *claimedFilter && !c {
				continue
			}
			if !*claimedFilter && c {
				continue
			}
		}
		items = append(items, SlotItem{SlotIndex: i + 1, RewardID: rid, RewardName: productName, Level: rw.Level, ProductImage: img, Claimed: c})
	}
	return total, items, nil
}

func (s *IchibanSlotsService) SlotDetail(ctx context.Context, activityID int64, issueID int64, slotIndex int64) (SlotItem, error) {
	mapping, err := s.buildMapping(ctx, activityID, issueID)
	if err != nil {
		return SlotItem{}, err
	}
	idx0 := slotIndex - 1
	if idx0 < 0 || idx0 >= int64(len(mapping)) {
		return SlotItem{}, errors.New("out of range")
	}
	rid := mapping[idx0]
	rw, _ := s.read.ActivityRewardSettings.WithContext(ctx).Where(s.read.ActivityRewardSettings.ID.Eq(rid)).First()
	if rw == nil {
		return SlotItem{}, errors.New("reward not found")
	}
	var img string
	var productName string
	if rw.ProductID > 0 {
		p, _ := s.read.Products.WithContext(ctx).Where(s.read.Products.ID.Eq(rw.ProductID)).First()
		if p != nil {
			productName = p.Name
			if p.ImagesJSON != "" {
				var arr []string
				_ = json.Unmarshal([]byte(p.ImagesJSON), &arr)
				if len(arr) > 0 {
					img = arr[0]
				}
			}
		}
	}
	var claimed int64
	_ = s.repo.GetDbR().Raw("SELECT COUNT(1) FROM issue_position_claims WHERE issue_id=? AND slot_index=?", issueID, idx0).Scan(&claimed)
	return SlotItem{SlotIndex: slotIndex, RewardID: rid, RewardName: productName, Level: rw.Level, ProductImage: img, Claimed: claimed > 0}, nil
}
