package channelserver

import (
	"testing"

	"erupe-ce/network/mhfpacket"
)

func TestGetAchData_Level0(t *testing.T) {
	// Score 0 should give level 0 with progress toward first threshold
	ach := GetAchData(0, 0)
	if ach.Level != 0 {
		t.Errorf("Level = %d, want 0", ach.Level)
	}
	if ach.Progress != 0 {
		t.Errorf("Progress = %d, want 0", ach.Progress)
	}
	if ach.NextValue != 5 {
		t.Errorf("NextValue = %d, want 5", ach.NextValue)
	}
}

func TestGetAchData_Level1(t *testing.T) {
	// Score 5 (exactly at first threshold) should give level 1
	ach := GetAchData(0, 5)
	if ach.Level != 1 {
		t.Errorf("Level = %d, want 1", ach.Level)
	}
	if ach.Value != 5 {
		t.Errorf("Value = %d, want 5", ach.Value)
	}
}

func TestGetAchData_Partial(t *testing.T) {
	// Score 3 should give level 0 with progress 3
	ach := GetAchData(0, 3)
	if ach.Level != 0 {
		t.Errorf("Level = %d, want 0", ach.Level)
	}
	if ach.Progress != 3 {
		t.Errorf("Progress = %d, want 3", ach.Progress)
	}
	if ach.Required != 5 {
		t.Errorf("Required = %d, want 5", ach.Required)
	}
}

func TestGetAchData_MaxLevel(t *testing.T) {
	// Score 999 should give max level for curve 0
	ach := GetAchData(0, 999)
	if ach.Level != 8 {
		t.Errorf("Level = %d, want 8", ach.Level)
	}
	if ach.Trophy != 0x7F {
		t.Errorf("Trophy = %x, want 0x7F (gold)", ach.Trophy)
	}
}

func TestGetAchData_BronzeTrophy(t *testing.T) {
	// Level 7 should have bronze trophy (0x40)
	// Curve 0: 5, 15, 30, 50, 100, 150, 200, 300
	// Cumulative: 5, 20, 50, 100, 200, 350, 550, 850
	// To reach level 7, need 550+ points (sum of first 7 thresholds)
	ach := GetAchData(0, 550)
	if ach.Level != 7 {
		t.Errorf("Level = %d, want 7", ach.Level)
	}
	if ach.Trophy != 0x60 {
		t.Errorf("Trophy = %x, want 0x60 (silver)", ach.Trophy)
	}
}

func TestGetAchData_SilverTrophy(t *testing.T) {
	// Level 8 (max) should have gold trophy (0x7F)
	// Need 850+ (sum of all 8 thresholds) for max level
	ach := GetAchData(0, 850)
	if ach.Level != 8 {
		t.Errorf("Level = %d, want 8", ach.Level)
	}
	if ach.Trophy != 0x7F {
		t.Errorf("Trophy = %x, want 0x7F (gold)", ach.Trophy)
	}
}

func TestGetAchData_DifferentCurves(t *testing.T) {
	tests := []struct {
		name     string
		id       uint8
		score    int32
		wantLvl  uint8
		wantProg uint32
	}{
		{"Curve1_ID7_Level0", 7, 0, 0, 0},
		{"Curve1_ID7_Level1", 7, 1, 1, 0},
		{"Curve2_ID8_Level0", 8, 0, 0, 0},
		{"Curve2_ID8_Level1", 8, 1, 1, 0},
		{"Curve3_ID16_Level0", 16, 0, 0, 0},
		{"Curve3_ID16_Partial", 16, 5, 0, 5},
		{"Curve3_ID16_Level1", 16, 10, 1, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ach := GetAchData(tt.id, tt.score)
			if ach.Level != tt.wantLvl {
				t.Errorf("Level = %d, want %d", ach.Level, tt.wantLvl)
			}
			if ach.Progress != tt.wantProg {
				t.Errorf("Progress = %d, want %d", ach.Progress, tt.wantProg)
			}
		})
	}
}

func TestGetAchData_AllCurveMappings(t *testing.T) {
	// Verify all achievement IDs have valid curve mappings
	for id := uint8(0); id <= 32; id++ {
		curve, ok := achievementCurveMap[id]
		if !ok {
			t.Errorf("Achievement ID %d has no curve mapping", id)
			continue
		}
		if len(curve) != 8 {
			t.Errorf("Achievement ID %d curve has %d elements, want 8", id, len(curve))
		}
	}
}

