package channelserver

import (
	"bytes"
	"database/sql"
	"encoding/binary"
	"errors"
	"testing"

	cfg "erupe-ce/config"
	"erupe-ce/network/mhfpacket"
	"erupe-ce/server/channelserver/compression/nullcomp"
)

// TestGetPointers tests the pointer map generation for different game versions
func TestGetPointers(t *testing.T) {
	tests := []struct {
		name       string
		clientMode cfg.Mode
		wantGender int
		wantHR     int
	}{
		{
			name:       "ZZ_version",
			clientMode: cfg.ZZ,
			wantGender: 81,
			wantHR:     130550,
		},
		{
			name:       "Z2_version",
			clientMode: cfg.Z2,
			wantGender: 81,
			wantHR:     94550,
		},
		{
			name:       "G10_version",
			clientMode: cfg.G10,
			wantGender: 81,
			wantHR:     94550,
		},
		{
			name:       "F5_version",
			clientMode: cfg.F5,
			wantGender: 81,
			wantHR:     62550,
		},
		{
			name:       "S6_version",
			clientMode: cfg.S6,
			wantGender: 81,
			wantHR:     14550,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pointers := getPointers(tt.clientMode)

			if pointers[pGender] != tt.wantGender {
				t.Errorf("pGender = %d, want %d", pointers[pGender], tt.wantGender)
			}

			if pointers[pHR] != tt.wantHR {
				t.Errorf("pHR = %d, want %d", pointers[pHR], tt.wantHR)
			}

			// Verify all required pointers exist
			requiredPointers := []SavePointer{pGender, pRP, pHouseTier, pHouseData, pBookshelfData,
				pGalleryData, pToreData, pGardenData, pPlaytime, pWeaponType, pWeaponID, pHR, lBookshelfData}

			for _, ptr := range requiredPointers {
				if _, exists := pointers[ptr]; !exists {
					t.Errorf("pointer %v not found in map", ptr)
				}
			}
		})
	}
}

// TestCharacterSaveData_Compress tests savedata compression
func TestCharacterSaveData_Compress(t *testing.T) {
	tests := []struct {
		name    string
		data    []byte
		wantErr bool
	}{
		{
			name:    "valid_small_data",
			data:    []byte{0x01, 0x02, 0x03, 0x04},
			wantErr: false,
		},
		{
			name:    "valid_large_data",
			data:    bytes.Repeat([]byte{0xAA}, 10000),
			wantErr: false,
		},
		{
			name:    "empty_data",
			data:    []byte{},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			save := &CharacterSaveData{
				decompSave: tt.data,
			}

			err := save.Compress()
			if (err != nil) != tt.wantErr {
				t.Errorf("Compress() error = %v, wantErr %v", err, tt.wantErr)
			}

			if !tt.wantErr && len(save.compSave) == 0 {
				t.Error("compressed save is empty")
			}
		})
	}
}

// TestCharacterSaveData_Decompress tests savedata decompression
func TestCharacterSaveData_Decompress(t *testing.T) {
	tests := []struct {
		name    string
		setup   func() []byte
		wantErr bool
	}{
		{
			name: "valid_compressed_data",
			setup: func() []byte {
				data := []byte{0x01, 0x02, 0x03, 0x04}
				compressed, _ := nullcomp.Compress(data)
				return compressed
			},
			wantErr: false,
		},
		{
			name: "valid_large_compressed_data",
			setup: func() []byte {
				data := bytes.Repeat([]byte{0xBB}, 5000)
				compressed, _ := nullcomp.Compress(data)
				return compressed
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			save := &CharacterSaveData{
				compSave: tt.setup(),
			}

			err := save.Decompress()
			if (err != nil) != tt.wantErr {
				t.Errorf("Decompress() error = %v, wantErr %v", err, tt.wantErr)
			}

			if !tt.wantErr && len(save.decompSave) == 0 {
				t.Error("decompressed save is empty")
			}
		})
	}
}

// TestCharacterSaveData_RoundTrip tests compression and decompression
func TestCharacterSaveData_RoundTrip(t *testing.T) {
	tests := []struct {
		name string
		data []byte
	}{
		{
			name: "small_data",
			data: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
		},
		{
			name: "repeating_pattern",
			data: bytes.Repeat([]byte{0xCC}, 1000),
		},
		{
			name: "mixed_data",
			data: []byte{0x00, 0xFF, 0x01, 0xFE, 0x02, 0xFD, 0x03, 0xFC},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			save := &CharacterSaveData{
				decompSave: tt.data,
			}

			// Compress
			if err := save.Compress(); err != nil {
				t.Fatalf("Compress() failed: %v", err)
			}

			// Clear decompressed data
			save.decompSave = nil

			// Decompress
			if err := save.Decompress(); err != nil {
				t.Fatalf("Decompress() failed: %v", err)
			}

			// Verify round trip
			if !bytes.Equal(save.decompSave, tt.data) {
				t.Errorf("round trip failed: got %v, want %v", save.decompSave, tt.data)
			}
		})
	}
}

