package channelserver

import (
	"sort"
	"strings"

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

// Guild sentinel and cost constants
const (
	guildNotJoinedSentinel = uint32(0xFFFFFFFF)
	guildRoomMaxRP         = uint32(55000)
)

func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfInfoGuild)

	var guild *Guild
	var err error

	if pkt.GuildID > 0 {
		guild, err = s.server.guildRepo.GetByID(pkt.GuildID)
	} else {
		guild, err = s.server.guildRepo.GetByCharID(s.charID)
	}

	if err == nil && guild != nil {
		s.prevGuildID = guild.ID

		guildName := stringsupport.UTF8ToSJIS(guild.Name)
		guildComment := stringsupport.UTF8ToSJIS(guild.Comment)
		guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName)

		characterGuildData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
		characterJoinedAt := guildNotJoinedSentinel

		if characterGuildData != nil && characterGuildData.JoinedAt != nil {
			characterJoinedAt = uint32(characterGuildData.JoinedAt.Unix())
		}

		if err != nil {
			resp := byteframe.NewByteFrame()
			resp.WriteUint32(0) // Count
			resp.WriteUint8(0)  // Unk, read if count == 0.

			doAckBufSucceed(s, pkt.AckHandle, resp.Data())
			return
		}

		bf := byteframe.NewByteFrame()

		bf.WriteUint32(guild.ID)
		bf.WriteUint32(guild.LeaderCharID)
		bf.WriteUint16(guild.Rank(s.server.erupeConfig.RealClientMode))
		bf.WriteUint16(guild.MemberCount)

		bf.WriteUint8(guild.MainMotto)
		bf.WriteUint8(guild.SubMotto)

		// Unk appears to be static
		bf.WriteUint8(0)
		bf.WriteUint8(0)
		bf.WriteUint8(0)
		bf.WriteUint8(0)
		bf.WriteUint8(0)
		bf.WriteUint8(0)

		flags := uint8(0)
		if !guild.Recruiting {
			flags |= 0x01
		}
		//if guild.Suspended {
		//	flags |= 0x02
		//}
		bf.WriteUint8(flags)

		if characterGuildData == nil || characterGuildData.IsApplicant {
			bf.WriteUint16(0)
		} else if guild.LeaderCharID == s.charID {
			bf.WriteUint16(1)
		} else {
			bf.WriteUint16(2)
		}

		bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
		bf.WriteUint32(characterJoinedAt)
		bf.WriteUint8(uint8(len(guildName)))
		bf.WriteUint8(uint8(len(guildComment)))
		bf.WriteUint8(uint8(5)) // Length of unknown string below
		bf.WriteUint8(uint8(len(guildLeaderName)))
		bf.WriteBytes(guildName)
		bf.WriteBytes(guildComment)
		bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor]))
		bf.WriteUint32(guild.RankRP)
		bf.WriteBytes(guildLeaderName)
		bf.WriteUint32(0)                  // Unk
		bf.WriteBool(guild.ReturnType > 0) // isReturnGuild
		bf.WriteBool(false)                // earnedSpecialHall
		bf.WriteUint8(2)
		bf.WriteUint8(2)
		bf.WriteUint32(guild.EventRP) // Skipped if last byte is <2?
		ps.Uint8(bf, guild.PugiName1, true)
		ps.Uint8(bf, guild.PugiName2, true)
		ps.Uint8(bf, guild.PugiName3, true)
		bf.WriteUint8(guild.PugiOutfit1)
		bf.WriteUint8(guild.PugiOutfit2)
		bf.WriteUint8(guild.PugiOutfit3)
		if s.server.erupeConfig.RealClientMode >= cfg.Z1 {
			bf.WriteUint8(guild.PugiOutfit1)
			bf.WriteUint8(guild.PugiOutfit2)
			bf.WriteUint8(guild.PugiOutfit3)
		}
		bf.WriteUint32(guild.PugiOutfits)

		limit := s.server.erupeConfig.GameplayOptions.ClanMemberLimits[0][1]
		for _, j := range s.server.erupeConfig.GameplayOptions.ClanMemberLimits {
			if guild.Rank(s.server.erupeConfig.RealClientMode) >= uint16(j[0]) {
				limit = j[1]
			}
		}
		if limit > 100 {
			limit = 100
		}
		bf.WriteUint8(limit)

		bf.WriteUint32(guildRoomMaxRP)
		bf.WriteUint32(uint32(guild.RoomExpiry.Unix()))
		bf.WriteUint16(guild.RoomRP)
		bf.WriteUint16(0) // Ignored

		if guild.AllianceID > 0 {
			alliance, err := s.server.guildRepo.GetAllianceByID(guild.AllianceID)
			if err != nil || alliance == nil {
				bf.WriteUint32(0) // Error or missing alliance
			} else {
				bf.WriteUint32(alliance.ID)
				bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
				bf.WriteUint16(alliance.TotalMembers)
				bf.WriteUint8(0) // Ignored
				bf.WriteUint8(0)
				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(0) // Unk1
				if alliance.ParentGuildID == guild.ID {
					bf.WriteUint16(1)
				} else {
					bf.WriteUint16(0)
				}
				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(0) // Unk1
					if alliance.SubGuild1ID == guild.ID {
						bf.WriteUint16(1)
					} else {
						bf.WriteUint16(0)
					}
					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(0) // Unk1
					if alliance.SubGuild2ID == guild.ID {
						bf.WriteUint16(1)
					} else {
						bf.WriteUint16(0)
					}
					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)
				}
			}
		} else {
			bf.WriteUint32(0) // No alliance
		}

		applicants, err := s.server.guildRepo.GetMembers(guild.ID, true)
		if err != nil || (characterGuildData != nil && !characterGuildData.CanRecruit()) {
			bf.WriteUint16(0)
		} else {
			bf.WriteUint16(uint16(len(applicants)))
			for _, applicant := range applicants {
				bf.WriteUint32(applicant.CharID)
				bf.WriteUint32(0)
				bf.WriteUint16(applicant.HR)
				if s.server.erupeConfig.RealClientMode >= cfg.G10 {
					bf.WriteUint16(applicant.GR)
				}
				ps.Uint8(bf, applicant.Name, true)
			}
		}

		type Activity struct {
			Pass uint8
			Unk1 uint8
			Unk2 uint8
		}
		activity := []Activity{
			// 1,0,0 = ok
			// 0,0,0 = ng
		}
		bf.WriteUint8(uint8(len(activity)))
		for _, info := range activity {
			bf.WriteUint8(info.Pass)
			bf.WriteUint8(info.Unk1)
			bf.WriteUint8(info.Unk2)
		}

		type AllianceInvite struct {
			GuildID    uint32
			LeaderID   uint32
			Unk0       uint16
			Unk1       uint16
			Members    uint16
			GuildName  string
			LeaderName string
		}
		allianceInvites := []AllianceInvite{}
		bf.WriteUint8(uint8(len(allianceInvites)))
		for _, invite := range allianceInvites {
			bf.WriteUint32(invite.GuildID)
			bf.WriteUint32(invite.LeaderID)
			bf.WriteUint16(invite.Unk0)
			bf.WriteUint16(invite.Unk1)
			bf.WriteUint16(invite.Members)
			ps.Uint16(bf, invite.GuildName, true)
			ps.Uint16(bf, invite.LeaderName, true)
		}

		if guild.Icon != nil {
			bf.WriteUint8(uint8(len(guild.Icon.Parts)))

			for _, p := range guild.Icon.Parts {
				bf.WriteUint16(p.Index)
				bf.WriteUint16(p.ID)
				bf.WriteUint8(p.Page)
				bf.WriteUint8(p.Size)
				bf.WriteUint8(p.Rotation)
				bf.WriteUint8(p.Red)
				bf.WriteUint8(p.Green)
				bf.WriteUint8(p.Blue)
				bf.WriteUint16(p.PosX)
				bf.WriteUint16(p.PosY)
			}
		} else {
			bf.WriteUint8(0)
		}
		bf.WriteUint8(0) // Unk

		doAckBufSucceed(s, pkt.AckHandle, bf.Data())
	} else {
		doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
	}
}

