package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// RewardItem 简化的奖品结构
type RewardItem struct {
	ID     int64
	Name   string
	Weight int
	Count  int // for ichiban expansion
}

// VerifyUnlimited 验证无限赏结果
func VerifyUnlimited(seedHex string, issueID int64, userID int64, saltHex string, rewards []RewardItem) (int64, string, error) {
	// 1. Decode inputs
	seedKey, err := decodeHex(seedHex)
	if err != nil {
		return 0, "", fmt.Errorf("invalid seed: %v", err)
	}
	salt, err := decodeHex(saltHex)
	if err != nil {
		return 0, "", fmt.Errorf("invalid salt: %v", err)
	}

	// Sort rewards by ID to ensure consistency with backend (which usually iterates by ID)
	// This fixes issues where input JSON is not sorted.
	// Note: For Ichiban, sorting might be more complex (see VerifyIchiban comments).
	// But for Unlimited, ID sort is the confirmed behavior.
	sortRewardsByID(rewards)

	// 2. Calculate Total Weight
	var totalWeight int64
	for _, r := range rewards {
		totalWeight += int64(r.Weight)
	}
	if totalWeight <= 0 {
		return 0, "", errors.New("total weight must be > 0")
	}

	// 3. HMAC Logic (Same as strategy/default.go)
	mac := hmac.New(sha256.New, seedKey)
	mac.Write([]byte(fmt.Sprintf("draw:issue:%d|user:%d|salt:%x", issueID, userID, salt)))
	sum := mac.Sum(nil)
	rnd := int64(binary.BigEndian.Uint64(sum[:8]) % uint64(totalWeight))

	// 4. Select Reward
	var acc int64
	var pickedID int64
	var pickedName string

	processLog := fmt.Sprintf("Total Weight: %d\nRandom Value: %d\n", totalWeight, rnd)

	for _, r := range rewards {
		acc += int64(r.Weight)
		if rnd < acc {
			pickedID = r.ID
			pickedName = r.Name
			processLog += fmt.Sprintf("WIN: [%d] %s (Range: %d-%d)\n", r.ID, r.Name, acc-int64(r.Weight), acc)
			processLog += fmt.Sprintf("Selected Item Name: %s\n", pickedName)
			break
		}
	}

	return pickedID, processLog, nil
}

// VerifyIchiban 验证一番赏结果
func VerifyIchiban(seedHex string, issueID int64, slotIndex int, rewardsInput []RewardItem) (int64, string, error) {
	// 1. Expand Rewards to Slots
	// 一番赏逻辑：将 A:2, B:3 展开为 [A, A, B, B, B]
	// 且排序必须确定（ID升序 或 输入顺序? Backend is: IsBoss Desc, Level Asc, Sort Asc, ID Asc.
	// 验证工具这里简单起见，假设输入已经是有序的配置列表

	var slots []RewardItem
	for _, r := range rewardsInput {
		for k := 0; k < r.Count; k++ {
			slots = append(slots, r)
		}
	}

	totalSlots := len(slots)
	if totalSlots == 0 {
		return 0, "", errors.New("no slots generated from rewards")
	}

	if slotIndex < 1 || slotIndex > totalSlots {
		return 0, "", fmt.Errorf("slot index %d out of range [1, %d]", slotIndex, totalSlots)
	}

	// 2. Decode Seed
	seedKey, err := decodeHex(seedHex)
	if err != nil {
		return 0, "", fmt.Errorf("invalid seed: %v", err)
	}

	// 3. Shuffle (Same as strategy/ichiban.go)
	// Create a mapping index array to shuffle
	indices := make([]int, totalSlots)
	for i := 0; i < totalSlots; i++ {
		indices[i] = i
	}

	mac := hmac.New(sha256.New, seedKey)
	for i := totalSlots - 1; i > 0; i-- {
		mac.Reset()
		mac.Write([]byte(fmt.Sprintf("shuffle:%d|issue:%d", i, issueID)))
		sum := mac.Sum(nil)
		// j := rnd % (i+1)
		j := int(binary.BigEndian.Uint64(sum[:8]) % uint64(i+1))
		indices[i], indices[j] = indices[j], indices[i]
	}

	// 4. Get Result
	// slotIndex is 1-based from user input
	// mapped index is indices[slotIndex-1]
	// BUT wait, backend: `picked = slots[slotIndex]` (after shuffle logic applied to `slots`)
	// In the backend implementation:
	/*
		slots := ... // filled
		for i:=... {
		    swap(slots[i], slots[j])
		}
		picked := slots[slotIndex] // slotIndex passed from req (0-based inside function usually? Let's check backend)
	*/
	// Checked backend: `req.SlotIndex` comes from frontend.
	// `validateIchibanSlots` checks `si < 1`. Code uses `selectedSlots = append(..., si-1)`.
	// `SelectItemBySlot` uses `slotIndex` arg.
	// So `slotIndex` in `SelectItemBySlot` is 0-based.
	// Shuffle operates on `slots`. Finally returns `slots[slotIndex]`.
	// This means "Slot N" (user chosen) always points to PHYSICAL position N.
	// The CONTENT of position N changes after shuffle.
	// OK, my implementation below uses `indices` to track movements, let's align.

	// Re-implement exactly as backend: shuffle the actual slice
	workingSlots := make([]RewardItem, totalSlots)
	copy(workingSlots, slots)

	for i := totalSlots - 1; i > 0; i-- {
		mac.Reset()
		mac.Write([]byte(fmt.Sprintf("shuffle:%d|issue:%d", i, issueID)))
		sum := mac.Sum(nil)
		j := int(binary.BigEndian.Uint64(sum[:8]) % uint64(i+1))
		workingSlots[i], workingSlots[j] = workingSlots[j], workingSlots[i]
	}

	finalReward := workingSlots[slotIndex-1] // 1-based input -> 0-based index

	log := fmt.Sprintf("Total Slots: %d\nUser Selected Slot: %d\nResult Reward: [%d] %s\n", totalSlots, slotIndex, finalReward.ID, finalReward.Name)
	return finalReward.ID, log, nil
}

// Helpers

func decodeHex(s string) ([]byte, error) {
	if len(s)%2 != 0 {
		return nil, errors.New("hex string length must be even")
	}
	// naive hex decode or use pkg
	// here reusing simple implementation or adding imports
	return parseHex(s)
}

func parseHex(s string) ([]byte, error) {
	return hex.DecodeString(s)
}

// ParseRewardsString parses "ID:Weight,ID:Weight" or "ID:Name:Weight"
func ParseRewardsString(s string) ([]RewardItem, error) {
	parts := strings.Split(s, ",")
	var res []RewardItem
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		// Format: ID:Weight or ID:Name:Weight
		sub := strings.Split(p, ":")
		if len(sub) == 2 {
			id, _ := strconv.ParseInt(sub[0], 10, 64)
			w, _ := strconv.Atoi(sub[1])
			res = append(res, RewardItem{ID: id, Name: fmt.Sprintf("Item-%d", id), Weight: w, Count: w})
		} else if len(sub) == 3 {
			id, _ := strconv.ParseInt(sub[0], 10, 64)
			name := sub[1]
			w, _ := strconv.Atoi(sub[2])
			res = append(res, RewardItem{ID: id, Name: name, Weight: w, Count: w})
		}
	}
	return res, nil
}

func sortRewardsByID(rewards []RewardItem) {
	sort.Slice(rewards, func(i, j int) bool {
		return rewards[i].ID < rewards[j].ID
	})
}
