package mhfpacket

import (
	"bytes"
	"io"
	"testing"

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

// TestParseLargeMsgSysUpdateRightBuild tests Build for MsgSysUpdateRight (no Parse implementation).
func TestParseLargeMsgSysUpdateRightBuild(t *testing.T) {
	ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
	original := &MsgSysUpdateRight{
		ClientRespAckHandle: 0x12345678,
		Bitfield:            0xDEADBEEF,
		Rights:              nil,
		TokenLength:         0,
	}

	bf := byteframe.NewByteFrame()
	if err := original.Build(bf, ctx); err != nil {
		t.Fatalf("Build() error = %v", err)
	}

	// Verify binary output manually:
	// uint32 ClientRespAckHandle + uint32 Bitfield + uint16 Rights count(0) + uint16 padding(0) + ps.Uint16 empty string(uint16(1) + 0x00)
	data := bf.Data()
	if len(data) < 12 {
		t.Fatalf("Build() wrote %d bytes, want at least 12", len(data))
	}

	_, _ = bf.Seek(0, io.SeekStart)
	if bf.ReadUint32() != 0x12345678 {
		t.Error("ClientRespAckHandle mismatch")
	}
	if bf.ReadUint32() != 0xDEADBEEF {
		t.Error("Bitfield mismatch")
	}
	if bf.ReadUint16() != 0 {
		t.Error("Rights count should be 0")
	}
}

// TestParseLargeMsgMhfOperateWarehouse tests Parse for MsgMhfOperateWarehouse.
func TestParseLargeMsgMhfOperateWarehouse(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xAABBCCDD) // AckHandle
	bf.WriteUint8(1)           // Operation
	bf.WriteUint8(0)           // BoxType = item
	bf.WriteUint8(2)           // BoxIndex
	bf.WriteUint8(8)           // lenName (unused but read)
	bf.WriteUint16(0)          // Unk
	bf.WriteBytes([]byte("TestBox"))
	bf.WriteUint8(0) // null terminator
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfOperateWarehouse{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xAABBCCDD {
		t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
	}
	if pkt.Operation != 1 {
		t.Errorf("Operation = %d, want 1", pkt.Operation)
	}
	if pkt.BoxType != 0 {
		t.Errorf("BoxType = %d, want 0", pkt.BoxType)
	}
	if pkt.BoxIndex != 2 {
		t.Errorf("BoxIndex = %d, want 2", pkt.BoxIndex)
	}
	if pkt.Name != "TestBox" {
		t.Errorf("Name = %q, want %q", pkt.Name, "TestBox")
	}
}

// TestParseLargeMsgMhfOperateWarehouseEquip tests Parse for MsgMhfOperateWarehouse with equip box type.
func TestParseLargeMsgMhfOperateWarehouseEquip(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(42) // AckHandle
	bf.WriteUint8(2)   // Operation
	bf.WriteUint8(1)   // BoxType = equip
	bf.WriteUint8(0)   // BoxIndex
	bf.WriteUint8(5)   // lenName
	bf.WriteUint16(0)  // Unk
	bf.WriteBytes([]byte("Arms"))
	bf.WriteUint8(0) // null terminator
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfOperateWarehouse{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.BoxType != 1 {
		t.Errorf("BoxType = %d, want 1", pkt.BoxType)
	}
	if pkt.Name != "Arms" {
		t.Errorf("Name = %q, want %q", pkt.Name, "Arms")
	}
}

// TestParseLargeMsgMhfLoadHouse tests Parse for MsgMhfLoadHouse.
func TestParseLargeMsgMhfLoadHouse(t *testing.T) {
	tests := []struct {
		name        string
		ackHandle   uint32
		charID      uint32
		destination uint8
		checkPass   bool
		password    string
	}{
		{"with password", 0xAABBCCDD, 12345, 1, true, "pass123"},
		{"no password", 0x11111111, 0, 0, false, ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			bf := byteframe.NewByteFrame()
			bf.WriteUint32(tt.ackHandle)
			bf.WriteUint32(tt.charID)
			bf.WriteUint8(tt.destination)
			bf.WriteBool(tt.checkPass)
			bf.WriteUint16(0)                          // Unk (hardcoded 0)
			bf.WriteUint8(uint8(len(tt.password) + 1)) // Password length
			bf.WriteBytes([]byte(tt.password))
			bf.WriteUint8(0) // null terminator
			_, _ = bf.Seek(0, io.SeekStart)

			pkt := &MsgMhfLoadHouse{}
			if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
				t.Fatalf("Parse() error = %v", err)
			}

			if pkt.AckHandle != tt.ackHandle {
				t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
			}
			if pkt.CharID != tt.charID {
				t.Errorf("CharID = %d, want %d", pkt.CharID, tt.charID)
			}
			if pkt.Destination != tt.destination {
				t.Errorf("Destination = %d, want %d", pkt.Destination, tt.destination)
			}
			if pkt.CheckPass != tt.checkPass {
				t.Errorf("CheckPass = %v, want %v", pkt.CheckPass, tt.checkPass)
			}
			if pkt.Password != tt.password {
				t.Errorf("Password = %q, want %q", pkt.Password, tt.password)
			}
		})
	}
}

