package channelserver

import (
	"database/sql"
	"errors"
	"testing"

	"github.com/jmoiron/sqlx"
)

func setupGachaRepo(t *testing.T) (*GachaRepository, *sqlx.DB, uint32) {
	t.Helper()
	db := SetupTestDB(t)
	userID := CreateTestUser(t, db, "gacha_test_user")
	charID := CreateTestCharacter(t, db, userID, "GachaChar")
	repo := NewGachaRepository(db, nil)
	t.Cleanup(func() { TeardownTestDB(t, db) })
	return repo, db, charID
}

func TestRepoGachaListShopEmpty(t *testing.T) {
	repo, _, _ := setupGachaRepo(t)

	shops, err := repo.ListShop()
	if err != nil {
		t.Fatalf("ListShop failed: %v", err)
	}
	if len(shops) != 0 {
		t.Errorf("Expected empty shop list, got: %d", len(shops))
	}
}

func TestRepoGachaListShop(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	CreateTestGachaShop(t, db, "Test Gacha", 1)
	CreateTestGachaShop(t, db, "Premium Gacha", 2)

	shops, err := repo.ListShop()
	if err != nil {
		t.Fatalf("ListShop failed: %v", err)
	}
	if len(shops) != 2 {
		t.Fatalf("Expected 2 shops, got: %d", len(shops))
	}
}

func TestRepoGachaGetShopType(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Type Test", 3)

	gachaType, err := repo.GetShopType(shopID)
	if err != nil {
		t.Fatalf("GetShopType failed: %v", err)
	}
	if gachaType != 3 {
		t.Errorf("Expected gacha_type=3, got: %d", gachaType)
	}
}

func TestRepoGachaGetEntryForTransaction(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Entry Test", 1)
	_, err := db.Exec(
		`INSERT INTO gacha_entries (gacha_id, entry_type, weight, rarity, item_type, item_number, item_quantity, rolls, frontier_points, daily_limit)
		VALUES ($1, 5, 100, 1, 7, 500, 10, 3, 0, 0)`, shopID,
	)
	if err != nil {
		t.Fatalf("Setup failed: %v", err)
	}

	itemType, itemNumber, rolls, err := repo.GetEntryForTransaction(shopID, 5)
	if err != nil {
		t.Fatalf("GetEntryForTransaction failed: %v", err)
	}
	if itemType != 7 {
		t.Errorf("Expected itemType=7, got: %d", itemType)
	}
	if itemNumber != 500 {
		t.Errorf("Expected itemNumber=500, got: %d", itemNumber)
	}
	if rolls != 3 {
		t.Errorf("Expected rolls=3, got: %d", rolls)
	}
}

func TestRepoGachaGetRewardPoolEmpty(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Empty Pool", 1)

	entries, err := repo.GetRewardPool(shopID)
	if err != nil {
		t.Fatalf("GetRewardPool failed: %v", err)
	}
	if len(entries) != 0 {
		t.Errorf("Expected empty reward pool, got: %d", len(entries))
	}
}

func TestRepoGachaGetRewardPoolOrdering(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Pool Test", 1)
	// entry_type=100 is the reward pool; each entry needs at least one item
	e1 := CreateTestGachaEntry(t, db, shopID, 100, 50)
	CreateTestGachaItem(t, db, e1, 1, 100, 1)
	e2 := CreateTestGachaEntry(t, db, shopID, 100, 200)
	CreateTestGachaItem(t, db, e2, 1, 101, 1)
	e3 := CreateTestGachaEntry(t, db, shopID, 100, 100)
	CreateTestGachaItem(t, db, e3, 1, 102, 1)
	// entry_type=5 should NOT appear in reward pool
	e4 := CreateTestGachaEntry(t, db, shopID, 5, 999)
	CreateTestGachaItem(t, db, e4, 1, 103, 1)

	entries, err := repo.GetRewardPool(shopID)
	if err != nil {
		t.Fatalf("GetRewardPool failed: %v", err)
	}
	if len(entries) != 3 {
		t.Fatalf("Expected 3 reward entries, got: %d", len(entries))
	}
	// Should be ordered by weight DESC
	if entries[0].Weight < entries[1].Weight || entries[1].Weight < entries[2].Weight {
		t.Errorf("Expected descending weight order, got: %v, %v, %v", entries[0].Weight, entries[1].Weight, entries[2].Weight)
	}
}

