package channelserver

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

	"erupe-ce/common/byteframe"
	"erupe-ce/network/mhfpacket"
)

func TestHandleMsgMhfGetGachaPlayHistory_StubResponse(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetGachaPlayHistory{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetGachaPlayHistory(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetGachaPoint(t *testing.T) {
	server := createMockServer()
	userRepo := &mockUserRepoGacha{
		gachaFP: 100,
		gachaGP: 200,
		gachaGT: 300,
	}
	server.userRepo = userRepo

	session := createMockSession(1, server)
	session.userID = 1

	pkt := &mhfpacket.MsgMhfGetGachaPoint{AckHandle: 100}
	handleMsgMhfGetGachaPoint(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfUseGachaPoint_TrialCoins(t *testing.T) {
	server := createMockServer()
	userRepo := &mockUserRepoGacha{}
	server.userRepo = userRepo

	session := createMockSession(1, server)
	session.userID = 1

	pkt := &mhfpacket.MsgMhfUseGachaPoint{
		AckHandle:    100,
		TrialCoins:   10,
		PremiumCoins: 0,
	}
	handleMsgMhfUseGachaPoint(session, pkt)

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfUseGachaPoint_PremiumCoins(t *testing.T) {
	server := createMockServer()
	userRepo := &mockUserRepoGacha{}
	server.userRepo = userRepo

	session := createMockSession(1, server)
	session.userID = 1

	pkt := &mhfpacket.MsgMhfUseGachaPoint{
		AckHandle:    100,
		TrialCoins:   0,
		PremiumCoins: 5,
	}
	handleMsgMhfUseGachaPoint(session, pkt)

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfReceiveGachaItem_Normal(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	// Store 2 items: count byte + 2 * 5 bytes each
	data := []byte{2, 1, 0, 100, 0, 5, 2, 0, 200, 0, 10}
	charRepo.columns["gacha_items"] = data
	server.charRepo = charRepo

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfReceiveGachaItem{AckHandle: 100, Freeze: false}
	handleMsgMhfReceiveGachaItem(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}

	// After non-freeze receive, gacha_items should be cleared
	if charRepo.columns["gacha_items"] != nil {
		t.Error("Expected gacha_items to be cleared after receive")
	}
}

func TestHandleMsgMhfReceiveGachaItem_Overflow(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	// Build data with >36 items (overflow scenario): count=37, 37*5=185 bytes + 1 count byte = 186
	data := make([]byte, 186)
	data[0] = 37
	for i := 1; i < 186; i++ {
		data[i] = byte(i % 256)
	}
	charRepo.columns["gacha_items"] = data
	server.charRepo = charRepo

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfReceiveGachaItem{AckHandle: 100, Freeze: false}
	handleMsgMhfReceiveGachaItem(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}

	// After overflow, remaining items should be saved
	saved := charRepo.columns["gacha_items"]
	if saved == nil {
		t.Error("Expected overflow items to be saved")
	}
}

func TestHandleMsgMhfReceiveGachaItem_Freeze(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	data := []byte{1, 1, 0, 100, 0, 5}
	charRepo.columns["gacha_items"] = data
	server.charRepo = charRepo

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfReceiveGachaItem{AckHandle: 100, Freeze: true}
	handleMsgMhfReceiveGachaItem(session, pkt)

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}

	// Freeze should NOT clear the items
	if charRepo.columns["gacha_items"] == nil {
		t.Error("Expected gacha_items to be preserved on freeze")
	}
}

func TestHandleMsgMhfPlayNormalGacha_TransactError(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		txErr:      errors.New("transact failed"),
		rewardPool: []GachaEntry{{ID: 10, Weight: 100}},
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayNormalGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayNormalGacha(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfPlayNormalGacha_RewardPoolError(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		txRolls:       1,
		rewardPoolErr: errors.New("pool error"),
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayNormalGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayNormalGacha(session, pkt)

	select {
	case <-session.sendPackets:
		// success - returns empty result
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfPlayNormalGacha_Success(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	gachaRepo := &mockGachaRepo{
		txRolls: 1,
		rewardPool: []GachaEntry{
			{ID: 10, Weight: 100, Rarity: 3},
		},
		entryItems: map[uint32][]GachaItem{
			10: {{ItemType: 1, ItemID: 500, Quantity: 1}},
		},
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayNormalGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayNormalGacha(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}

	// Verify gacha items were stored
	if charRepo.columns["gacha_items"] == nil {
		t.Error("Expected gacha items to be saved")
	}
}

func TestHandleMsgMhfPlayStepupGacha_TransactError(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		txErr:      errors.New("transact failed"),
		rewardPool: []GachaEntry{{ID: 10, Weight: 100}},
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayStepupGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayStepupGacha(session, pkt)

	select {
	case <-session.sendPackets:
		// success - returns empty result
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfPlayStepupGacha_Success(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	gachaRepo := &mockGachaRepo{
		txRolls: 1,
		rewardPool: []GachaEntry{
			{ID: 10, Weight: 100, Rarity: 2},
		},
		entryItems: map[uint32][]GachaItem{
			10: {{ItemType: 1, ItemID: 600, Quantity: 2}},
		},
		guaranteedItems: []GachaItem{
			{ItemType: 1, ItemID: 700, Quantity: 1},
		},
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayStepupGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayStepupGacha(session, pkt)

	if !gachaRepo.deletedStepup {
		t.Error("Expected stepup to be deleted")
	}
	if gachaRepo.insertedStep != 1 {
		t.Errorf("Expected insertedStep=1, got %d", gachaRepo.insertedStep)
	}

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetStepupStatus_FreshStep(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		stepupStep:   2,
		stepupTime:   time.Now(), // recent, not stale
		hasEntryType: true,
	}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetStepupStatus{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetStepupStatus(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetStepupStatus_StaleStep(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		stepupStep: 3,
		stepupTime: time.Now().Add(-48 * time.Hour), // stale
	}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetStepupStatus{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetStepupStatus(session, pkt)

	if !gachaRepo.deletedStepup {
		t.Error("Expected stale stepup to be deleted")
	}

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetStepupStatus_NoRows(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		stepupErr: sql.ErrNoRows,
	}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetStepupStatus{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetStepupStatus(session, pkt)

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetStepupStatus_NoEntryType(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		stepupStep:   2,
		stepupTime:   time.Now(),
		hasEntryType: false, // no matching entry type -> reset
	}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetStepupStatus{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetStepupStatus(session, pkt)

	if !gachaRepo.deletedStepup {
		t.Error("Expected stepup to be reset when no entry type")
	}

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetBoxGachaInfo_Error(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		boxEntryIDsErr: errors.New("db error"),
	}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetBoxGachaInfo{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetBoxGachaInfo(session, pkt)

	select {
	case <-session.sendPackets:
		// returns empty
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetBoxGachaInfo_Success(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		boxEntryIDs: []uint32{10, 20, 30},
	}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetBoxGachaInfo{AckHandle: 100, GachaID: 1}
	handleMsgMhfGetBoxGachaInfo(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Fatal("Empty response")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfPlayBoxGacha_TransactError(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{
		txErr:      errors.New("transact failed"),
		rewardPool: []GachaEntry{{ID: 10, Weight: 100}},
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayBoxGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayBoxGacha(session, pkt)

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfPlayBoxGacha_Success(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	gachaRepo := &mockGachaRepo{
		txRolls: 1,
		rewardPool: []GachaEntry{
			{ID: 10, Weight: 100, Rarity: 1},
		},
		entryItems: map[uint32][]GachaItem{
			10: {{ItemType: 1, ItemID: 800, Quantity: 1}},
		},
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayBoxGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayBoxGacha(session, pkt)

	if len(gachaRepo.insertedBoxIDs) == 0 {
		t.Error("Expected box entry to be inserted")
	}

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfResetBoxGachaInfo(t *testing.T) {
	server := createMockServer()
	gachaRepo := &mockGachaRepo{}
	server.gachaRepo = gachaRepo
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfResetBoxGachaInfo{AckHandle: 100, GachaID: 1}
	handleMsgMhfResetBoxGachaInfo(session, pkt)

	if !gachaRepo.deletedBox {
		t.Error("Expected box entries to be deleted")
	}

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfPlayFreeGacha_StubACK(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayFreeGacha{AckHandle: 100, GachaID: 1}
	handleMsgMhfPlayFreeGacha(session, pkt)

	select {
	case <-session.sendPackets:
		// success
	default:
		t.Error("No response packet queued")
	}
}

func TestGetRandomEntries_NonBox(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 50},
		{ID: 2, Weight: 50},
	}
	result, err := getRandomEntries(entries, 3, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(result) != 3 {
		t.Errorf("Expected 3 entries, got %d", len(result))
	}
}

func TestGetRandomEntries_Box(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 50},
		{ID: 2, Weight: 50},
		{ID: 3, Weight: 50},
	}
	result, err := getRandomEntries(entries, 2, true)
	if err != nil {
		t.Fatal(err)
	}
	if len(result) != 2 {
		t.Errorf("Expected 2 entries, got %d", len(result))
	}
	// Box mode removes entries without replacement — all IDs should be unique
	if result[0].ID == result[1].ID {
		t.Error("Box mode should return unique entries")
	}
}

func TestGetRandomEntries_BoxMoreRollsThanEntries(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 50},
	}
	result, err := getRandomEntries(entries, 5, true)
	if err != nil {
		t.Fatal(err)
	}
	// Should clamp to available entries instead of panicking
	if len(result) != 1 {
		t.Errorf("Expected 1 entry (clamped), got %d", len(result))
	}
}

func TestGetRandomEntries_EmptyEntries(t *testing.T) {
	_, err := getRandomEntries(nil, 1, false)
	if err == nil {
		t.Fatal("Expected error for empty entries, got nil")
	}
}

func TestGetRandomEntries_ZeroWeight(t *testing.T) {
	entries := []GachaEntry{
		{ID: 1, Weight: 0},
		{ID: 2, Weight: 0},
	}
	_, err := getRandomEntries(entries, 1, false)
	if err == nil {
		t.Fatal("Expected error for zero total weight, got nil")
	}
}

func TestHandleMsgMhfPlayStepupGacha_RewardPoolError(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	gachaRepo := &mockGachaRepo{
		txRolls:       1,
		rewardPoolErr: errors.New("pool error"),
	}
	server.gachaRepo = gachaRepo
	server.userRepo = &mockUserRepoGacha{}
	ensureGachaService(server)

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfPlayStepupGacha{AckHandle: 100, GachaID: 1, RollType: 0}
	handleMsgMhfPlayStepupGacha(session, pkt)

	select {
	case p := <-session.sendPackets:
		// Verify minimal response (1 byte)
		_ = p
	default:
		t.Error("No response packet queued")
	}
}

// Verify the response payload of GetGachaPoint contains the expected values
func TestHandleMsgMhfGetGachaPoint_ResponsePayload(t *testing.T) {
	server := createMockServer()
	userRepo := &mockUserRepoGacha{
		gachaFP: 111,
		gachaGP: 222,
		gachaGT: 333,
	}
	server.userRepo = userRepo

	session := createMockSession(1, server)
	session.userID = 1

	pkt := &mhfpacket.MsgMhfGetGachaPoint{AckHandle: 100}
	handleMsgMhfGetGachaPoint(session, pkt)

	select {
	case p := <-session.sendPackets:
		// The ack wraps the payload. The handler writes gp, gt, fp (12 bytes).
		// Just verify we got a reasonable-sized response.
		if len(p.data) < 12 {
			t.Errorf("Expected at least 12 bytes of gacha point data in response, got %d", len(p.data))
		}
	default:
		t.Error("No response packet queued")
	}
}

// Verify the response when no gacha items exist (default column)
func TestHandleMsgMhfReceiveGachaItem_Empty(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	// No gacha_items set — will return default {0x00}
	server.charRepo = charRepo

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfReceiveGachaItem{AckHandle: 100, Freeze: false}
	handleMsgMhfReceiveGachaItem(session, pkt)

	select {
	case p := <-session.sendPackets:
		// The response should contain the default byte
		bf := byteframe.NewByteFrameFromBytes(p.data)
		_ = bf
	default:
		t.Error("No response packet queued")
	}
}