// TestParseLargeMsgMhfSendMail tests Parse for MsgMhfSendMail.
func TestParseLargeMsgMhfSendMail(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x12345678) // AckHandle
	bf.WriteUint32(99999)      // RecipientID
	bf.WriteUint16(6)          // SubjectLength
	bf.WriteUint16(12)         // BodyLength
	bf.WriteUint32(5)          // Quantity
	bf.WriteUint16(1001)       // ItemID
	bf.WriteBytes([]byte("Hello"))
	bf.WriteUint8(0) // null terminator for Subject
	bf.WriteBytes([]byte("Hello World"))
	bf.WriteUint8(0) // null terminator for Body
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfSendMail{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x12345678 {
		t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
	}
	if pkt.RecipientID != 99999 {
		t.Errorf("RecipientID = %d, want 99999", pkt.RecipientID)
	}
	if pkt.SubjectLength != 6 {
		t.Errorf("SubjectLength = %d, want 6", pkt.SubjectLength)
	}
	if pkt.BodyLength != 12 {
		t.Errorf("BodyLength = %d, want 12", pkt.BodyLength)
	}
	if pkt.Quantity != 5 {
		t.Errorf("Quantity = %d, want 5", pkt.Quantity)
	}
	if pkt.ItemID != 1001 {
		t.Errorf("ItemID = %d, want 1001", pkt.ItemID)
	}
	if pkt.Subject != "Hello" {
		t.Errorf("Subject = %q, want %q", pkt.Subject, "Hello")
	}
	if pkt.Body != "Hello World" {
		t.Errorf("Body = %q, want %q", pkt.Body, "Hello World")
	}
}

// TestParseLargeMsgMhfApplyBbsArticle tests Parse for MsgMhfApplyBbsArticle.
func TestParseLargeMsgMhfApplyBbsArticle(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xCAFEBABE) // AckHandle
	bf.WriteUint32(42)         // Unk0

	// Unk1: 16 bytes
	unk1 := make([]byte, 16)
	for i := range unk1 {
		unk1[i] = byte(i + 1)
	}
	bf.WriteBytes(unk1)

	// Name: 32 bytes (padded with nulls) - uses bfutil.UpToNull
	nameBytes := make([]byte, 32)
	copy(nameBytes, "Hunter")
	bf.WriteBytes(nameBytes)

	// Title: 128 bytes (padded with nulls)
	titleBytes := make([]byte, 128)
	copy(titleBytes, "My Post Title")
	bf.WriteBytes(titleBytes)

	// Description: 256 bytes (padded with nulls)
	descBytes := make([]byte, 256)
	copy(descBytes, "This is a description")
	bf.WriteBytes(descBytes)

	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfApplyBbsArticle{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xCAFEBABE {
		t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
	}
	if pkt.Unk0 != 42 {
		t.Errorf("Unk0 = %d, want 42", pkt.Unk0)
	}
	if !bytes.Equal(pkt.Unk1, unk1) {
		t.Error("Unk1 mismatch")
	}
	if pkt.Name != "Hunter" {
		t.Errorf("Name = %q, want %q", pkt.Name, "Hunter")
	}
	if pkt.Title != "My Post Title" {
		t.Errorf("Title = %q, want %q", pkt.Title, "My Post Title")
	}
	if pkt.Description != "This is a description" {
		t.Errorf("Description = %q, want %q", pkt.Description, "This is a description")
	}
}