// TestCharacterSaveData_updateStructWithSaveData tests parsing save data
func TestCharacterSaveData_updateStructWithSaveData(t *testing.T) {
	tests := []struct {
		name           string
		isNewCharacter bool
		setupSaveData  func() []byte
		wantName       string
		wantGender     bool
	}{
		{
			name:           "male_character",
			isNewCharacter: false,
			setupSaveData: func() []byte {
				data := make([]byte, 150000)
				copy(data[88:], []byte("TestChar\x00"))
				data[81] = 0 // Male
				return data
			},
			wantName:   "TestChar",
			wantGender: false,
		},
		{
			name:           "female_character",
			isNewCharacter: false,
			setupSaveData: func() []byte {
				data := make([]byte, 150000)
				copy(data[88:], []byte("FemaleChar\x00"))
				data[81] = 1 // Female
				return data
			},
			wantName:   "FemaleChar",
			wantGender: true,
		},
		{
			name:           "new_character_skips_parsing",
			isNewCharacter: true,
			setupSaveData: func() []byte {
				data := make([]byte, 150000)
				copy(data[88:], []byte("NewChar\x00"))
				return data
			},
			wantName:   "NewChar",
			wantGender: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			save := &CharacterSaveData{
				Mode:           cfg.Z2,
				Pointers:       getPointers(cfg.Z2),
				decompSave:     tt.setupSaveData(),
				IsNewCharacter: tt.isNewCharacter,
			}

			save.updateStructWithSaveData()

			if save.Name != tt.wantName {
				t.Errorf("Name = %q, want %q", save.Name, tt.wantName)
			}

			if save.Gender != tt.wantGender {
				t.Errorf("Gender = %v, want %v", save.Gender, tt.wantGender)
			}
		})
	}
}

// TestCharacterSaveData_updateSaveDataWithStruct tests writing struct to save data
func TestCharacterSaveData_updateSaveDataWithStruct(t *testing.T) {
	tests := []struct {
		name   string
		rp     uint16
		kqf    []byte
		wantRP uint16
	}{
		{
			name:   "update_rp_value",
			rp:     1234,
			kqf:    []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
			wantRP: 1234,
		},
		{
			name:   "zero_rp_value",
			rp:     0,
			kqf:    []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
			wantRP: 0,
		},
		{
			name:   "max_rp_value",
			rp:     65535,
			kqf:    []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			wantRP: 65535,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			save := &CharacterSaveData{
				Mode:       cfg.G10,
				Pointers:   getPointers(cfg.G10),
				decompSave: make([]byte, 150000),
				RP:         tt.rp,
				KQF:        tt.kqf,
			}

			save.updateSaveDataWithStruct()

			// Verify RP was written correctly
			rpOffset := save.Pointers[pRP]
			gotRP := binary.LittleEndian.Uint16(save.decompSave[rpOffset : rpOffset+2])
			if gotRP != tt.wantRP {
				t.Errorf("RP in save data = %d, want %d", gotRP, tt.wantRP)
			}

			// Verify KQF was written correctly
			kqfOffset := save.Pointers[pKQF]
			gotKQF := save.decompSave[kqfOffset : kqfOffset+8]
			if !bytes.Equal(gotKQF, tt.kqf) {
				t.Errorf("KQF in save data = %v, want %v", gotKQF, tt.kqf)
			}
		})
	}
}

// TestHandleMsgMhfSexChanger tests the sex changer handler
func TestHandleMsgMhfSexChanger(t *testing.T) {
	tests := []struct {
		name      string
		ackHandle uint32
	}{
		{
			name:      "basic_sex_change",
			ackHandle: 1234,
		},
		{
			name:      "different_ack_handle",
			ackHandle: 9999,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
			s := createTestSession(mock)

			pkt := &mhfpacket.MsgMhfSexChanger{
				AckHandle: tt.ackHandle,
			}

			handleMsgMhfSexChanger(s, pkt)

			// Verify ACK was sent
			if len(s.sendPackets) == 0 {
				t.Fatal("no ACK packet was sent")
			}

			// Drain the channel
			<-s.sendPackets
		})
	}
}