func TestGetAchData_ValueAccumulation(t *testing.T) {
	// Test that Value correctly accumulates based on level
	// Level values: 1=5, 2-4=10, 5-7=15, 8=20
	// At max level 8: 5 + 10*3 + 15*3 + 20 = 5 + 30 + 45 + 20 = 100
	ach := GetAchData(0, 1000) // Score well above max
	expectedValue := uint32(5 + 10 + 10 + 10 + 15 + 15 + 15 + 20)
	if ach.Value != expectedValue {
		t.Errorf("Value = %d, want %d", ach.Value, expectedValue)
	}
}

func TestGetAchData_NextValueByLevel(t *testing.T) {
	tests := []struct {
		level       uint8
		wantNext    uint16
		approxScore int32
	}{
		{0, 5, 0},
		{1, 10, 5},
		{2, 10, 15},
		{3, 10, 30},
		{4, 15, 50},
		{5, 15, 100},
	}

	for _, tt := range tests {
		t.Run("Level"+string(rune('0'+tt.level)), func(t *testing.T) {
			ach := GetAchData(0, tt.approxScore)
			if ach.Level != tt.level {
				t.Skipf("Skipping: got level %d, expected %d", ach.Level, tt.level)
			}
			if ach.NextValue != tt.wantNext {
				t.Errorf("NextValue at level %d = %d, want %d", ach.Level, ach.NextValue, tt.wantNext)
			}
		})
	}
}

func TestAchievementCurves(t *testing.T) {
	// Verify curve values are strictly increasing
	for i, curve := range achievementCurves {
		for j := 1; j < len(curve); j++ {
			if curve[j] <= curve[j-1] {
				t.Errorf("Curve %d: value[%d]=%d should be > value[%d]=%d",
					i, j, curve[j], j-1, curve[j-1])
			}
		}
	}
}

func TestAchievementCurveMap_Coverage(t *testing.T) {
	// Ensure all mapped curves exist
	for id, curve := range achievementCurveMap {
		found := false
		for _, c := range achievementCurves {
			if &c[0] == &curve[0] {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("Achievement ID %d maps to unknown curve", id)
		}
	}
}

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

	pkt := &mhfpacket.MsgMhfSetCaAchievementHist{
		AckHandle: 12345,
	}

	handleMsgMhfSetCaAchievementHist(session, pkt)

	// Verify response packet was queued
	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Error("Response packet should have data")
		}
	default:
		t.Error("No response packet queued")
	}
}

// Test empty achievement handlers don't panic
func TestEmptyAchievementHandlers(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

	tests := []struct {
		name    string
		handler func(s *Session, p mhfpacket.MHFPacket)
	}{
		{"handleMsgMhfResetAchievement", handleMsgMhfResetAchievement},
		{"handleMsgMhfPaymentAchievement", handleMsgMhfPaymentAchievement},
		{"handleMsgMhfDisplayedAchievement", handleMsgMhfDisplayedAchievement},
		{"handleMsgMhfGetCaAchievementHist", handleMsgMhfGetCaAchievementHist},
		{"handleMsgMhfSetCaAchievement", handleMsgMhfSetCaAchievement},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer func() {
				if r := recover(); r != nil {
					t.Errorf("%s panicked: %v", tt.name, r)
				}
			}()
			tt.handler(session, nil)
		})
	}
}

// --- NEW TESTS ---

// TestGetAchData_Level6BronzeTrophy tests that level 6 (in-progress toward level 7)
// awards the bronze trophy (0x40).
// Curve 0: {5, 15, 30, 50, 100, 150, 200, 300}
// Cumulative at each level: L1=5, L2=20, L3=50, L4=100, L5=200, L6=350, L7=550, L8=850
// At cumulative 350, we reach level 6. Score 400 means level 6 with progress 50 toward next.
func TestGetAchData_Level6BronzeTrophy(t *testing.T) {
	// Score to reach level 6 and be partway to level 7:
	// cumulative to level 6 = 5+15+30+50+100+150 = 350
	// score 400 = level 6 with 50 remaining progress
	ach := GetAchData(0, 400)
	if ach.Level != 6 {
		t.Errorf("Level = %d, want 6", ach.Level)
	}
	if ach.Trophy != 0x40 {
		t.Errorf("Trophy = 0x%02x, want 0x40 (bronze)", ach.Trophy)
	}
	if ach.NextValue != 15 {
		t.Errorf("NextValue = %d, want 15", ach.NextValue)
	}
	if ach.Progress != 50 {
		t.Errorf("Progress = %d, want 50", ach.Progress)
	}
	if ach.Required != 200 {
		t.Errorf("Required = %d, want 200 (curve[6])", ach.Required)
	}
}