// TestParseLargeMsgMhfChargeFesta tests Parse for MsgMhfChargeFesta.
func TestParseLargeMsgMhfChargeFesta(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x11223344) // AckHandle
	bf.WriteUint32(100)        // FestaID
	bf.WriteUint32(200)        // GuildID
	bf.WriteUint16(3)          // soul count
	bf.WriteUint16(10)         // soul value 1
	bf.WriteUint16(20)         // soul value 2
	bf.WriteUint16(30)         // soul value 3
	bf.WriteUint8(0)           // Unk
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfChargeFesta{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x11223344 {
		t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
	}
	if pkt.FestaID != 100 {
		t.Errorf("FestaID = %d, want 100", pkt.FestaID)
	}
	if pkt.GuildID != 200 {
		t.Errorf("GuildID = %d, want 200", pkt.GuildID)
	}
	if len(pkt.Souls) != 3 {
		t.Fatalf("Souls len = %d, want 3", len(pkt.Souls))
	}
	expectedSouls := []uint16{10, 20, 30}
	for i, v := range expectedSouls {
		if pkt.Souls[i] != v {
			t.Errorf("Souls[%d] = %d, want %d", i, pkt.Souls[i], v)
		}
	}
}

// TestParseLargeMsgMhfChargeFestaZeroSouls tests Parse for MsgMhfChargeFesta with zero soul entries.
func TestParseLargeMsgMhfChargeFestaZeroSouls(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(1) // AckHandle
	bf.WriteUint32(0) // FestaID
	bf.WriteUint32(0) // GuildID
	bf.WriteUint16(0) // soul count = 0
	bf.WriteUint8(0)  // Unk
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfChargeFesta{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}
	if len(pkt.Souls) != 0 {
		t.Errorf("Souls len = %d, want 0", len(pkt.Souls))
	}
}

// TestParseLargeMsgMhfOperateJoint tests Parse for MsgMhfOperateJoint.
// Parse reads: uint32 AckHandle, uint32 AllianceID, uint32 GuildID, uint8 Action,
// uint8 dataLen, 4 bytes Data1, dataLen bytes Data2.
func TestParseLargeMsgMhfOperateJoint(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x12345678)                    // AckHandle
	bf.WriteUint32(100)                           // AllianceID
	bf.WriteUint32(200)                           // GuildID
	bf.WriteUint8(0x01)                           // Action = OPERATE_JOINT_DISBAND
	bf.WriteUint8(3)                              // dataLen = 3
	bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC, 0xDD}) // Data1 (always 4 bytes)
	bf.WriteBytes([]byte{0x01, 0x02, 0x03})       // Data2 (dataLen bytes)
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfOperateJoint{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x12345678 {
		t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
	}
	if pkt.AllianceID != 100 {
		t.Errorf("AllianceID = %d, want 100", pkt.AllianceID)
	}
	if pkt.GuildID != 200 {
		t.Errorf("GuildID = %d, want 200", pkt.GuildID)
	}
	if pkt.Action != OPERATE_JOINT_DISBAND {
		t.Errorf("Action = %d, want %d", pkt.Action, OPERATE_JOINT_DISBAND)
	}
	if pkt.Data1 == nil {
		t.Fatal("Data1 is nil")
	}
	if pkt.Data2 == nil {
		t.Fatal("Data2 is nil")
	}
}