func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfEnumerateGuild)

	var guilds []*Guild
	var alliances []*GuildAlliance
	var err error

	if pkt.Type <= 8 {
		var tempGuilds []*Guild
		tempGuilds, err = s.server.guildRepo.ListAll()
		if err == nil {
			switch pkt.Type {
			case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
				searchName := stringsupport.SJISToUTF8Lossy(pkt.Data2.ReadNullTerminatedBytes())
				for _, guild := range tempGuilds {
					if strings.Contains(guild.Name, searchName) {
						guilds = append(guilds, guild)
					}
				}
			case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
				searchName := stringsupport.SJISToUTF8Lossy(pkt.Data2.ReadNullTerminatedBytes())
				for _, guild := range tempGuilds {
					if strings.Contains(guild.LeaderName, searchName) {
						guilds = append(guilds, guild)
					}
				}
			case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
				CID := pkt.Data1.ReadUint32()
				for _, guild := range tempGuilds {
					if guild.LeaderCharID == CID {
						guilds = append(guilds, guild)
					}
				}
			case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
				if pkt.Sorting {
					sort.Slice(tempGuilds, func(i, j int) bool {
						return tempGuilds[i].MemberCount > tempGuilds[j].MemberCount
					})
				} else {
					sort.Slice(tempGuilds, func(i, j int) bool {
						return tempGuilds[i].MemberCount < tempGuilds[j].MemberCount
					})
				}
				guilds = tempGuilds
			case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
				if pkt.Sorting {
					sort.Slice(tempGuilds, func(i, j int) bool {
						return tempGuilds[i].CreatedAt.Unix() > tempGuilds[j].CreatedAt.Unix()
					})
				} else {
					sort.Slice(tempGuilds, func(i, j int) bool {
						return tempGuilds[i].CreatedAt.Unix() < tempGuilds[j].CreatedAt.Unix()
					})
				}
				guilds = tempGuilds
			case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
				if pkt.Sorting {
					sort.Slice(tempGuilds, func(i, j int) bool {
						return tempGuilds[i].RankRP > tempGuilds[j].RankRP
					})
				} else {
					sort.Slice(tempGuilds, func(i, j int) bool {
						return tempGuilds[i].RankRP < tempGuilds[j].RankRP
					})
				}
				guilds = tempGuilds
			case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
				mainMotto := uint8(pkt.Data1.ReadUint16())
				subMotto := uint8(pkt.Data1.ReadUint16())
				for _, guild := range tempGuilds {
					if guild.MainMotto == mainMotto && guild.SubMotto == subMotto {
						guilds = append(guilds, guild)
					}
				}
			case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
				recruitingMotto := uint8(pkt.Data1.ReadUint16())
				for _, guild := range tempGuilds {
					if guild.MainMotto == recruitingMotto {
						guilds = append(guilds, guild)
					}
				}
			}
		}
	}

	if pkt.Type > 8 {
		var tempAlliances []*GuildAlliance
		tempAlliances, err = s.server.guildRepo.ListAlliances()
		switch pkt.Type {
		case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
			searchName := stringsupport.SJISToUTF8Lossy(pkt.Data2.ReadNullTerminatedBytes())
			for _, alliance := range tempAlliances {
				if strings.Contains(alliance.Name, searchName) {
					alliances = append(alliances, alliance)
				}
			}
		case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
			searchName := stringsupport.SJISToUTF8Lossy(pkt.Data2.ReadNullTerminatedBytes())
			for _, alliance := range tempAlliances {
				if strings.Contains(alliance.ParentGuild.LeaderName, searchName) {
					alliances = append(alliances, alliance)
				}
			}
		case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_ID:
			CID := pkt.Data1.ReadUint32()
			for _, alliance := range tempAlliances {
				if alliance.ParentGuild.LeaderCharID == CID {
					alliances = append(alliances, alliance)
				}
			}
		case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ORDER_MEMBERS:
			if pkt.Sorting {
				sort.Slice(tempAlliances, func(i, j int) bool {
					return tempAlliances[i].TotalMembers > tempAlliances[j].TotalMembers
				})
			} else {
				sort.Slice(tempAlliances, func(i, j int) bool {
					return tempAlliances[i].TotalMembers < tempAlliances[j].TotalMembers
				})
			}
			alliances = tempAlliances
		case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ORDER_REGISTRATION:
			if pkt.Sorting {
				sort.Slice(tempAlliances, func(i, j int) bool {
					return tempAlliances[i].CreatedAt.Unix() > tempAlliances[j].CreatedAt.Unix()
				})
			} else {
				sort.Slice(tempAlliances, func(i, j int) bool {
					return tempAlliances[i].CreatedAt.Unix() < tempAlliances[j].CreatedAt.Unix()
				})
			}
			alliances = tempAlliances
		}
	}

	if err != nil || (guilds == nil && alliances == nil) {
		stubEnumerateNoResults(s, pkt.AckHandle)
		return
	}

	bf := byteframe.NewByteFrame()

	if pkt.Type > 8 {
		hasNextPage := false
		if len(alliances) > 10 {
			hasNextPage = true
			alliances = alliances[:10]
		}
		bf.WriteUint16(uint16(len(alliances)))
		bf.WriteBool(hasNextPage)
		for _, alliance := range alliances {
			bf.WriteUint32(alliance.ID)
			bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
			bf.WriteUint16(alliance.TotalMembers)
			bf.WriteUint16(0x0000)
			if alliance.SubGuild1ID == 0 && alliance.SubGuild2ID == 0 {
				bf.WriteUint16(1)
			} else if alliance.SubGuild1ID > 0 && alliance.SubGuild2ID == 0 || alliance.SubGuild1ID == 0 && alliance.SubGuild2ID > 0 {
				bf.WriteUint16(2)
			} else {
				bf.WriteUint16(3)
			}
			bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
			ps.Uint8(bf, alliance.Name, true)
			ps.Uint8(bf, alliance.ParentGuild.LeaderName, true)
			bf.WriteUint8(0x01) // Unk
			bf.WriteBool(!alliance.Recruiting)
		}
	} else {
		hasNextPage := false
		if len(guilds) > 10 {
			hasNextPage = true
			guilds = guilds[:10]
		}
		bf.WriteUint16(uint16(len(guilds)))
		bf.WriteBool(hasNextPage)
		for _, guild := range guilds {
			bf.WriteUint32(guild.ID)
			bf.WriteUint32(guild.LeaderCharID)
			bf.WriteUint16(guild.MemberCount)
			bf.WriteUint16(0x0000) // Unk
			bf.WriteUint16(guild.Rank(s.server.erupeConfig.RealClientMode))
			bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
			ps.Uint8(bf, guild.Name, true)
			ps.Uint8(bf, guild.LeaderName, true)
			bf.WriteUint8(0x01) // Unk
			bf.WriteBool(!guild.Recruiting)
		}
	}

	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