// TestGetCharacterSaveData_Integration tests retrieving character save data from database
func TestGetCharacterSaveData_Integration(t *testing.T) {
	db := SetupTestDB(t)
	defer TeardownTestDB(t, db)

	tests := []struct {
		name           string
		charName       string
		isNewCharacter bool
		wantError      bool
	}{
		{
			name:           "existing_character",
			charName:       "TestChar",
			isNewCharacter: false,
			wantError:      false,
		},
		{
			name:           "new_character",
			charName:       "NewChar",
			isNewCharacter: true,
			wantError:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create test user and character
			userID := CreateTestUser(t, db, "testuser_"+tt.name)
			charID := CreateTestCharacter(t, db, userID, tt.charName)

			// Update is_new_character flag
			_, err := db.Exec("UPDATE characters SET is_new_character = $1 WHERE id = $2", tt.isNewCharacter, charID)
			if err != nil {
				t.Fatalf("Failed to update character: %v", err)
			}

			// Create test session
			mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
			s := createTestSession(mock)
			s.charID = charID
			SetTestDB(s.server, db)
			s.server.erupeConfig.RealClientMode = cfg.Z2

			// Get character save data
			saveData, err := GetCharacterSaveData(s, charID)
			if (err != nil) != tt.wantError {
				t.Errorf("GetCharacterSaveData() error = %v, wantErr %v", err, tt.wantError)
				return
			}

			if !tt.wantError {
				if saveData == nil {
					t.Fatal("saveData is nil")
				}

				if saveData.CharID != charID {
					t.Errorf("CharID = %d, want %d", saveData.CharID, charID)
				}

				if saveData.Name != tt.charName {
					t.Errorf("Name = %q, want %q", saveData.Name, tt.charName)
				}

				if saveData.IsNewCharacter != tt.isNewCharacter {
					t.Errorf("IsNewCharacter = %v, want %v", saveData.IsNewCharacter, tt.isNewCharacter)
				}
			}
		})
	}
}

