package channelserver

import (
	"erupe-ce/common/byteframe"
	ps "erupe-ce/common/pascalstring"
	"time"

	"erupe-ce/network/mhfpacket"
	"go.uber.org/zap"
)

// GuildAlliance represents a multi-guild alliance.
type GuildAlliance struct {
	ID           uint32    `db:"id"`
	Name         string    `db:"name"`
	CreatedAt    time.Time `db:"created_at"`
	TotalMembers uint16
	Recruiting   bool `db:"recruiting"`

	ParentGuildID uint32 `db:"parent_id"`
	SubGuild1ID   uint32 `db:"sub1_id"`
	SubGuild2ID   uint32 `db:"sub2_id"`

	ParentGuild Guild
	SubGuild1   Guild
	SubGuild2   Guild
}

func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfCreateJoint)
	if err := s.server.guildRepo.CreateAlliance(pkt.Name, pkt.GuildID); err != nil {
		s.logger.Error("Failed to create guild alliance in db", zap.Error(err))
	}
	doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
}

func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfOperateJoint)

	guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
	if err != nil {
		s.logger.Error("Failed to get guild info", zap.Error(err))
	}
	if guild == nil {
		s.logger.Error("Guild not found for alliance operation", zap.Uint32("GuildID", pkt.GuildID))
		doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		return
	}
	alliance, err := s.server.guildRepo.GetAllianceByID(pkt.AllianceID)
	if err != nil {
		s.logger.Error("Failed to get alliance info", zap.Error(err))
	}
	if alliance == nil {
		s.logger.Error("Alliance not found for operation", zap.Uint32("AllianceID", pkt.AllianceID))
		doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		return
	}

	switch pkt.Action {
	case mhfpacket.OPERATE_JOINT_DISBAND:
		if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
			if err := s.server.guildRepo.DeleteAlliance(alliance.ID); err != nil {
				s.logger.Error("Failed to disband alliance", zap.Error(err))
			}
			doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
		} else {
			s.logger.Warn(
				"Non-owner of alliance attempted disband",
				zap.Uint32("CharID", s.charID),
				zap.Uint32("AllyID", alliance.ID),
			)
			doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		}
	case mhfpacket.OPERATE_JOINT_LEAVE:
		if guild.LeaderCharID == s.charID {
			if err := s.server.guildRepo.RemoveGuildFromAlliance(alliance.ID, guild.ID, alliance.SubGuild1ID, alliance.SubGuild2ID); err != nil {
				s.logger.Error("Failed to remove guild from alliance", zap.Error(err))
			}
			// NOTE: Alliance join requests are not yet implemented (no DB table exists),
			// so there are no pending applications to clean up on leave.
			doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
		} else {
			s.logger.Warn(
				"Non-owner of guild attempted alliance leave",
				zap.Uint32("CharID", s.charID),
			)
			doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		}
	case mhfpacket.OPERATE_JOINT_ALLOW:
		if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
			if err := s.server.guildRepo.SetAllianceRecruiting(alliance.ID, true); err != nil {
				s.logger.Error("Failed to allow alliance applications", zap.Error(err))
			}
			doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
		} else {
			doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		}
	case mhfpacket.OPERATE_JOINT_DENY:
		if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
			if err := s.server.guildRepo.SetAllianceRecruiting(alliance.ID, false); err != nil {
				s.logger.Error("Failed to deny alliance applications", zap.Error(err))
			}
			doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
		} else {
			doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		}
	case mhfpacket.OPERATE_JOINT_KICK:
		if alliance.ParentGuild.LeaderCharID == s.charID {
			kickedGuildID := pkt.Data1.ReadUint32()
			if err := s.server.guildRepo.RemoveGuildFromAlliance(alliance.ID, kickedGuildID, alliance.SubGuild1ID, alliance.SubGuild2ID); err != nil {
				s.logger.Error("Failed to kick guild from alliance", zap.Error(err))
			}
			doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
		} else {
			s.logger.Warn(
				"Non-owner of alliance attempted kick",
				zap.Uint32("CharID", s.charID),
				zap.Uint32("AllyID", alliance.ID),
			)
			doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		}
	default:
		s.logger.Error("unhandled operate joint action", zap.Uint8("action", uint8(pkt.Action)))
		doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
	}
}

func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfInfoJoint)
	bf := byteframe.NewByteFrame()
	alliance, err := s.server.guildRepo.GetAllianceByID(pkt.AllianceID)
	if err != nil || alliance == nil {
		doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
		return
	}
	bf.WriteUint32(alliance.ID)
	bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
	bf.WriteUint16(alliance.TotalMembers)
	bf.WriteUint16(0x0000) // Unk
	ps.Uint16(bf, alliance.Name, true)
	if alliance.SubGuild1ID > 0 {
		if alliance.SubGuild2ID > 0 {
			bf.WriteUint8(3)
		} else {
			bf.WriteUint8(2)
		}
	} else {
		bf.WriteUint8(1)
	}
	bf.WriteUint32(alliance.ParentGuildID)
	bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
	bf.WriteUint16(alliance.ParentGuild.Rank(s.server.erupeConfig.RealClientMode))
	bf.WriteUint16(alliance.ParentGuild.MemberCount)
	ps.Uint16(bf, alliance.ParentGuild.Name, true)
	ps.Uint16(bf, alliance.ParentGuild.LeaderName, true)
	if alliance.SubGuild1ID > 0 {
		bf.WriteUint32(alliance.SubGuild1ID)
		bf.WriteUint32(alliance.SubGuild1.LeaderCharID)
		bf.WriteUint16(alliance.SubGuild1.Rank(s.server.erupeConfig.RealClientMode))
		bf.WriteUint16(alliance.SubGuild1.MemberCount)
		ps.Uint16(bf, alliance.SubGuild1.Name, true)
		ps.Uint16(bf, alliance.SubGuild1.LeaderName, true)
	}
	if alliance.SubGuild2ID > 0 {
		bf.WriteUint32(alliance.SubGuild2ID)
		bf.WriteUint32(alliance.SubGuild2.LeaderCharID)
		bf.WriteUint16(alliance.SubGuild2.Rank(s.server.erupeConfig.RealClientMode))
		bf.WriteUint16(alliance.SubGuild2.MemberCount)
		ps.Uint16(bf, alliance.SubGuild2.Name, true)
		ps.Uint16(bf, alliance.SubGuild2.LeaderName, true)
	}
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
