package channelserver

import (
	"testing"

	"erupe-ce/common/byteframe"
	cfg "erupe-ce/config"
)

func TestWriteShopItems_Empty(t *testing.T) {
	bf := byteframe.NewByteFrame()
	items := []ShopItem{}

	writeShopItems(bf, items, cfg.ZZ)

	result := byteframe.NewByteFrameFromBytes(bf.Data())
	count1 := result.ReadUint16()
	count2 := result.ReadUint16()

	if count1 != 0 {
		t.Errorf("Expected first count 0, got %d", count1)
	}
	if count2 != 0 {
		t.Errorf("Expected second count 0, got %d", count2)
	}
}

func TestWriteShopItems_SingleItem(t *testing.T) {
	bf := byteframe.NewByteFrame()
	items := []ShopItem{
		{
			ID:           1,
			ItemID:       100,
			Cost:         500,
			Quantity:     10,
			MinHR:        1,
			MinSR:        0,
			MinGR:        0,
			StoreLevel:   1,
			MaxQuantity:  99,
			UsedQuantity: 5,
			RoadFloors:   0,
			RoadFatalis:  0,
		},
	}

	writeShopItems(bf, items, cfg.ZZ)

	result := byteframe.NewByteFrameFromBytes(bf.Data())
	count1 := result.ReadUint16()
	count2 := result.ReadUint16()

	if count1 != 1 {
		t.Errorf("Expected first count 1, got %d", count1)
	}
	if count2 != 1 {
		t.Errorf("Expected second count 1, got %d", count2)
	}

	// Read the item data
	id := result.ReadUint32()
	_ = result.ReadUint16() // padding
	itemID := result.ReadUint16()
	cost := result.ReadUint32()
	quantity := result.ReadUint16()
	minHR := result.ReadUint16()
	minSR := result.ReadUint16()
	minGR := result.ReadUint16()
	storeLevel := result.ReadUint16()
	maxQuantity := result.ReadUint16()
	usedQuantity := result.ReadUint16()
	roadFloors := result.ReadUint16()
	roadFatalis := result.ReadUint16()

	if id != 1 {
		t.Errorf("Expected ID 1, got %d", id)
	}
	if itemID != 100 {
		t.Errorf("Expected itemID 100, got %d", itemID)
	}
	if cost != 500 {
		t.Errorf("Expected cost 500, got %d", cost)
	}
	if quantity != 10 {
		t.Errorf("Expected quantity 10, got %d", quantity)
	}
	if minHR != 1 {
		t.Errorf("Expected minHR 1, got %d", minHR)
	}
	if minSR != 0 {
		t.Errorf("Expected minSR 0, got %d", minSR)
	}
	if minGR != 0 {
		t.Errorf("Expected minGR 0, got %d", minGR)
	}
	if storeLevel != 1 {
		t.Errorf("Expected storeLevel 1, got %d", storeLevel)
	}
	if maxQuantity != 99 {
		t.Errorf("Expected maxQuantity 99, got %d", maxQuantity)
	}
	if usedQuantity != 5 {
		t.Errorf("Expected usedQuantity 5, got %d", usedQuantity)
	}
	if roadFloors != 0 {
		t.Errorf("Expected roadFloors 0, got %d", roadFloors)
	}
	if roadFatalis != 0 {
		t.Errorf("Expected roadFatalis 0, got %d", roadFatalis)
	}
}

func TestWriteShopItems_MultipleItems(t *testing.T) {
	bf := byteframe.NewByteFrame()
	items := []ShopItem{
		{ID: 1, ItemID: 100, Cost: 500, Quantity: 10},
		{ID: 2, ItemID: 200, Cost: 1000, Quantity: 5},
		{ID: 3, ItemID: 300, Cost: 2000, Quantity: 1},
	}

	writeShopItems(bf, items, cfg.ZZ)

	result := byteframe.NewByteFrameFromBytes(bf.Data())
	count1 := result.ReadUint16()
	count2 := result.ReadUint16()

	if count1 != 3 {
		t.Errorf("Expected first count 3, got %d", count1)
	}
	if count2 != 3 {
		t.Errorf("Expected second count 3, got %d", count2)
	}
}

// Test struct definitions
func TestShopItemStruct(t *testing.T) {
	item := ShopItem{
		ID:           42,
		ItemID:       1234,
		Cost:         9999,
		Quantity:     50,
		MinHR:        10,
		MinSR:        5,
		MinGR:        100,
		StoreLevel:   3,
		MaxQuantity:  99,
		UsedQuantity: 10,
		RoadFloors:   50,
		RoadFatalis:  25,
	}

	if item.ID != 42 {
		t.Errorf("ID = %d, want 42", item.ID)
	}
	if item.ItemID != 1234 {
		t.Errorf("ItemID = %d, want 1234", item.ItemID)
	}
	if item.Cost != 9999 {
		t.Errorf("Cost = %d, want 9999", item.Cost)
	}
}

func TestGachaStruct(t *testing.T) {
	gacha := Gacha{
		ID:           1,
		MinGR:        100,
		MinHR:        999,
		Name:         "Test Gacha",
		URLBanner:    "http://example.com/banner.png",
		URLFeature:   "http://example.com/feature.png",
		URLThumbnail: "http://example.com/thumb.png",
		Wide:         true,
		Recommended:  true,
		GachaType:    2,
		Hidden:       false,
	}

	if gacha.ID != 1 {
		t.Errorf("ID = %d, want 1", gacha.ID)
	}
	if gacha.Name != "Test Gacha" {
		t.Errorf("Name = %s, want Test Gacha", gacha.Name)
	}
	if !gacha.Wide {
		t.Error("Wide should be true")
	}
	if !gacha.Recommended {
		t.Error("Recommended should be true")
	}
}

