package channelserver

import (
	"testing"
	"time"

	"erupe-ce/network/mhfpacket"
)

func TestMailStruct(t *testing.T) {
	mail := Mail{
		ID:                   123,
		SenderID:             1000,
		RecipientID:          2000,
		Subject:              "Test Subject",
		Body:                 "Test Body Content",
		Read:                 false,
		Deleted:              false,
		Locked:               true,
		AttachedItemReceived: false,
		AttachedItemID:       500,
		AttachedItemAmount:   10,
		CreatedAt:            time.Now(),
		IsGuildInvite:        false,
		IsSystemMessage:      true,
		SenderName:           "TestSender",
	}

	if mail.ID != 123 {
		t.Errorf("ID = %d, want 123", mail.ID)
	}
	if mail.SenderID != 1000 {
		t.Errorf("SenderID = %d, want 1000", mail.SenderID)
	}
	if mail.RecipientID != 2000 {
		t.Errorf("RecipientID = %d, want 2000", mail.RecipientID)
	}
	if mail.Subject != "Test Subject" {
		t.Errorf("Subject = %s, want 'Test Subject'", mail.Subject)
	}
	if mail.Body != "Test Body Content" {
		t.Errorf("Body = %s, want 'Test Body Content'", mail.Body)
	}
	if mail.Read {
		t.Error("Read should be false")
	}
	if mail.Deleted {
		t.Error("Deleted should be false")
	}
	if !mail.Locked {
		t.Error("Locked should be true")
	}
	if mail.AttachedItemReceived {
		t.Error("AttachedItemReceived should be false")
	}
	if mail.AttachedItemID != 500 {
		t.Errorf("AttachedItemID = %d, want 500", mail.AttachedItemID)
	}
	if mail.AttachedItemAmount != 10 {
		t.Errorf("AttachedItemAmount = %d, want 10", mail.AttachedItemAmount)
	}
	if mail.IsGuildInvite {
		t.Error("IsGuildInvite should be false")
	}
	if !mail.IsSystemMessage {
		t.Error("IsSystemMessage should be true")
	}
	if mail.SenderName != "TestSender" {
		t.Errorf("SenderName = %s, want 'TestSender'", mail.SenderName)
	}
}

func TestMailStruct_DefaultValues(t *testing.T) {
	mail := Mail{}

	if mail.ID != 0 {
		t.Errorf("Default ID should be 0, got %d", mail.ID)
	}
	if mail.Subject != "" {
		t.Errorf("Default Subject should be empty, got %s", mail.Subject)
	}
	if mail.Read {
		t.Error("Default Read should be false")
	}
}

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