func TestRepoGachaGetItemsForEntry(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Items Test", 1)
	entryID := CreateTestGachaEntry(t, db, shopID, 100, 100)
	CreateTestGachaItem(t, db, entryID, 1, 100, 5)
	CreateTestGachaItem(t, db, entryID, 2, 200, 10)

	items, err := repo.GetItemsForEntry(entryID)
	if err != nil {
		t.Fatalf("GetItemsForEntry failed: %v", err)
	}
	if len(items) != 2 {
		t.Fatalf("Expected 2 items, got: %d", len(items))
	}
}

func TestRepoGachaGetGuaranteedItems(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Guaranteed Test", 1)
	entryID := CreateTestGachaEntry(t, db, shopID, 10, 0)
	CreateTestGachaItem(t, db, entryID, 3, 300, 1)

	items, err := repo.GetGuaranteedItems(10, shopID)
	if err != nil {
		t.Fatalf("GetGuaranteedItems failed: %v", err)
	}
	if len(items) != 1 {
		t.Fatalf("Expected 1 guaranteed item, got: %d", len(items))
	}
	if items[0].ItemID != 300 {
		t.Errorf("Expected item_id=300, got: %d", items[0].ItemID)
	}
}

func TestRepoGachaGetAllEntries(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "All Entries", 1)
	CreateTestGachaEntry(t, db, shopID, 100, 50)
	CreateTestGachaEntry(t, db, shopID, 5, 200)

	entries, err := repo.GetAllEntries(shopID)
	if err != nil {
		t.Fatalf("GetAllEntries failed: %v", err)
	}
	if len(entries) != 2 {
		t.Fatalf("Expected 2 entries, got: %d", len(entries))
	}
}

func TestRepoGachaGetWeightDivisorZero(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Zero Weight", 1)

	divisor, err := repo.GetWeightDivisor(shopID)
	if err != nil {
		t.Fatalf("GetWeightDivisor failed: %v", err)
	}
	if divisor != 0 {
		t.Errorf("Expected divisor=0 for empty, got: %f", divisor)
	}
}

func TestRepoGachaGetWeightDivisor(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Weight Test", 1)
	CreateTestGachaEntry(t, db, shopID, 100, 50000)
	CreateTestGachaEntry(t, db, shopID, 100, 50000)

	divisor, err := repo.GetWeightDivisor(shopID)
	if err != nil {
		t.Fatalf("GetWeightDivisor failed: %v", err)
	}
	// (50000 + 50000) / 100000 = 1.0
	if divisor != 1.0 {
		t.Errorf("Expected divisor=1.0, got: %f", divisor)
	}
}

func TestRepoGachaHasEntryTypeTrue(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "HasType Test", 1)
	CreateTestGachaEntry(t, db, shopID, 100, 50)

	has, err := repo.HasEntryType(shopID, 100)
	if err != nil {
		t.Fatalf("HasEntryType failed: %v", err)
	}
	if !has {
		t.Error("Expected HasEntryType=true for entry_type=100")
	}
}

func TestRepoGachaHasEntryTypeFalse(t *testing.T) {
	repo, db, _ := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "HasType False", 1)

	has, err := repo.HasEntryType(shopID, 100)
	if err != nil {
		t.Fatalf("HasEntryType failed: %v", err)
	}
	if has {
		t.Error("Expected HasEntryType=false for empty gacha")
	}
}

// Stepup tests

func TestRepoGachaStepupLifecycle(t *testing.T) {
	repo, db, charID := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Stepup Test", 1)

	// Insert stepup
	if err := repo.InsertStepup(shopID, 1, charID); err != nil {
		t.Fatalf("InsertStepup failed: %v", err)
	}

	// Get step
	step, err := repo.GetStepupStep(shopID, charID)
	if err != nil {
		t.Fatalf("GetStepupStep failed: %v", err)
	}
	if step != 1 {
		t.Errorf("Expected step=1, got: %d", step)
	}

	// Delete stepup
	if err := repo.DeleteStepup(shopID, charID); err != nil {
		t.Fatalf("DeleteStepup failed: %v", err)
	}

	// Get step should fail
	_, err = repo.GetStepupStep(shopID, charID)
	if err == nil {
		t.Fatal("Expected error after DeleteStepup, got nil")
	}
}