// TestGetAchData_Level7SilverTrophy tests that level 7 (in-progress toward level 8)
// awards the silver trophy (0x60).
// cumulative to level 7 = 5+15+30+50+100+150+200 = 550
// score 600 = level 7 with 50 remaining progress
func TestGetAchData_Level7SilverTrophy(t *testing.T) {
	ach := GetAchData(0, 600)
	if ach.Level != 7 {
		t.Errorf("Level = %d, want 7", ach.Level)
	}
	if ach.Trophy != 0x60 {
		t.Errorf("Trophy = 0x%02x, want 0x60 (silver)", ach.Trophy)
	}
	if ach.NextValue != 20 {
		t.Errorf("NextValue = %d, want 20", ach.NextValue)
	}
	if ach.Progress != 50 {
		t.Errorf("Progress = %d, want 50", ach.Progress)
	}
	if ach.Required != 300 {
		t.Errorf("Required = %d, want 300 (curve[7])", ach.Required)
	}
}

// TestGetAchData_MaxedOut_AllCurves tests that reaching max level on each curve
// produces the correct gold trophy and the last threshold as Required/Progress.
func TestGetAchData_MaxedOut_AllCurves(t *testing.T) {
	tests := []struct {
		name       string
		id         uint8
		score      int32
		lastThresh int32
	}{
		// Curve 0: {5,15,30,50,100,150,200,300} sum=850, last=300
		{"Curve0_ID0", 0, 5000, 300},
		// Curve 1: {1,5,10,15,30,50,75,100} sum=286, last=100
		{"Curve1_ID7", 7, 5000, 100},
		// Curve 2: {1,2,3,4,5,6,7,8} sum=36, last=8
		{"Curve2_ID8", 8, 5000, 8},
		// Curve 3: {10,50,100,200,350,500,750,999} sum=2959, last=999
		{"Curve3_ID16", 16, 50000, 999},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ach := GetAchData(tt.id, tt.score)
			if ach.Level != 8 {
				t.Errorf("Level = %d, want 8 (max)", ach.Level)
			}
			if ach.Trophy != 0x7F {
				t.Errorf("Trophy = 0x%02x, want 0x7F (gold)", ach.Trophy)
			}
			if ach.Required != uint32(tt.lastThresh) {
				t.Errorf("Required = %d, want %d", ach.Required, tt.lastThresh)
			}
			if ach.Progress != ach.Required {
				t.Errorf("Progress = %d, want %d (should equal Required at max)", ach.Progress, ach.Required)
			}
		})
	}
}

// TestGetAchData_ExactlyAtEachThreshold tests the exact cumulative score at each
// threshold boundary for curve 0.
func TestGetAchData_ExactlyAtEachThreshold(t *testing.T) {
	// Curve 0: {5, 15, 30, 50, 100, 150, 200, 300}
	// Cumulative thresholds (exact score to reach each level):
	// L1: 5, L2: 20, L3: 50, L4: 100, L5: 200, L6: 350, L7: 550, L8: 850
	cumulativeScores := []int32{5, 20, 50, 100, 200, 350, 550, 850}
	expectedLevels := []uint8{1, 2, 3, 4, 5, 6, 7, 8}
	expectedValues := []uint32{5, 15, 25, 35, 50, 65, 80, 100}

	for i, score := range cumulativeScores {
		t.Run("ExactThreshold_L"+string(rune('1'+i)), func(t *testing.T) {
			ach := GetAchData(0, score)
			if ach.Level != expectedLevels[i] {
				t.Errorf("score=%d: Level = %d, want %d", score, ach.Level, expectedLevels[i])
			}
			if ach.Value != expectedValues[i] {
				t.Errorf("score=%d: Value = %d, want %d", score, ach.Value, expectedValues[i])
			}
		})
	}
}

// TestGetAchData_OneBeforeEachThreshold tests scores that are one less than
// each cumulative threshold, verifying they stay at the previous level.
func TestGetAchData_OneBeforeEachThreshold(t *testing.T) {
	// Curve 0: cumulative thresholds: 5, 20, 50, 100, 200, 350, 550, 850
	cumulativeScores := []int32{4, 19, 49, 99, 199, 349, 549, 849}
	expectedLevels := []uint8{0, 1, 2, 3, 4, 5, 6, 7}

	for i, score := range cumulativeScores {
		t.Run("OneBeforeThreshold_L"+string(rune('0'+i)), func(t *testing.T) {
			ach := GetAchData(0, score)
			if ach.Level != expectedLevels[i] {
				t.Errorf("score=%d: Level = %d, want %d", score, ach.Level, expectedLevels[i])
			}
		})
	}
}