// TestParseLargeMsgMhfOperationInvGuild tests Parse for MsgMhfOperationInvGuild.
func TestParseLargeMsgMhfOperationInvGuild(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xAABBCCDD) // AckHandle
	bf.WriteUint8(1)           // Operation
	bf.WriteUint8(5)           // ActiveHours
	bf.WriteUint8(7)           // DaysActive
	bf.WriteUint8(3)           // PlayStyle
	bf.WriteUint8(2)           // GuildRequest
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfOperationInvGuild{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xAABBCCDD {
		t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
	}
	if pkt.Operation != 1 {
		t.Errorf("Operation = %d, want 1", pkt.Operation)
	}
	if pkt.ActiveHours != 5 {
		t.Errorf("ActiveHours = %d, want 5", pkt.ActiveHours)
	}
	if pkt.DaysActive != 7 {
		t.Errorf("DaysActive = %d, want 7", pkt.DaysActive)
	}
	if pkt.PlayStyle != 3 {
		t.Errorf("PlayStyle = %d, want 3", pkt.PlayStyle)
	}
	if pkt.GuildRequest != 2 {
		t.Errorf("GuildRequest = %d, want 2", pkt.GuildRequest)
	}
}

// TestParseLargeMsgMhfSaveMercenary tests Parse for MsgMhfSaveMercenary.
func TestParseLargeMsgMhfSaveMercenary(t *testing.T) {
	mercData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}

	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xCAFEBABE)            // AckHandle
	bf.WriteUint32(0)                     // lenData (skipped)
	bf.WriteUint32(5000)                  // GCP
	bf.WriteUint32(42)                    // PactMercID
	bf.WriteUint32(uint32(len(mercData))) // dataSize
	bf.WriteUint32(0)                     // Merc index (skipped)
	bf.WriteBytes(mercData)
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfSaveMercenary{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xCAFEBABE {
		t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
	}
	if pkt.GCP != 5000 {
		t.Errorf("GCP = %d, want 5000", pkt.GCP)
	}
	if pkt.PactMercID != 42 {
		t.Errorf("PactMercID = %d, want 42", pkt.PactMercID)
	}
	if !bytes.Equal(pkt.MercData, mercData) {
		t.Errorf("MercData = %v, want %v", pkt.MercData, mercData)
	}
}

// TestParseLargeMsgMhfUpdateHouse tests Parse for MsgMhfUpdateHouse.
func TestParseLargeMsgMhfUpdateHouse(t *testing.T) {
	tests := []struct {
		name     string
		state    uint8
		password string
	}{
		{"with password", 1, "secret"},
		{"empty password", 0, ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			bf := byteframe.NewByteFrame()
			bf.WriteUint32(0x12345678)                 // AckHandle
			bf.WriteUint8(tt.state)                    // State
			bf.WriteUint8(1)                           // Unk1
			bf.WriteUint16(0)                          // Unk2
			bf.WriteUint8(uint8(len(tt.password) + 1)) // Password length
			bf.WriteBytes([]byte(tt.password))
			bf.WriteUint8(0) // null terminator
			_, _ = bf.Seek(0, io.SeekStart)

			pkt := &MsgMhfUpdateHouse{}
			if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
				t.Fatalf("Parse() error = %v", err)
			}

			if pkt.AckHandle != 0x12345678 {
				t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
			}
			if pkt.State != tt.state {
				t.Errorf("State = %d, want %d", pkt.State, tt.state)
			}
			if pkt.HasPassword != 1 {
				t.Errorf("HasPassword = %d, want 1", pkt.HasPassword)
			}
			if pkt.Password != tt.password {
				t.Errorf("Password = %q, want %q", pkt.Password, tt.password)
			}
		})
	}
}

// TestParseLargeMsgSysCreateAcquireSemaphore tests Parse for MsgSysCreateAcquireSemaphore.
func TestParseLargeMsgSysCreateAcquireSemaphore(t *testing.T) {
	semID := "stage_001"
	semBytes := make([]byte, len(semID)+1) // include space for null if needed
	copy(semBytes, semID)

	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xDEADBEEF)          // AckHandle
	bf.WriteUint16(100)                 // Unk0
	bf.WriteUint8(4)                    // PlayerCount
	bf.WriteUint8(uint8(len(semBytes))) // SemaphoreIDLength
	bf.WriteBytes(semBytes)
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgSysCreateAcquireSemaphore{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xDEADBEEF {
		t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
	}
	if pkt.Unk0 != 100 {
		t.Errorf("Unk0 = %d, want 100", pkt.Unk0)
	}
	if pkt.PlayerCount != 4 {
		t.Errorf("PlayerCount = %d, want 4", pkt.PlayerCount)
	}
	if pkt.SemaphoreID != semID {
		t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, semID)
	}
}