// TestGetCharacterSaveData_BackupRecovery tests that a character whose primary
// savedata has a hash mismatch is transparently recovered from the backup table.
func TestGetCharacterSaveData_BackupRecovery(t *testing.T) {
	db := SetupTestDB(t)
	defer TeardownTestDB(t, db)

	// Build valid compressed savedata (same layout as CreateTestCharacter).
	rawSave := make([]byte, 150000)
	copy(rawSave[88:], append([]byte("BackupChar"), 0x00))
	validCompressed, err := nullcomp.Compress(rawSave)
	if err != nil {
		t.Fatalf("compress valid savedata: %v", err)
	}

	// Build a compressed blob that will fail decompression (garbage bytes).
	invalidCompressed := []byte("this is not valid compressed data")

	corruptHash := make([]byte, 32) // all-zero hash is wrong for any real savedata
	corruptHash[0] = 0xFF

	repo := NewCharacterRepository(db)

	t.Run("recovers_from_most_recent_backup", func(t *testing.T) {
		userID := CreateTestUser(t, db, "recovery_user")
		charID := CreateTestCharacter(t, db, userID, "BackupChar")

		// Store a valid backup in slot 0.
		if err := repo.SaveBackup(charID, 0, validCompressed); err != nil {
			t.Fatalf("SaveBackup: %v", err)
		}

		// Set a wrong hash on the primary so the integrity check fails.
		if _, err := db.Exec("UPDATE characters SET savedata_hash = $1 WHERE id = $2", corruptHash, charID); err != nil {
			t.Fatalf("set corrupt hash: %v", err)
		}

		mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
		s := createTestSession(mock)
		s.charID = charID
		SetTestDB(s.server, db)
		s.server.erupeConfig.RealClientMode = cfg.Z2

		got, err := GetCharacterSaveData(s, charID)
		if err != nil {
			t.Fatalf("GetCharacterSaveData() unexpected error: %v", err)
		}
		if got == nil {
			t.Fatal("GetCharacterSaveData() returned nil")
		}
		if got.CharID != charID {
			t.Errorf("CharID = %d, want %d", got.CharID, charID)
		}
	})

	t.Run("skips_corrupt_backup_and_uses_next", func(t *testing.T) {
		userID := CreateTestUser(t, db, "multibackup_user")
		charID := CreateTestCharacter(t, db, userID, "BackupChar")

		// Slot 1 is newer (saved second) but has invalid compressed data.
		// Slot 0 is older but valid. Recovery must skip slot 1 and use slot 0.
		if err := repo.SaveBackup(charID, 0, validCompressed); err != nil {
			t.Fatalf("SaveBackup slot 0: %v", err)
		}
		if err := repo.SaveBackup(charID, 1, invalidCompressed); err != nil {
			t.Fatalf("SaveBackup slot 1: %v", err)
		}
		// Update slot 1's saved_at to be newer than slot 0.
		if _, err := db.Exec(
			"UPDATE savedata_backups SET saved_at = now() + interval '1 minute' WHERE char_id = $1 AND slot = 1",
			charID,
		); err != nil {
			t.Fatalf("update saved_at: %v", err)
		}

		if _, err := db.Exec("UPDATE characters SET savedata_hash = $1 WHERE id = $2", corruptHash, charID); err != nil {
			t.Fatalf("set corrupt hash: %v", err)
		}

		mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
		s := createTestSession(mock)
		s.charID = charID
		SetTestDB(s.server, db)
		s.server.erupeConfig.RealClientMode = cfg.Z2

		got, err := GetCharacterSaveData(s, charID)
		if err != nil {
			t.Fatalf("GetCharacterSaveData() unexpected error: %v", err)
		}
		if got == nil {
			t.Fatal("GetCharacterSaveData() returned nil")
		}
	})

	t.Run("returns_error_when_no_backups", func(t *testing.T) {
		userID := CreateTestUser(t, db, "nobackup_user")
		charID := CreateTestCharacter(t, db, userID, "NoBackupChar")

		if _, err := db.Exec("UPDATE characters SET savedata_hash = $1 WHERE id = $2", corruptHash, charID); err != nil {
			t.Fatalf("set corrupt hash: %v", err)
		}

		mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
		s := createTestSession(mock)
		s.charID = charID
		SetTestDB(s.server, db)
		s.server.erupeConfig.RealClientMode = cfg.Z2

		_, err := GetCharacterSaveData(s, charID)
		if err == nil {
			t.Fatal("expected error when no backups available, got nil")
		}
	})

	t.Run("returns_error_when_all_backups_corrupt", func(t *testing.T) {
		userID := CreateTestUser(t, db, "allcorrupt_user")
		charID := CreateTestCharacter(t, db, userID, "AllCorruptChar")

		if err := repo.SaveBackup(charID, 0, invalidCompressed); err != nil {
			t.Fatalf("SaveBackup: %v", err)
		}

		if _, err := db.Exec("UPDATE characters SET savedata_hash = $1 WHERE id = $2", corruptHash, charID); err != nil {
			t.Fatalf("set corrupt hash: %v", err)
		}

		mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
		s := createTestSession(mock)
		s.charID = charID
		SetTestDB(s.server, db)
		s.server.erupeConfig.RealClientMode = cfg.Z2

		_, err := GetCharacterSaveData(s, charID)
		if err == nil {
			t.Fatal("expected error when all backups corrupt, got nil")
		}
	})
}

// TestCharacterSaveData_Save_Integration tests saving character data to database
func TestCharacterSaveData_Save_Integration(t *testing.T) {
	db := SetupTestDB(t)
	defer TeardownTestDB(t, db)

	// Create test user and character
	userID := CreateTestUser(t, db, "savetest")
	charID := CreateTestCharacter(t, db, userID, "SaveChar")

	// Create test session
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)
	s.charID = charID
	SetTestDB(s.server, db)
	s.server.erupeConfig.RealClientMode = cfg.Z2

	// Load character save data
	saveData, err := GetCharacterSaveData(s, charID)
	if err != nil {
		t.Fatalf("Failed to get save data: %v", err)
	}

	// Modify save data
	saveData.HR = 999
	saveData.GR = 100
	saveData.Gender = true
	saveData.WeaponType = 5
	saveData.WeaponID = 1234

	// Save it
	if err := saveData.Save(s); err != nil {
		t.Fatalf("Save failed: %v", err)
	}

	// Reload and verify
	var hr, gr uint16
	var gender bool
	var weaponType uint8
	var weaponID uint16

	err = db.QueryRow("SELECT hr, gr, is_female, weapon_type, weapon_id FROM characters WHERE id = $1",
		charID).Scan(&hr, &gr, &gender, &weaponType, &weaponID)
	if err != nil {
		t.Fatalf("Failed to query updated character: %v", err)
	}

	if hr != 999 {
		t.Errorf("HR = %d, want 999", hr)
	}
	if gr != 100 {
		t.Errorf("GR = %d, want 100", gr)
	}
	if !gender {
		t.Error("Gender should be true (female)")
	}
	if weaponType != 5 {
		t.Errorf("WeaponType = %d, want 5", weaponType)
	}
	if weaponID != 1234 {
		t.Errorf("WeaponID = %d, want 1234", weaponID)
	}
}