// TestGetAchData_Curve2_FestaWins exercises the "Festa wins" curve which has
// small thresholds: {1, 2, 3, 4, 5, 6, 7, 8}
func TestGetAchData_Curve2_FestaWins(t *testing.T) {
	// Curve 2: {1, 2, 3, 4, 5, 6, 7, 8}
	// Cumulative: 1, 3, 6, 10, 15, 21, 28, 36
	tests := []struct {
		score    int32
		wantLvl  uint8
		wantProg uint32
		wantReq  uint32
	}{
		{0, 0, 0, 1},
		{1, 1, 0, 2},   // Exactly at first threshold
		{2, 1, 1, 2},   // One into second threshold
		{3, 2, 0, 3},   // Exactly at second cumulative
		{36, 8, 8, 8},  // Max level (sum of all thresholds)
		{100, 8, 8, 8}, // Well above max
	}

	for _, tt := range tests {
		t.Run("", func(t *testing.T) {
			ach := GetAchData(8, tt.score) // ID 8 maps to curve 2
			if ach.Level != tt.wantLvl {
				t.Errorf("score=%d: Level = %d, want %d", tt.score, ach.Level, tt.wantLvl)
			}
			if ach.Progress != tt.wantProg {
				t.Errorf("score=%d: Progress = %d, want %d", tt.score, ach.Progress, tt.wantProg)
			}
			if ach.Required != tt.wantReq {
				t.Errorf("score=%d: Required = %d, want %d", tt.score, ach.Required, tt.wantReq)
			}
		})
	}
}

// TestGetAchData_AllIDs_ZeroScore verifies that calling GetAchData with score=0
// for every valid ID returns level 0 without panicking.
func TestGetAchData_AllIDs_ZeroScore(t *testing.T) {
	for id := uint8(0); id <= 32; id++ {
		ach := GetAchData(id, 0)
		if ach.Level != 0 {
			t.Errorf("ID %d, score 0: Level = %d, want 0", id, ach.Level)
		}
		if ach.Value != 0 {
			t.Errorf("ID %d, score 0: Value = %d, want 0", id, ach.Value)
		}
		if ach.Trophy != 0 {
			t.Errorf("ID %d, score 0: Trophy = 0x%02x, want 0x00", id, ach.Trophy)
		}
	}
}

// TestGetAchData_AllIDs_MaxScore verifies that calling GetAchData with a very
// high score for every valid ID returns level 8 with gold trophy.
func TestGetAchData_AllIDs_MaxScore(t *testing.T) {
	for id := uint8(0); id <= 32; id++ {
		ach := GetAchData(id, 99999)
		if ach.Level != 8 {
			t.Errorf("ID %d: Level = %d, want 8", id, ach.Level)
		}
		if ach.Trophy != 0x7F {
			t.Errorf("ID %d: Trophy = 0x%02x, want 0x7F", id, ach.Trophy)
		}
		// At max, Progress should equal Required
		if ach.Progress != ach.Required {
			t.Errorf("ID %d: Progress (%d) != Required (%d) at max", id, ach.Progress, ach.Required)
		}
	}
}

// TestGetAchData_UpdatedAlwaysFalse confirms Updated is always false since
// GetAchData never sets it.
func TestGetAchData_UpdatedAlwaysFalse(t *testing.T) {
	scores := []int32{0, 1, 5, 50, 500, 5000}
	for _, score := range scores {
		ach := GetAchData(0, score)
		if ach.Updated {
			t.Errorf("score=%d: Updated should always be false, got true", score)
		}
	}
}

// --- Mock-based handler tests ---