func TestGachaEntryStruct(t *testing.T) {
	entry := GachaEntry{
		EntryType:      1,
		ID:             100,
		ItemType:       0,
		ItemNumber:     1234,
		ItemQuantity:   10,
		Weight:         0.5,
		Rarity:         3,
		Rolls:          1,
		FrontierPoints: 500,
		DailyLimit:     5,
	}

	if entry.EntryType != 1 {
		t.Errorf("EntryType = %d, want 1", entry.EntryType)
	}
	if entry.ID != 100 {
		t.Errorf("ID = %d, want 100", entry.ID)
	}
	if entry.Weight != 0.5 {
		t.Errorf("Weight = %f, want 0.5", entry.Weight)
	}
}

func TestGachaItemStruct(t *testing.T) {
	item := GachaItem{
		ItemType: 0,
		ItemID:   5678,
		Quantity: 20,
	}

	if item.ItemType != 0 {
		t.Errorf("ItemType = %d, want 0", item.ItemType)
	}
	if item.ItemID != 5678 {
		t.Errorf("ItemID = %d, want 5678", item.ItemID)
	}
	if item.Quantity != 20 {
		t.Errorf("Quantity = %d, want 20", item.Quantity)
	}
}

func TestGetRandomEntries_ZeroRolls(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 1.0},
	}
	result, err := getRandomEntries(entries, 0, false)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 0 {
		t.Errorf("expected 0 results, got %d", len(result))
	}
}

func TestGetRandomEntries_SingleEntryNonBox(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 1.0, ItemNumber: 100},
	}
	result, err := getRandomEntries(entries, 3, false)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 3 {
		t.Errorf("expected 3 results, got %d", len(result))
	}
	for i, r := range result {
		if r.ID != 1 {
			t.Errorf("result[%d].ID = %d, expected 1", i, r.ID)
		}
	}
}

func TestGetRandomEntries_NonBoxAllowsDuplicates(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 1.0},
	}
	result, err := getRandomEntries(entries, 5, false)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 5 {
		t.Errorf("expected 5 results, got %d", len(result))
	}
	// All should be the same since there's only one entry
	for i, r := range result {
		if r.ID != 1 {
			t.Errorf("result[%d].ID = %d, expected 1", i, r.ID)
		}
	}
}

func TestGetRandomEntries_BoxModeRemovesSelected(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 1.0},
		{ID: 2, Weight: 1.0},
		{ID: 3, Weight: 1.0},
	}
	result, err := getRandomEntries(entries, 3, true)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 3 {
		t.Errorf("expected 3 results, got %d", len(result))
	}

	// In box mode, all entries should be unique
	seen := make(map[uint32]bool)
	for _, r := range result {
		if seen[r.ID] {
			t.Errorf("duplicate entry in box mode: ID=%d", r.ID)
		}
		seen[r.ID] = true
	}
}

func TestGetRandomEntries_BoxModeMatchingCount(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 1.0},
		{ID: 2, Weight: 1.0},
	}
	result, err := getRandomEntries(entries, 2, true)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 2 {
		t.Errorf("expected 2 results, got %d", len(result))
	}

	// Should contain both entries exactly once
	seen := make(map[uint32]bool)
	for _, r := range result {
		seen[r.ID] = true
	}
	if !seen[1] || !seen[2] {
		t.Errorf("box mode should return all entries when rolls == len(entries)")
	}
}

func TestGetRandomEntries_WeightedSelectionBias(t *testing.T) {
	// Test that weighted selection respects weights
	entries := []GachaEntry{
		{ID: 1, Weight: 100.0}, // Very high weight
		{ID: 2, Weight: 0.001}, // Very low weight
	}

	// Run many iterations
	counts := make(map[uint32]int)
	for i := 0; i < 1000; i++ {
		result, _ := getRandomEntries(entries, 1, false)
		if len(result) > 0 {
			counts[result[0].ID]++
		}
	}

	// ID 1 should be selected much more often
	if counts[1] <= counts[2] {
		t.Errorf("weighted selection not working: high weight count=%d, low weight count=%d",
			counts[1], counts[2])
	}
}

func TestGetRandomEntries_MultipleEntriesMultipleRolls(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 1.0},
		{ID: 2, Weight: 1.0},
		{ID: 3, Weight: 1.0},
	}
	result, err := getRandomEntries(entries, 10, false)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 10 {
		t.Errorf("expected 10 results, got %d", len(result))
	}

	// All results should have valid IDs
	for i, r := range result {
		if r.ID < 1 || r.ID > 3 {
			t.Errorf("result[%d].ID = %d, expected 1, 2, or 3", i, r.ID)
		}
	}
}

func TestGetRandomEntries_PreservesEntryData(t *testing.T) {
	entries := []GachaEntry{
		{
			ID:             1,
			Weight:         1.0,
			ItemNumber:     100,
			ItemQuantity:   5,
			Rarity:         3,
			FrontierPoints: 500,
		},
	}
	result, err := getRandomEntries(entries, 1, false)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(result) != 1 {
		t.Fatalf("expected 1 result, got %d", len(result))
	}

	r := result[0]
	if r.ItemNumber != 100 {
		t.Errorf("ItemNumber = %d, expected 100", r.ItemNumber)
	}
	if r.ItemQuantity != 5 {
		t.Errorf("ItemQuantity = %d, expected 5", r.ItemQuantity)
	}
	if r.Rarity != 3 {
		t.Errorf("Rarity = %d, expected 3", r.Rarity)
	}
	if r.FrontierPoints != 500 {
		t.Errorf("FrontierPoints = %d, expected 500", r.FrontierPoints)
	}
}