// TestParseLargeMsgMhfOperateGuild tests Parse for MsgMhfOperateGuild.
func TestParseLargeMsgMhfOperateGuild(t *testing.T) {
	dataPayload := []byte{0x10, 0x20, 0x30, 0x40, 0x50}

	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xAABBCCDD)                    // AckHandle
	bf.WriteUint32(999)                           // GuildID
	bf.WriteUint8(0x09)                           // Action = OperateGuildUpdateComment
	bf.WriteUint8(uint8(len(dataPayload)))        // dataLen
	bf.WriteBytes([]byte{0x01, 0x02, 0x03, 0x04}) // Data1 (always 4 bytes)
	bf.WriteBytes(dataPayload)                    // Data2 (dataLen bytes)
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfOperateGuild{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xAABBCCDD {
		t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
	}
	if pkt.GuildID != 999 {
		t.Errorf("GuildID = %d, want 999", pkt.GuildID)
	}
	if pkt.Action != OperateGuildUpdateComment {
		t.Errorf("Action = %d, want %d", pkt.Action, OperateGuildUpdateComment)
	}
	if pkt.Data1 == nil {
		t.Fatal("Data1 is nil")
	}
	if pkt.Data2 == nil {
		t.Fatal("Data2 is nil")
	}
	data2Bytes := pkt.Data2.Data()
	if !bytes.Equal(data2Bytes, dataPayload) {
		t.Errorf("Data2 = %v, want %v", data2Bytes, dataPayload)
	}
}

// TestParseLargeMsgMhfReadBeatLevel tests Parse for MsgMhfReadBeatLevel.
func TestParseLargeMsgMhfReadBeatLevel(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x12345678) // AckHandle
	bf.WriteUint32(1)          // Unk0
	bf.WriteUint32(4)          // ValidIDCount

	// Write 16 uint32 IDs
	ids := [16]uint32{0x74, 0x6B, 0x02, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	for _, id := range ids {
		bf.WriteUint32(id)
	}
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfReadBeatLevel{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x12345678 {
		t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
	}
	if pkt.Unk0 != 1 {
		t.Errorf("Unk0 = %d, want 1", pkt.Unk0)
	}
	if pkt.ValidIDCount != 4 {
		t.Errorf("ValidIDCount = %d, want 4", pkt.ValidIDCount)
	}
	for i, id := range ids {
		if pkt.IDs[i] != id {
			t.Errorf("IDs[%d] = 0x%X, want 0x%X", i, pkt.IDs[i], id)
		}
	}
}

// TestParseLargeMsgSysCreateObject tests Parse for MsgSysCreateObject.
func TestParseLargeMsgSysCreateObject(t *testing.T) {
	tests := []struct {
		name      string
		ackHandle uint32
		x, y, z   float32
		unk0      uint32
	}{
		{"origin", 1, 0.0, 0.0, 0.0, 0},
		{"typical", 0x12345678, 1.5, 2.5, 3.5, 42},
		{"negative coords", 0xFFFFFFFF, -100.25, 200.75, -300.125, 0xDEADBEEF},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			bf := byteframe.NewByteFrame()
			bf.WriteUint32(tt.ackHandle)
			bf.WriteFloat32(tt.x)
			bf.WriteFloat32(tt.y)
			bf.WriteFloat32(tt.z)
			bf.WriteUint32(tt.unk0)
			_, _ = bf.Seek(0, io.SeekStart)

			pkt := &MsgSysCreateObject{}
			if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
				t.Fatalf("Parse() error = %v", err)
			}

			if pkt.AckHandle != tt.ackHandle {
				t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
			}
			if pkt.X != tt.x {
				t.Errorf("X = %f, want %f", pkt.X, tt.x)
			}
			if pkt.Y != tt.y {
				t.Errorf("Y = %f, want %f", pkt.Y, tt.y)
			}
			if pkt.Z != tt.z {
				t.Errorf("Z = %f, want %f", pkt.Z, tt.z)
			}
			if pkt.Unk0 != tt.unk0 {
				t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
			}
		})
	}
}

