package channelserver

import (
	"bytes"
	"encoding/binary"
	"testing"

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

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

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

	handleMsgMhfLoadLegendDispatch(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")
	}
}

// --- NEW TESTS ---

// buildCatBytes constructs a binary cat data payload suitable for GetAirouDetails.
func buildCatBytes(cats []Airou) []byte {
	buf := new(bytes.Buffer)
	// catCount
	buf.WriteByte(byte(len(cats)))
	for _, cat := range cats {
		catBuf := new(bytes.Buffer)
		// ID (uint32)
		_ = binary.Write(catBuf, binary.BigEndian, cat.ID)
		// 1 byte skip (unknown bool)
		catBuf.WriteByte(0)
		// Name (18 bytes)
		name := make([]byte, 18)
		copy(name, cat.Name)
		catBuf.Write(name)
		// Task (uint8)
		catBuf.WriteByte(cat.Task)
		// 16 bytes skip (appearance data)
		catBuf.Write(make([]byte, 16))
		// Personality (uint8)
		catBuf.WriteByte(cat.Personality)
		// Class (uint8)
		catBuf.WriteByte(cat.Class)
		// 5 bytes skip (affection and colour sliders)
		catBuf.Write(make([]byte, 5))
		// Experience (uint32)
		_ = binary.Write(catBuf, binary.BigEndian, cat.Experience)
		// 1 byte skip (bool for weapon equipped)
		catBuf.WriteByte(0)
		// WeaponType (uint8)
		catBuf.WriteByte(cat.WeaponType)
		// WeaponID (uint16)
		_ = binary.Write(catBuf, binary.BigEndian, cat.WeaponID)

		catData := catBuf.Bytes()
		// catDefLen (uint32) - total length of the cat data after this field
		_ = binary.Write(buf, binary.BigEndian, uint32(len(catData)))
		buf.Write(catData)
	}
	return buf.Bytes()
}

func TestGetAirouDetails_Empty(t *testing.T) {
	// Zero cats
	data := []byte{0x00}
	bf := byteframe.NewByteFrameFromBytes(data)
	cats := GetAirouDetails(bf)

	if len(cats) != 0 {
		t.Errorf("Expected 0 cats, got %d", len(cats))
	}
}

func TestGetAirouDetails_SingleCat(t *testing.T) {
	input := Airou{
		ID:          42,
		Name:        []byte("TestCat"),
		Task:        4,
		Personality: 3,
		Class:       2,
		Experience:  1500,
		WeaponType:  6,
		WeaponID:    100,
	}

	data := buildCatBytes([]Airou{input})
	bf := byteframe.NewByteFrameFromBytes(data)
	cats := GetAirouDetails(bf)

	if len(cats) != 1 {
		t.Fatalf("Expected 1 cat, got %d", len(cats))
	}

	cat := cats[0]
	if cat.ID != 42 {
		t.Errorf("ID = %d, want 42", cat.ID)
	}
	if cat.Task != 4 {
		t.Errorf("Task = %d, want 4", cat.Task)
	}
	if cat.Personality != 3 {
		t.Errorf("Personality = %d, want 3", cat.Personality)
	}
	if cat.Class != 2 {
		t.Errorf("Class = %d, want 2", cat.Class)
	}
	if cat.Experience != 1500 {
		t.Errorf("Experience = %d, want 1500", cat.Experience)
	}
	if cat.WeaponType != 6 {
		t.Errorf("WeaponType = %d, want 6", cat.WeaponType)
	}
	if cat.WeaponID != 100 {
		t.Errorf("WeaponID = %d, want 100", cat.WeaponID)
	}
	// Name should be 18 bytes (padded with nulls)
	if len(cat.Name) != 18 {
		t.Errorf("Name length = %d, want 18", len(cat.Name))
	}
	// First bytes should match "TestCat"
	if !bytes.HasPrefix(cat.Name, []byte("TestCat")) {
		t.Errorf("Name does not start with 'TestCat', got %v", cat.Name)
	}
}