func TestHandleMsgMhfListMail_Empty(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{mails: []Mail{}}
	server.mailRepo = mock
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfListMail{AckHandle: 100}

	handleMsgMhfListMail(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 TestHandleMsgMhfListMail_WithMails(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{
		mails: []Mail{
			{ID: 10, SenderID: 100, Subject: "Hello", SenderName: "Sender1", CreatedAt: time.Now()},
			{ID: 20, SenderID: 200, Subject: "World", SenderName: "Sender2", CreatedAt: time.Now(), Locked: true},
		},
	}
	server.mailRepo = mock
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfListMail{AckHandle: 100}

	handleMsgMhfListMail(session, pkt)

	// Verify mailList was populated
	if session.mailList == nil {
		t.Fatal("mailList should be initialized")
	}
	if session.mailList[0] != 10 {
		t.Errorf("mailList[0] = %d, want 10", session.mailList[0])
	}
	if session.mailList[1] != 20 {
		t.Errorf("mailList[1] = %d, want 20", session.mailList[1])
	}
	if session.mailAccIndex != 2 {
		t.Errorf("mailAccIndex = %d, want 2", session.mailAccIndex)
	}

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

func TestHandleMsgMhfListMail_DBError(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{listErr: errNotFound}
	server.mailRepo = mock
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfListMail{AckHandle: 100}

	handleMsgMhfListMail(session, pkt)

	select {
	case p := <-session.sendPackets:
		// Should return a fallback response with single zero byte
		if len(p.data) == 0 {
			t.Error("Should have fallback response data")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfReadMail_Success(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{
		mailByID: map[int]*Mail{
			42: {ID: 42, Body: "Test body content"},
		},
	}
	server.mailRepo = mock
	session := createMockSession(1, server)
	session.mailList = make([]int, 256)
	session.mailList[0] = 42

	pkt := &mhfpacket.MsgMhfReadMail{
		AckHandle: 100,
		AccIndex:  0,
	}

	handleMsgMhfReadMail(session, pkt)

	if mock.markReadCalled != 42 {
		t.Errorf("MarkRead called with %d, want 42", mock.markReadCalled)
	}

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

func TestHandleMsgMhfReadMail_OutOfBounds(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{}
	server.mailRepo = mock
	session := createMockSession(1, server)
	// mailList is nil, so any AccIndex is out of bounds

	pkt := &mhfpacket.MsgMhfReadMail{
		AckHandle: 100,
		AccIndex:  5,
	}

	handleMsgMhfReadMail(session, pkt)

	select {
	case p := <-session.sendPackets:
		// Should get fallback single-byte response
		if len(p.data) == 0 {
			t.Error("Should have fallback response")
		}
	default:
		t.Error("No response packet queued")
	}
}

func TestHandleMsgMhfReadMail_ZeroMailID(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{}
	server.mailRepo = mock
	session := createMockSession(1, server)
	session.mailList = make([]int, 256)
	// mailList[0] is 0 (default)

	pkt := &mhfpacket.MsgMhfReadMail{
		AckHandle: 100,
		AccIndex:  0,
	}

	handleMsgMhfReadMail(session, pkt)

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

func TestHandleMsgMhfOprtMail_Delete(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{
		mailByID: map[int]*Mail{
			42: {ID: 42},
		},
	}
	server.mailRepo = mock
	session := createMockSession(1, server)
	session.mailList = make([]int, 256)
	session.mailList[0] = 42

	pkt := &mhfpacket.MsgMhfOprtMail{
		AckHandle: 100,
		AccIndex:  0,
		Operation: mhfpacket.OperateMailDelete,
	}

	handleMsgMhfOprtMail(session, pkt)

	if mock.markDeletedID != 42 {
		t.Errorf("MarkDeleted called with %d, want 42", mock.markDeletedID)
	}

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

func TestHandleMsgMhfOprtMail_Lock(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{
		mailByID: map[int]*Mail{
			42: {ID: 42},
		},
	}
	server.mailRepo = mock
	session := createMockSession(1, server)
	session.mailList = make([]int, 256)
	session.mailList[0] = 42

	pkt := &mhfpacket.MsgMhfOprtMail{
		AckHandle: 100,
		AccIndex:  0,
		Operation: mhfpacket.OperateMailLock,
	}

	handleMsgMhfOprtMail(session, pkt)

	if mock.lockID != 42 || !mock.lockValue {
		t.Errorf("SetLocked called with ID=%d locked=%v, want ID=42 locked=true", mock.lockID, mock.lockValue)
	}
}

func TestHandleMsgMhfOprtMail_Unlock(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{
		mailByID: map[int]*Mail{
			42: {ID: 42},
		},
	}
	server.mailRepo = mock
	session := createMockSession(1, server)
	session.mailList = make([]int, 256)
	session.mailList[0] = 42

	pkt := &mhfpacket.MsgMhfOprtMail{
		AckHandle: 100,
		AccIndex:  0,
		Operation: mhfpacket.OperateMailUnlock,
	}

	handleMsgMhfOprtMail(session, pkt)

	if mock.lockID != 42 || mock.lockValue {
		t.Errorf("SetLocked called with ID=%d locked=%v, want ID=42 locked=false", mock.lockID, mock.lockValue)
	}
}

func TestHandleMsgMhfOprtMail_AcquireItem(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{
		mailByID: map[int]*Mail{
			42: {ID: 42, AttachedItemID: 100, AttachedItemAmount: 5},
		},
	}
	server.mailRepo = mock
	session := createMockSession(1, server)
	session.mailList = make([]int, 256)
	session.mailList[0] = 42

	pkt := &mhfpacket.MsgMhfOprtMail{
		AckHandle: 100,
		AccIndex:  0,
		Operation: mhfpacket.OperateMailAcquireItem,
	}

	handleMsgMhfOprtMail(session, pkt)

	if mock.itemReceivedID != 42 {
		t.Errorf("MarkItemReceived called with %d, want 42", mock.itemReceivedID)
	}
}

func TestHandleMsgMhfOprtMail_OutOfBounds(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{}
	server.mailRepo = mock
	session := createMockSession(1, server)
	// No mailList set

	pkt := &mhfpacket.MsgMhfOprtMail{
		AckHandle: 100,
		AccIndex:  5,
		Operation: mhfpacket.OperateMailDelete,
	}

	handleMsgMhfOprtMail(session, pkt)

	// Should not have called any repo methods
	if mock.markDeletedID != 0 {
		t.Error("Should not have called MarkDeleted for out-of-bounds access")
	}

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

func TestHandleMsgMhfSendMail_Direct(t *testing.T) {
	server := createMockServer()
	mock := &mockMailRepo{}
	server.mailRepo = mock
	ensureMailService(server)
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfSendMail{
		AckHandle:   100,
		RecipientID: 42,
		Subject:     "Hello",
		Body:        "World",
		ItemID:      500,
		Quantity:    3,
	}

	handleMsgMhfSendMail(session, pkt)

	if len(mock.sentMails) != 1 {
		t.Fatalf("Expected 1 sent mail, got %d", len(mock.sentMails))
	}
	sent := mock.sentMails[0]
	if sent.senderID != 1 {
		t.Errorf("SenderID = %d, want 1", sent.senderID)
	}
	if sent.recipientID != 42 {
		t.Errorf("RecipientID = %d, want 42", sent.recipientID)
	}
	if sent.subject != "Hello" {
		t.Errorf("Subject = %s, want Hello", sent.subject)
	}
	if sent.itemID != 500 {
		t.Errorf("ItemID = %d, want 500", sent.itemID)
	}

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

func TestHandleMsgMhfSendMail_Guild(t *testing.T) {
	server := createMockServer()
	mailMock := &mockMailRepo{}
	guildMock := &mockGuildRepo{
		guild: &Guild{ID: 10},
		members: []*GuildMember{
			{CharID: 100},
			{CharID: 200},
			{CharID: 300},
		},
	}
	server.mailRepo = mailMock
	server.guildRepo = guildMock
	ensureMailService(server)
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfSendMail{
		AckHandle:   100,
		RecipientID: 0, // 0 = guild mail
		Subject:     "Guild News",
		Body:        "Important update",
	}

	handleMsgMhfSendMail(session, pkt)

	if len(mailMock.sentMails) != 3 {
		t.Fatalf("Expected 3 sent mails (one per guild member), got %d", len(mailMock.sentMails))
	}
	for i, sent := range mailMock.sentMails {
		if sent.senderID != 1 {
			t.Errorf("Mail %d: SenderID = %d, want 1", i, sent.senderID)
		}
	}
	recipients := map[uint32]bool{}
	for _, sent := range mailMock.sentMails {
		recipients[sent.recipientID] = true
	}
	if !recipients[100] || !recipients[200] || !recipients[300] {
		t.Errorf("Expected recipients 100, 200, 300, got %v", recipients)
	}
}

func TestHandleMsgMhfSendMail_GuildNotFound(t *testing.T) {
	server := createMockServer()
	mailMock := &mockMailRepo{}
	guildMock := &mockGuildRepo{getErr: errNotFound}
	server.mailRepo = mailMock
	server.guildRepo = guildMock
	ensureMailService(server)
	session := createMockSession(1, server)

	pkt := &mhfpacket.MsgMhfSendMail{
		AckHandle:   100,
		RecipientID: 0, // Guild mail
		Subject:     "Guild News",
		Body:        "Update",
	}

	handleMsgMhfSendMail(session, pkt)

	if len(mailMock.sentMails) != 0 {
		t.Errorf("No mails should be sent when guild not found, got %d", len(mailMock.sentMails))
	}

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