// TestGRPtoGR tests the GRP to GR conversion function
func TestGRPtoGR(t *testing.T) {
	tests := []struct {
		name   string
		grp    int
		wantGR uint16
	}{
		{
			name:   "zero_grp",
			grp:    0,
			wantGR: 1, // Function returns 1 for 0 GRP
		},
		{
			name:   "low_grp",
			grp:    10000,
			wantGR: 10, // Function returns 10 for 10000 GRP
		},
		{
			name:   "mid_grp",
			grp:    500000,
			wantGR: 88, // Function returns 88 for 500000 GRP
		},
		{
			name:   "high_grp",
			grp:    2000000,
			wantGR: 265, // Function returns 265 for 2000000 GRP
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotGR := grpToGR(tt.grp)
			if gotGR != tt.wantGR {
				t.Errorf("grpToGR(%d) = %d, want %d", tt.grp, gotGR, tt.wantGR)
			}
		})
	}
}

// BenchmarkCompress benchmarks savedata compression
func BenchmarkCompress(b *testing.B) {
	data := bytes.Repeat([]byte{0xAA, 0xBB, 0xCC, 0xDD}, 25000) // 100KB
	save := &CharacterSaveData{
		decompSave: data,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = save.Compress()
	}
}

// BenchmarkDecompress benchmarks savedata decompression
func BenchmarkDecompress(b *testing.B) {
	data := bytes.Repeat([]byte{0xAA, 0xBB, 0xCC, 0xDD}, 25000)
	compressed, _ := nullcomp.Compress(data)

	save := &CharacterSaveData{
		compSave: compressed,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = save.Decompress()
	}
}

// --- Mock-based GetCharacterSaveData tests ---

func TestGetCharacterSaveData_NilSavedata(t *testing.T) {
	server := createMockServer()
	mock := newMockCharacterRepo()
	mock.loadSaveDataID = 42
	mock.loadSaveDataName = "Hunter"
	mock.loadSaveDataNew = true
	server.charRepo = mock
	session := createMockSession(42, server)

	result, err := GetCharacterSaveData(session, 42)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.CharID != 42 {
		t.Errorf("CharID = %d, want 42", result.CharID)
	}
	if result.Name != "Hunter" {
		t.Errorf("Name = %q, want %q", result.Name, "Hunter")
	}
	if !result.IsNewCharacter {
		t.Error("IsNewCharacter should be true")
	}
}

func TestGetCharacterSaveData_NotFound(t *testing.T) {
	server := createMockServer()
	mock := newMockCharacterRepo()
	mock.loadSaveDataErr = sql.ErrNoRows
	server.charRepo = mock
	session := createMockSession(1, server)

	result, err := GetCharacterSaveData(session, 999)
	if err == nil {
		t.Fatal("expected error for missing character")
	}
	if result != nil {
		t.Error("expected nil result for missing character")
	}
}

func TestGetCharacterSaveData_DBError(t *testing.T) {
	server := createMockServer()
	mock := newMockCharacterRepo()
	mock.loadSaveDataErr = errors.New("connection refused")
	server.charRepo = mock
	session := createMockSession(1, server)

	result, err := GetCharacterSaveData(session, 1)
	if err == nil {
		t.Fatal("expected error on DB failure")
	}
	if result != nil {
		t.Error("expected nil result on DB failure")
	}
}

func TestGetCharacterSaveData_WithCompressedData(t *testing.T) {
	server := createMockServer()
	mock := newMockCharacterRepo()

	// Create minimal valid savedata and compress it
	saveData := make([]byte, 150000)
	copy(saveData[88:], append([]byte("TestHunter"), 0x00))
	compressed, err := nullcomp.Compress(saveData)
	if err != nil {
		t.Fatalf("failed to compress test savedata: %v", err)
	}

	mock.loadSaveDataID = 10
	mock.loadSaveDataData = compressed
	mock.loadSaveDataName = "TestHunter"
	mock.loadSaveDataNew = false
	server.charRepo = mock
	session := createMockSession(10, server)

	result, err := GetCharacterSaveData(session, 10)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.CharID != 10 {
		t.Errorf("CharID = %d, want 10", result.CharID)
	}
	if result.IsNewCharacter {
		t.Error("IsNewCharacter should be false")
	}
	if result.Name != "TestHunter" {
		t.Errorf("Name = %q, want %q", result.Name, "TestHunter")
	}
}