func TestGetAirouDetails_MultipleCats(t *testing.T) {
	inputs := []Airou{
		{ID: 1, Name: []byte("Alpha"), Task: 1, Personality: 0, Class: 0, Experience: 100, WeaponType: 6, WeaponID: 10},
		{ID: 2, Name: []byte("Beta"), Task: 2, Personality: 1, Class: 1, Experience: 200, WeaponType: 6, WeaponID: 20},
		{ID: 3, Name: []byte("Gamma"), Task: 4, Personality: 2, Class: 2, Experience: 300, WeaponType: 6, WeaponID: 30},
	}

	data := buildCatBytes(inputs)
	bf := byteframe.NewByteFrameFromBytes(data)
	cats := GetAirouDetails(bf)

	if len(cats) != 3 {
		t.Fatalf("Expected 3 cats, got %d", len(cats))
	}

	for i, cat := range cats {
		if cat.ID != inputs[i].ID {
			t.Errorf("Cat %d: CatID = %d, want %d", i, cat.ID, inputs[i].ID)
		}
		if cat.Task != inputs[i].Task {
			t.Errorf("Cat %d: CurrentTask = %d, want %d", i, cat.Task, inputs[i].Task)
		}
		if cat.Experience != inputs[i].Experience {
			t.Errorf("Cat %d: Experience = %d, want %d", i, cat.Experience, inputs[i].Experience)
		}
		if cat.WeaponID != inputs[i].WeaponID {
			t.Errorf("Cat %d: WeaponID = %d, want %d", i, cat.WeaponID, inputs[i].WeaponID)
		}
	}
}

func TestGetAirouDetails_ExtraTrailingBytes(t *testing.T) {
	// The GetAirouDetails function handles extra bytes by seeking to catStart+catDefLen.
	// Simulate a cat definition with extra trailing bytes by increasing catDefLen.
	buf := new(bytes.Buffer)
	buf.WriteByte(1) // catCount = 1

	catBuf := new(bytes.Buffer)
	_ = binary.Write(catBuf, binary.BigEndian, uint32(99))  // catID
	catBuf.WriteByte(0)                                     // skip
	catBuf.Write(make([]byte, 18))                          // name
	catBuf.WriteByte(3)                                     // currentTask
	catBuf.Write(make([]byte, 16))                          // appearance skip
	catBuf.WriteByte(1)                                     // personality
	catBuf.WriteByte(2)                                     // class
	catBuf.Write(make([]byte, 5))                           // affection skip
	_ = binary.Write(catBuf, binary.BigEndian, uint32(500)) // experience
	catBuf.WriteByte(0)                                     // weapon equipped bool
	catBuf.WriteByte(6)                                     // weaponType
	_ = binary.Write(catBuf, binary.BigEndian, uint16(50))  // weaponID

	catData := catBuf.Bytes()
	// Add 10 extra trailing bytes
	extra := make([]byte, 10)
	catDataWithExtra := append(catData, extra...)

	_ = binary.Write(buf, binary.BigEndian, uint32(len(catDataWithExtra)))
	buf.Write(catDataWithExtra)

	bf := byteframe.NewByteFrameFromBytes(buf.Bytes())
	cats := GetAirouDetails(bf)

	if len(cats) != 1 {
		t.Fatalf("Expected 1 cat, got %d", len(cats))
	}
	if cats[0].ID != 99 {
		t.Errorf("ID = %d, want 99", cats[0].ID)
	}
	if cats[0].Experience != 500 {
		t.Errorf("Experience = %d, want 500", cats[0].Experience)
	}
}