func TestRepoGachaGetStepupWithTime(t *testing.T) {
	repo, db, charID := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Stepup Time", 1)

	if err := repo.InsertStepup(shopID, 2, charID); err != nil {
		t.Fatalf("InsertStepup failed: %v", err)
	}

	step, createdAt, err := repo.GetStepupWithTime(shopID, charID)
	if err != nil {
		t.Fatalf("GetStepupWithTime failed: %v", err)
	}
	if step != 2 {
		t.Errorf("Expected step=2, got: %d", step)
	}
	if createdAt.IsZero() {
		t.Error("Expected non-zero created_at")
	}
}

func TestRepoGachaGetStepupWithTimeNotFound(t *testing.T) {
	repo, db, charID := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Stepup NF", 1)

	_, _, err := repo.GetStepupWithTime(shopID, charID)
	if !errors.Is(err, sql.ErrNoRows) {
		t.Fatalf("Expected sql.ErrNoRows, got: %v", err)
	}
}

// Box gacha tests

func TestRepoGachaBoxLifecycle(t *testing.T) {
	repo, db, charID := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Box Test", 1)
	entryID1 := CreateTestGachaEntry(t, db, shopID, 100, 50)
	entryID2 := CreateTestGachaEntry(t, db, shopID, 100, 100)

	// Initially empty
	ids, err := repo.GetBoxEntryIDs(shopID, charID)
	if err != nil {
		t.Fatalf("GetBoxEntryIDs failed: %v", err)
	}
	if len(ids) != 0 {
		t.Errorf("Expected empty box, got: %d entries", len(ids))
	}

	// Insert drawn entries
	if err := repo.InsertBoxEntry(shopID, entryID1, charID); err != nil {
		t.Fatalf("InsertBoxEntry failed: %v", err)
	}
	if err := repo.InsertBoxEntry(shopID, entryID2, charID); err != nil {
		t.Fatalf("InsertBoxEntry failed: %v", err)
	}

	ids, err = repo.GetBoxEntryIDs(shopID, charID)
	if err != nil {
		t.Fatalf("GetBoxEntryIDs failed: %v", err)
	}
	if len(ids) != 2 {
		t.Errorf("Expected 2 box entries, got: %d", len(ids))
	}

	// Delete all box entries (reset)
	if err := repo.DeleteBoxEntries(shopID, charID); err != nil {
		t.Fatalf("DeleteBoxEntries failed: %v", err)
	}

	ids, err = repo.GetBoxEntryIDs(shopID, charID)
	if err != nil {
		t.Fatalf("GetBoxEntryIDs after delete failed: %v", err)
	}
	if len(ids) != 0 {
		t.Errorf("Expected empty box after delete, got: %d", len(ids))
	}
}

func TestRepoGachaBoxIsolation(t *testing.T) {
	repo, db, charID := setupGachaRepo(t)

	shopID := CreateTestGachaShop(t, db, "Box Iso", 1)
	entryID := CreateTestGachaEntry(t, db, shopID, 100, 50)

	// Create another character
	userID2 := CreateTestUser(t, db, "gacha_other_user")
	charID2 := CreateTestCharacter(t, db, userID2, "GachaChar2")

	// Char1 draws
	if err := repo.InsertBoxEntry(shopID, entryID, charID); err != nil {
		t.Fatalf("InsertBoxEntry failed: %v", err)
	}

	// Char2 should have empty box
	ids, err := repo.GetBoxEntryIDs(shopID, charID2)
	if err != nil {
		t.Fatalf("GetBoxEntryIDs for char2 failed: %v", err)
	}
	if len(ids) != 0 {
		t.Errorf("Expected empty box for char2, got: %d entries", len(ids))
	}
}
