package channelserver

import (
	"encoding/binary"
	"errors"
	"testing"

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

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

	pkt := &mhfpacket.MsgSysTerminalLog{
		AckHandle: 100,
		LogID:     5,
		Entries: []mhfpacket.TerminalLogEntry{
			{Type1: 1, Type2: 2, Unk0: 3, Unk1: 4, Unk2: 5, Unk3: 6},
		},
	}
	handleMsgSysTerminalLog(session, pkt)

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

func TestHandleMsgSysLogin_Success(t *testing.T) {
	server := createMockServer()
	server.erupeConfig.DebugOptions.DisableTokenCheck = true
	server.userBinary = NewUserBinaryStore()

	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	sessionRepo := &mockSessionRepo{}
	server.sessionRepo = sessionRepo

	userRepo := &mockUserRepoGacha{}
	server.userRepo = userRepo

	session := createMockSession(0, server)

	pkt := &mhfpacket.MsgSysLogin{
		AckHandle:        100,
		CharID0:          42,
		LoginTokenString: "test-token",
	}
	handleMsgSysLogin(session, pkt)

	if session.charID != 42 {
		t.Errorf("Expected charID 42, got %d", session.charID)
	}
	if session.token != "test-token" {
		t.Errorf("Expected token 'test-token', got %q", session.token)
	}
	if sessionRepo.boundToken != "test-token" {
		t.Errorf("Expected BindSession called with 'test-token', got %q", sessionRepo.boundToken)
	}

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

func TestHandleMsgSysLogin_GetUserIDError(t *testing.T) {
	server := createMockServer()
	server.erupeConfig.DebugOptions.DisableTokenCheck = true

	charRepo := newMockCharacterRepo()
	server.charRepo = &mockCharRepoGetUserIDErr{
		mockCharacterRepo: charRepo,
		getUserIDErr:      errors.New("user not found"),
	}

	sessionRepo := &mockSessionRepo{}
	server.sessionRepo = sessionRepo

	userRepo := &mockUserRepoGacha{}
	server.userRepo = userRepo

	session := createMockSession(0, server)

	pkt := &mhfpacket.MsgSysLogin{
		AckHandle:        100,
		CharID0:          42,
		LoginTokenString: "test-token",
	}
	handleMsgSysLogin(session, pkt)

	select {
	case <-session.sendPackets:
		// got a response (fail ACK)
	default:
		t.Error("No response packet queued on GetUserID error")
	}
}

func TestHandleMsgSysLogin_BindSessionError(t *testing.T) {
	server := createMockServer()
	server.erupeConfig.DebugOptions.DisableTokenCheck = true

	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	sessionRepo := &mockSessionRepo{bindErr: errors.New("bind failed")}
	server.sessionRepo = sessionRepo

	userRepo := &mockUserRepoGacha{}
	server.userRepo = userRepo

	session := createMockSession(0, server)

	pkt := &mhfpacket.MsgSysLogin{
		AckHandle:        100,
		CharID0:          42,
		LoginTokenString: "test-token",
	}
	handleMsgSysLogin(session, pkt)

	select {
	case <-session.sendPackets:
		// got a response (fail ACK)
	default:
		t.Error("No response packet queued on BindSession error")
	}
}

func TestHandleMsgSysLogin_SetLastCharacterError(t *testing.T) {
	server := createMockServer()
	server.erupeConfig.DebugOptions.DisableTokenCheck = true

	charRepo := newMockCharacterRepo()
	server.charRepo = charRepo

	sessionRepo := &mockSessionRepo{}
	server.sessionRepo = sessionRepo

	userRepo := &mockUserRepoGacha{setLastCharErr: errors.New("set failed")}
	server.userRepo = userRepo

	session := createMockSession(0, server)

	pkt := &mhfpacket.MsgSysLogin{
		AckHandle:        100,
		CharID0:          42,
		LoginTokenString: "test-token",
	}
	handleMsgSysLogin(session, pkt)

	select {
	case <-session.sendPackets:
		// got a response (fail ACK)
	default:
		t.Error("No response packet queued on SetLastCharacter error")
	}
}

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

	pkt := &mhfpacket.MsgSysPing{AckHandle: 100}
	handleMsgSysPing(session, pkt)

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

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

	pkt := &mhfpacket.MsgSysIssueLogkey{AckHandle: 100}
	handleMsgSysIssueLogkey(session, pkt)

	if len(session.logKey) != 16 {
		t.Errorf("Expected 16-byte log key, got %d bytes", len(session.logKey))
	}

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

func TestHandleMsgSysRecordLog_ZZMode(t *testing.T) {
	server := createMockServer()
	server.erupeConfig.RealClientMode = cfg.ZZ
	server.userBinary = NewUserBinaryStore()

	guildRepo := &mockGuildRepo{}
	server.guildRepo = guildRepo

	session := createMockSession(1, server)

	// Create a stage for the session (handler accesses s.stage.reservedClientSlots)
	stage := &Stage{
		id:                  "testStage",
		clients:             make(map[*Session]uint32),
		reservedClientSlots: make(map[uint32]bool),
	}
	stage.reservedClientSlots[1] = true
	session.stage = stage

	// Build kill log data: 32 header bytes + 176 monster bytes
	data := make([]byte, 32+176)
	// Set monster index 5 to have 2 kills (a large monster per mhfmon)
	data[32+5] = 2

	pkt := &mhfpacket.MsgSysRecordLog{
		AckHandle: 100,
		Data:      data,
	}
	handleMsgSysRecordLog(session, pkt)

	// Check that reserved slot was cleaned up
	if _, exists := stage.reservedClientSlots[1]; exists {
		t.Error("Expected reserved client slot to be removed")
	}

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

func TestHandleMsgSysLockGlobalSema_LocalChannel(t *testing.T) {
	server := createMockServer()
	server.GlobalID = "ch1"
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgSysLockGlobalSema{
		AckHandle:             100,
		UserIDString:          "someStage",
		ServerChannelIDString: "ch1",
	}
	handleMsgSysLockGlobalSema(session, pkt)

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

func TestHandleMsgSysLockGlobalSema_RemoteMatch(t *testing.T) {
	server := createMockServer()
	server.GlobalID = "ch1"

	otherChannel := createMockServer()
	otherChannel.GlobalID = "ch2"
	otherChannel.stages.Store("prefix_testStage", &Stage{
		id:                  "prefix_testStage",
		clients:             make(map[*Session]uint32),
		reservedClientSlots: make(map[uint32]bool),
	})
	server.Registry = NewLocalChannelRegistry([]*Server{server, otherChannel})

	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgSysLockGlobalSema{
		AckHandle:             100,
		UserIDString:          "testStage",
		ServerChannelIDString: "ch1",
	}
	handleMsgSysLockGlobalSema(session, pkt)

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

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

	pkt := &mhfpacket.MsgSysUnlockGlobalSema{AckHandle: 100}
	handleMsgSysUnlockGlobalSema(session, pkt)

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

func TestHandleMsgSysRightsReload_Session(t *testing.T) {
	server := createMockServer()
	userRepo := &mockUserRepoGacha{rights: 0x02}
	server.userRepo = userRepo

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

	pkt := &mhfpacket.MsgSysRightsReload{AckHandle: 100}
	handleMsgSysRightsReload(session, pkt)

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

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

	dataBf := byteframe.NewByteFrame()
	dataBf.WriteUint8(2) // type = berserk

	pkt := &mhfpacket.MsgMhfAnnounce{
		AckHandle: 100,
		IPAddress: binary.LittleEndian.Uint32([]byte{127, 0, 0, 1}),
		Port:      54001,
		StageID:   make([]byte, 32),
		Data:      byteframe.NewByteFrameFromBytes(dataBf.Data()),
	}
	handleMsgMhfAnnounce(session, pkt)

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

// mockCharRepoGetUserIDErr wraps mockCharacterRepo to return an error from GetUserID
type mockCharRepoGetUserIDErr struct {
	*mockCharacterRepo
	getUserIDErr error
}

func (m *mockCharRepoGetUserIDErr) GetUserID(_ uint32) (uint32, error) {
	return 0, m.getUserIDErr
}