func TestHandleMsgMhfGetAchievement_Success(t *testing.T) {
	server := createMockServer()
	mock := &mockAchievementRepo{
		scores: [33]int32{5, 0, 20, 0, 0, 0, 0, 1}, // A few non-zero scores
	}
	server.achievementRepo = mock
	ensureAchievementService(server)
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetAchievement{
		AckHandle: 100,
		CharID:    1,
	}

	handleMsgMhfGetAchievement(session, pkt)

	if !mock.ensureCalled {
		t.Error("EnsureExists should have been called")
	}

	select {
	case p := <-session.sendPackets:
		// Response should contain: 16 bytes header + 3 bytes unk + 1 byte count + 33 entries
		// Each entry: 1+1+2+4+1+1+2+4 = 16 bytes, so 33*16 = 528 + 20 header = 548
		if len(p.data) < 100 {
			t.Errorf("Response too short: %d bytes", len(p.data))
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetAchievement_DBError(t *testing.T) {
	server := createMockServer()
	mock := &mockAchievementRepo{
		getScoresErr: errNotFound,
	}
	server.achievementRepo = mock
	ensureAchievementService(server)
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetAchievement{
		AckHandle: 100,
		CharID:    1,
	}

	handleMsgMhfGetAchievement(session, pkt)

	select {
	case p := <-session.sendPackets:
		// On error, should return 20 zero bytes
		if len(p.data) == 0 {
			t.Error("Response should have fallback data")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfGetAchievement_AllZeroScores(t *testing.T) {
	server := createMockServer()
	mock := &mockAchievementRepo{} // All scores default to 0
	server.achievementRepo = mock
	ensureAchievementService(server)
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfGetAchievement{
		AckHandle: 200,
		CharID:    1,
	}

	handleMsgMhfGetAchievement(session, pkt)

	select {
	case p := <-session.sendPackets:
		if len(p.data) < 100 {
			t.Errorf("Response too short: %d bytes", len(p.data))
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfAddAchievement_Valid(t *testing.T) {
	server := createMockServer()
	mock := &mockAchievementRepo{}
	server.achievementRepo = mock
	ensureAchievementService(server)
	session := createMockSession(42, server)

	pkt := &mhfpacket.MsgMhfAddAchievement{
		AchievementID: 5,
	}

	handleMsgMhfAddAchievement(session, pkt)

	if !mock.ensureCalled {
		t.Error("EnsureExists should have been called")
	}
	if mock.incrementedID != 5 {
		t.Errorf("IncrementScore called with ID %d, want 5", mock.incrementedID)
	}
}

func TestHandleMsgMhfAddAchievement_OutOfRange(t *testing.T) {
	server := createMockServer()
	mock := &mockAchievementRepo{}
	server.achievementRepo = mock
	ensureAchievementService(server)
	session := createMockSession(42, server)

	pkt := &mhfpacket.MsgMhfAddAchievement{
		AchievementID: 33, // > 32, should be rejected
	}

	handleMsgMhfAddAchievement(session, pkt)

	if mock.ensureCalled {
		t.Error("EnsureExists should NOT be called for out-of-range ID")
	}
}

func TestHandleMsgMhfAddAchievement_BoundaryID32(t *testing.T) {
	server := createMockServer()
	mock := &mockAchievementRepo{}
	server.achievementRepo = mock
	ensureAchievementService(server)
	session := createMockSession(42, server)

	pkt := &mhfpacket.MsgMhfAddAchievement{
		AchievementID: 32, // Exactly at boundary, should be accepted
	}

	handleMsgMhfAddAchievement(session, pkt)

	if !mock.ensureCalled {
		t.Error("EnsureExists should be called for ID 32")
	}
	if mock.incrementedID != 32 {
		t.Errorf("IncrementScore called with ID %d, want 32", mock.incrementedID)
	}
}

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

	pkt := &mhfpacket.MsgMhfSetCaAchievementHist{
		AckHandle: 44444,
	}

	handleMsgMhfSetCaAchievementHist(session, pkt)

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

// Tests consolidated from handlers_coverage3_test.go

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

	tests := []struct {
		name string
		fn   func()
	}{
		{"handleMsgMhfDisplayedAchievement", func() {
			handleMsgMhfDisplayedAchievement(session, &mhfpacket.MsgMhfDisplayedAchievement{})
		}},
		{"handleMsgMhfGetCaAchievementHist", func() { handleMsgMhfGetCaAchievementHist(session, nil) }},
		{"handleMsgMhfSetCaAchievement", func() { handleMsgMhfSetCaAchievement(session, nil) }},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer func() {
				if r := recover(); r != nil {
					t.Errorf("%s panicked: %v", tt.name, r)
				}
			}()
			tt.fn()
		})
	}
}

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

	defer func() {
		if r := recover(); r != nil {
			t.Errorf("handleMsgMhfPaymentAchievement panicked: %v", r)
		}
	}()
	handleMsgMhfPaymentAchievement(session, nil)
}