func TestGetCharacterSaveData_NewCharacterSkipsDecompress(t *testing.T) {
	// When savedata is nil AND IsNewCharacter=true, GetCharacterSaveData
	// returns a valid result without decompressing.
	server := createMockServer()
	mock := newMockCharacterRepo()
	mock.loadSaveDataID = 5
	mock.loadSaveDataName = "NewPlayer"
	mock.loadSaveDataNew = true
	// loadSaveDataData is nil
	server.charRepo = mock
	session := createMockSession(5, server)

	result, err := GetCharacterSaveData(session, 5)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if !result.IsNewCharacter {
		t.Error("IsNewCharacter should be true")
	}
	if result.CharID != 5 {
		t.Errorf("CharID = %d, want 5", result.CharID)
	}
}

func TestGetCharacterSaveData_ConfigMode(t *testing.T) {
	server := createMockServer()
	mock := newMockCharacterRepo()

	saveData := make([]byte, 150000)
	copy(saveData[88:], append([]byte("ModeTest"), 0x00))
	compressed, err := nullcomp.Compress(saveData)
	if err != nil {
		t.Fatalf("failed to compress: %v", err)
	}

	mock.loadSaveDataID = 1
	mock.loadSaveDataData = compressed
	mock.loadSaveDataName = "ModeTest"
	server.charRepo = mock

	modes := []struct {
		mode cfg.Mode
		name string
	}{
		{cfg.S6, "S6"},
		{cfg.F5, "F5"},
		{cfg.G10, "G10"},
		{cfg.Z2, "Z2"},
		{cfg.ZZ, "ZZ"},
	}
	for _, tc := range modes {
		mode := tc.mode
		t.Run(tc.name, func(t *testing.T) {
			server.erupeConfig.RealClientMode = mode
			session := createMockSession(1, server)

			result, err := GetCharacterSaveData(session, 1)
			if err != nil {
				t.Fatalf("unexpected error for mode %v: %v", mode, err)
			}
			if result.Mode != mode {
				t.Errorf("Mode = %v, want %v", result.Mode, mode)
			}
			expectedPointers := getPointers(mode)
			if len(result.Pointers) != len(expectedPointers) {
				t.Errorf("Pointers count = %d, want %d", len(result.Pointers), len(expectedPointers))
			}
		})
	}
}

// TestGetCharacterSaveData_IntegrityCheck verifies the SHA-256 hash guard and
// that DisableSaveIntegrityCheck bypasses it without returning an error.
func TestGetCharacterSaveData_IntegrityCheck(t *testing.T) {
	// Build a minimal valid savedata blob and compress it.
	rawSave := make([]byte, 150000)
	copy(rawSave[88:], []byte("TestChar\x00"))
	compressed, err := nullcomp.Compress(rawSave)
	if err != nil {
		t.Fatalf("compress: %v", err)
	}

	// A hash that deliberately does NOT match rawSave.
	wrongHash := bytes.Repeat([]byte{0xDE}, 32)

	tests := []struct {
		name    string
		disable bool
		hash    []byte
		wantErr bool
	}{
		{
			name:    "nil hash skips check",
			disable: false,
			hash:    nil,
			wantErr: false,
		},
		{
			name:    "mismatched hash fails when check enabled",
			disable: false,
			hash:    wrongHash,
			wantErr: true,
		},
		{
			name:    "mismatched hash passes when check disabled",
			disable: true,
			hash:    wrongHash,
			wantErr: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			mock := newMockCharacterRepo()
			mock.loadSaveDataID = 1
			mock.loadSaveDataData = compressed
			mock.loadSaveDataName = "TestChar"
			mock.loadSaveDataHash = tc.hash

			server := createMockServer()
			server.erupeConfig.RealClientMode = cfg.ZZ
			server.erupeConfig.DisableSaveIntegrityCheck = tc.disable
			server.charRepo = mock
			session := createMockSession(1, server)

			_, err := GetCharacterSaveData(session, 1)
			if tc.wantErr && err == nil {
				t.Error("expected error, got nil")
			}
			if !tc.wantErr && err != nil {
				t.Errorf("unexpected error: %v", err)
			}
		})
	}
}