func TestGetAirouDetails_CatNamePadding(t *testing.T) {
	// Verify that names shorter than 18 bytes are correctly padded with null bytes.
	input := Airou{
		ID:   1,
		Name: []byte("Hi"),
	}

	data := buildCatBytes([]Airou{input})
	bf := byteframe.NewByteFrameFromBytes(data)
	cats := GetAirouDetails(bf)

	if len(cats) != 1 {
		t.Fatalf("Expected 1 cat, got %d", len(cats))
	}
	if len(cats[0].Name) != 18 {
		t.Errorf("Name length = %d, want 18", len(cats[0].Name))
	}
	// "Hi" followed by null bytes
	if cats[0].Name[0] != 'H' || cats[0].Name[1] != 'i' {
		t.Errorf("Name first bytes = %v, want 'Hi...'", cats[0].Name[:2])
	}
}

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

	pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
		AckHandle:   12345,
		RequestType: 1,
	}

	handleMsgMhfMercenaryHuntdata(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")
	}
}

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

	pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
		AckHandle:   12345,
		RequestType: 0,
	}

	handleMsgMhfMercenaryHuntdata(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")
	}
}

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

	pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
		AckHandle:   12345,
		RequestType: 2,
	}

	handleMsgMhfMercenaryHuntdata(session, pkt)

	// RequestType=2 takes the else branch (same as 0)
	select {
	case p := <-session.sendPackets:
		if len(p.data) == 0 {
			t.Error("Response packet should have data")
		}
	default:
		t.Error("No response packet queued")
	}
}

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

	pkt := &mhfpacket.MsgMhfLoadLegendDispatch{
		AckHandle: 22222,
	}

	handleMsgMhfLoadLegendDispatch(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")
	}
}

func TestAirouStruct(t *testing.T) {
	cat := Airou{
		ID:          42,
		Name:        []byte("TestCat"),
		Task:        4,
		Personality: 2,
		Class:       1,
		Experience:  1500,
		WeaponType:  6,
		WeaponID:    100,
	}

	if cat.ID != 42 {
		t.Errorf("ID = %d, want 42", cat.ID)
	}
	if cat.Task != 4 {
		t.Errorf("Task = %d, want 4", cat.Task)
	}
	if cat.Experience != 1500 {
		t.Errorf("Experience = %d, want 1500", cat.Experience)
	}
	if cat.WeaponType != 6 {
		t.Errorf("WeaponType = %d, want 6", cat.WeaponType)
	}
	if cat.WeaponID != 100 {
		t.Errorf("WeaponID = %d, want 100", cat.WeaponID)
	}
}

// TestHandleMsgMhfMercenaryHuntdata_Unk0_1 tests with Unk0=1 (returns 1 byte)
func TestHandleMsgMhfMercenaryHuntdata_Unk0_1(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
		AckHandle:   12345,
		RequestType: 1,
	}

	handleMsgMhfMercenaryHuntdata(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")
	}
}

// TestHandleMsgMhfMercenaryHuntdata_Unk0_0 tests with Unk0=0 (returns 0 bytes payload)
func TestHandleMsgMhfMercenaryHuntdata_Unk0_0(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
		AckHandle:   12345,
		RequestType: 0,
	}

	handleMsgMhfMercenaryHuntdata(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")
	}
}

// TestHandleMsgMhfEnumerateMercenaryLog tests the mercenary log enumeration handler
func TestHandleMsgMhfEnumerateMercenaryLog(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

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

	handleMsgMhfEnumerateMercenaryLog(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")
	}
}