// TestParseLargeMsgSysLockGlobalSema tests Parse for MsgSysLockGlobalSema.
func TestParseLargeMsgSysLockGlobalSema(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xDEADBEEF) // AckHandle
	bf.WriteUint16(8)          // UserIDLength
	bf.WriteUint16(11)         // ServerChannelIDLength
	bf.WriteBytes([]byte("user123"))
	bf.WriteUint8(0) // null terminator
	bf.WriteBytes([]byte("channel_01"))
	bf.WriteUint8(0) // null terminator
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgSysLockGlobalSema{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xDEADBEEF {
		t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
	}
	if pkt.UserIDLength != 8 {
		t.Errorf("UserIDLength = %d, want 8", pkt.UserIDLength)
	}
	if pkt.ServerChannelIDLength != 11 {
		t.Errorf("ServerChannelIDLength = %d, want 11", pkt.ServerChannelIDLength)
	}
	if pkt.UserIDString != "user123" {
		t.Errorf("UserIDString = %q, want %q", pkt.UserIDString, "user123")
	}
	if pkt.ServerChannelIDString != "channel_01" {
		t.Errorf("ServerChannelIDString = %q, want %q", pkt.ServerChannelIDString, "channel_01")
	}
}

// TestParseLargeMsgMhfCreateJoint tests Parse for MsgMhfCreateJoint.
func TestParseLargeMsgMhfCreateJoint(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xCAFEBABE) // AckHandle
	bf.WriteUint32(500)        // GuildID
	bf.WriteUint32(15)         // len (unused)
	bf.WriteBytes([]byte("Alliance01"))
	bf.WriteUint8(0) // null terminator
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfCreateJoint{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xCAFEBABE {
		t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
	}
	if pkt.GuildID != 500 {
		t.Errorf("GuildID = %d, want 500", pkt.GuildID)
	}
	if pkt.Name != "Alliance01" {
		t.Errorf("Name = %q, want %q", pkt.Name, "Alliance01")
	}
}

// TestParseLargeMsgMhfGetUdTacticsRemainingPoint tests Parse for MsgMhfGetUdTacticsRemainingPoint.
func TestParseLargeMsgMhfGetUdTacticsRemainingPoint(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x12345678) // AckHandle
	bf.WriteUint32(100)        // Unk0
	bf.WriteUint32(200)        // Unk1
	bf.WriteUint32(300)        // Unk2
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfGetUdTacticsRemainingPoint{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x12345678 {
		t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
	}
	if pkt.Unk0 != 100 {
		t.Errorf("Unk0 = %d, want 100", pkt.Unk0)
	}
	if pkt.Unk1 != 200 {
		t.Errorf("Unk1 = %d, want 200", pkt.Unk1)
	}
	if pkt.Unk2 != 300 {
		t.Errorf("Unk2 = %d, want 300", pkt.Unk2)
	}
}

// TestParseLargeMsgMhfPostCafeDurationBonusReceived tests Parse for MsgMhfPostCafeDurationBonusReceived.
func TestParseLargeMsgMhfPostCafeDurationBonusReceived(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xAABBCCDD) // AckHandle
	bf.WriteUint32(3)          // count
	bf.WriteUint32(1001)       // CafeBonusID[0]
	bf.WriteUint32(1002)       // CafeBonusID[1]
	bf.WriteUint32(1003)       // CafeBonusID[2]
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfPostCafeDurationBonusReceived{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xAABBCCDD {
		t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
	}
	if len(pkt.CafeBonusID) != 3 {
		t.Fatalf("CafeBonusID len = %d, want 3", len(pkt.CafeBonusID))
	}
	expected := []uint32{1001, 1002, 1003}
	for i, v := range expected {
		if pkt.CafeBonusID[i] != v {
			t.Errorf("CafeBonusID[%d] = %d, want %d", i, pkt.CafeBonusID[i], v)
		}
	}
}