func TestHandleMsgMhfLoadPartner(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfLoadPartner{AckHandle: 1}
	handleMsgMhfLoadPartner(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfSavePartner(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfSavePartner{AckHandle: 1, RawDataPayload: []byte{1, 2, 3, 4}}
	handleMsgMhfSavePartner(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfLoadHunterNavi_G8(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	server.erupeConfig.RealClientMode = cfg.G10
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfLoadHunterNavi{AckHandle: 1}
	handleMsgMhfLoadHunterNavi(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfLoadHunterNavi_G7(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	server.erupeConfig.RealClientMode = cfg.G7
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfLoadHunterNavi{AckHandle: 1}
	handleMsgMhfLoadHunterNavi(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfSaveHunterNavi_NoDiff(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	data := make([]byte, 100)
	pkt := &mhfpacket.MsgMhfSaveHunterNavi{
		AckHandle:      1,
		IsDataDiff:     false,
		RawDataPayload: data,
	}
	handleMsgMhfSaveHunterNavi(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfSaveHunterNavi_Diff(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	charRepo.columns["hunternavi"] = make([]byte, 552)
	server.charRepo = charRepo
	server.erupeConfig.RealClientMode = cfg.G10
	session := createMockSession(100, server)

	// Create a valid diff payload (deltacomp format: pairs of offset+data)
	// A simple diff: zero length means no changes
	diffData := make([]byte, 4) // minimal diff
	pkt := &mhfpacket.MsgMhfSaveHunterNavi{
		AckHandle:      1,
		IsDataDiff:     true,
		RawDataPayload: diffData,
	}
	handleMsgMhfSaveHunterNavi(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfSaveHunterNavi_OversizedPayload(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	data := make([]byte, 5000) // > 4096
	pkt := &mhfpacket.MsgMhfSaveHunterNavi{
		AckHandle:      1,
		IsDataDiff:     false,
		RawDataPayload: data,
	}
	handleMsgMhfSaveHunterNavi(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfCreateMercenary_Success(t *testing.T) {
	server := createMockServer()
	server.mercenaryRepo = &mockMercenaryRepo{nextRastaID: 42}
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfCreateMercenary{AckHandle: 1}
	handleMsgMhfCreateMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfCreateMercenary_Error(t *testing.T) {
	server := createMockServer()
	server.mercenaryRepo = &mockMercenaryRepo{rastaIDErr: errNotFound}
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfCreateMercenary{AckHandle: 1}
	handleMsgMhfCreateMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfSaveMercenary_Normal(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	mercData := make([]byte, 100)
	// Write a uint32 index at the start
	mercData[0] = 0
	mercData[1] = 0
	mercData[2] = 0
	mercData[3] = 1
	pkt := &mhfpacket.MsgMhfSaveMercenary{
		AckHandle:  1,
		GCP:        500,
		PactMercID: 10,
		MercData:   mercData,
	}
	handleMsgMhfSaveMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfSaveMercenary_Oversized(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfSaveMercenary{
		AckHandle: 1,
		MercData:  make([]byte, 70000),
	}
	handleMsgMhfSaveMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfReadMercenaryM_EmptyData(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfReadMercenaryM{AckHandle: 1, CharID: 200}
	handleMsgMhfReadMercenaryM(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfReadMercenaryM_WithData(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	charRepo.columns["savemercenary"] = []byte{0x01, 0x02, 0x03, 0x04}
	server.charRepo = charRepo
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfReadMercenaryM{AckHandle: 1, CharID: 100}
	handleMsgMhfReadMercenaryM(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfContractMercenary_Op0(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfContractMercenary{AckHandle: 1, Op: 0, CID: 200, PactMercID: 42}
	handleMsgMhfContractMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfContractMercenary_Op1(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfContractMercenary{AckHandle: 1, Op: 1}
	handleMsgMhfContractMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfContractMercenary_Op2(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfContractMercenary{AckHandle: 1, Op: 2, CID: 200}
	handleMsgMhfContractMercenary(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfReadMercenaryW_NoPact(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo
	server.mercenaryRepo = &mockMercenaryRepo{}
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfReadMercenaryW{AckHandle: 1, Op: 0}
	handleMsgMhfReadMercenaryW(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfReadMercenaryW_WithPact(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	charRepo.ints["pact_id"] = 42
	server.charRepo = charRepo
	server.mercenaryRepo = &mockMercenaryRepo{}
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfReadMercenaryW{AckHandle: 1, Op: 0}
	handleMsgMhfReadMercenaryW(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfReadMercenaryW_Op2(t *testing.T) {
	server := createMockServer()
	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo
	server.mercenaryRepo = &mockMercenaryRepo{}
	session := createMockSession(100, server)

	// Op 2 skips loan enumeration
	pkt := &mhfpacket.MsgMhfReadMercenaryW{AckHandle: 1, Op: 2}
	handleMsgMhfReadMercenaryW(session, pkt)
	<-session.sendPackets
}

func TestHandleMsgMhfLoadOtomoAirou(t *testing.T) {
	server := createMockServer()
	server.charRepo = newMockCharacterRepo()
	session := createMockSession(100, server)

	pkt := &mhfpacket.MsgMhfLoadOtomoAirou{AckHandle: 1}
	handleMsgMhfLoadOtomoAirou(session, pkt)
	<-session.sendPackets
}