// TestParseLargeMsgMhfPostCafeDurationBonusReceivedEmpty tests Parse with zero IDs.
func TestParseLargeMsgMhfPostCafeDurationBonusReceivedEmpty(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(1) // AckHandle
	bf.WriteUint32(0) // count = 0
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfPostCafeDurationBonusReceived{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}
	if len(pkt.CafeBonusID) != 0 {
		t.Errorf("CafeBonusID len = %d, want 0", len(pkt.CafeBonusID))
	}
}

// TestParseLargeMsgMhfRegistGuildAdventureDiva tests Parse for MsgMhfRegistGuildAdventureDiva.
func TestParseLargeMsgMhfRegistGuildAdventureDiva(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x12345678) // AckHandle
	bf.WriteUint32(5)          // Destination
	bf.WriteUint32(1000)       // Charge
	bf.WriteUint32(42)         // CharID (skipped)
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfRegistGuildAdventureDiva{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x12345678 {
		t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
	}
	if pkt.Destination != 5 {
		t.Errorf("Destination = %d, want 5", pkt.Destination)
	}
	if pkt.Charge != 1000 {
		t.Errorf("Charge = %d, want 1000", pkt.Charge)
	}
}

// TestParseLargeMsgMhfStateFestaG tests Parse for MsgMhfStateFestaG.
func TestParseLargeMsgMhfStateFestaG(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xDEADBEEF) // AckHandle
	bf.WriteUint32(100)        // FestaID
	bf.WriteUint32(200)        // GuildID
	bf.WriteUint16(0)          // Hardcoded 0
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfStateFestaG{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xDEADBEEF {
		t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
	}
	if pkt.FestaID != 100 {
		t.Errorf("FestaID = %d, want 100", pkt.FestaID)
	}
	if pkt.GuildID != 200 {
		t.Errorf("GuildID = %d, want 200", pkt.GuildID)
	}
}

// TestParseLargeMsgMhfStateFestaU tests Parse for MsgMhfStateFestaU.
func TestParseLargeMsgMhfStateFestaU(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xCAFEBABE) // AckHandle
	bf.WriteUint32(300)        // FestaID
	bf.WriteUint32(400)        // GuildID
	bf.WriteUint16(0)          // Hardcoded 0
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgMhfStateFestaU{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xCAFEBABE {
		t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
	}
	if pkt.FestaID != 300 {
		t.Errorf("FestaID = %d, want 300", pkt.FestaID)
	}
	if pkt.GuildID != 400 {
		t.Errorf("GuildID = %d, want 400", pkt.GuildID)
	}
}

// TestParseLargeMsgSysEnumerateStage tests Parse for MsgSysEnumerateStage.
func TestParseLargeMsgSysEnumerateStage(t *testing.T) {
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0x11223344) // AckHandle
	bf.WriteUint8(1)           // Unk0
	bf.WriteUint8(0)           // skipped byte
	bf.WriteBytes([]byte("quest_"))
	bf.WriteUint8(0) // null terminator
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgSysEnumerateStage{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0x11223344 {
		t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
	}
	if pkt.StagePrefix != "quest_" {
		t.Errorf("StagePrefix = %q, want %q", pkt.StagePrefix, "quest_")
	}
}

// TestParseLargeMsgSysReserveStage tests Parse for MsgSysReserveStage.
func TestParseLargeMsgSysReserveStage(t *testing.T) {
	stageID := "stage_42"
	stageBytes := make([]byte, len(stageID)+1) // padded with null at end
	copy(stageBytes, stageID)

	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0xAABBCCDD)            // AckHandle
	bf.WriteUint8(0x11)                   // Ready
	bf.WriteUint8(uint8(len(stageBytes))) // stageIDLength
	bf.WriteBytes(stageBytes)
	_, _ = bf.Seek(0, io.SeekStart)

	pkt := &MsgSysReserveStage{}
	if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
		t.Fatalf("Parse() error = %v", err)
	}

	if pkt.AckHandle != 0xAABBCCDD {
		t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
	}
	if pkt.Ready != 0x11 {
		t.Errorf("Ready = 0x%X, want 0x11", pkt.Ready)
	}
	if pkt.StageID != stageID {
		t.Errorf("StageID = %q, want %q", pkt.StageID, stageID)
	}
}
