package channelserver

import (
	"erupe-ce/network/mhfpacket"
	"net"
	"strings"
)

// LocalChannelRegistry is the in-process ChannelRegistry backed by []*Server.
type LocalChannelRegistry struct {
	channels []*Server
}

// NewLocalChannelRegistry creates a LocalChannelRegistry wrapping the given channels.
func NewLocalChannelRegistry(channels []*Server) *LocalChannelRegistry {
	return &LocalChannelRegistry{channels: channels}
}

func (r *LocalChannelRegistry) Worldcast(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) {
	for _, c := range r.channels {
		if c == ignoredChannel {
			continue
		}
		c.BroadcastMHF(pkt, ignoredSession)
	}
}

func (r *LocalChannelRegistry) FindSessionByCharID(charID uint32) *Session {
	for _, c := range r.channels {
		c.Lock()
		for _, session := range c.sessions {
			if session.charID == charID {
				c.Unlock()
				return session
			}
		}
		c.Unlock()
	}
	return nil
}

func (r *LocalChannelRegistry) DisconnectUser(cids []uint32) {
	for _, c := range r.channels {
		c.Lock()
		for _, session := range c.sessions {
			for _, cid := range cids {
				if session.charID == cid {
					_ = session.rawConn.Close()
					break
				}
			}
		}
		c.Unlock()
	}
}

func (r *LocalChannelRegistry) FindChannelForStage(stageSuffix string) string {
	for _, channel := range r.channels {
		var gid string
		channel.stages.Range(func(id string, _ *Stage) bool {
			if strings.HasSuffix(id, stageSuffix) {
				gid = channel.GlobalID
				return false // stop iteration
			}
			return true
		})
		if gid != "" {
			return gid
		}
	}
	return ""
}

func (r *LocalChannelRegistry) SearchSessions(predicate func(SessionSnapshot) bool, max int) []SessionSnapshot {
	var results []SessionSnapshot
	for _, c := range r.channels {
		if len(results) >= max {
			break
		}
		c.Lock()
		for _, session := range c.sessions {
			if len(results) >= max {
				break
			}
			snap := SessionSnapshot{
				CharID:     session.charID,
				Name:       session.Name,
				ServerIP:   net.ParseIP(c.IP).To4(),
				ServerPort: c.Port,
			}
			if session.stage != nil {
				snap.StageID = session.stage.id
			}
			snap.UserBinary3 = c.userBinary.GetCopy(session.charID, 3)
			if predicate(snap) {
				results = append(results, snap)
			}
		}
		c.Unlock()
	}
	return results
}

func (r *LocalChannelRegistry) SearchStages(stagePrefix string, max int) []StageSnapshot {
	var results []StageSnapshot
	for _, c := range r.channels {
		if len(results) >= max {
			break
		}

		// Pre-collect which charIDs are in quest stages under server lock,
		// so we can count quest-reserved players without lock ordering issues
		// (Server.Mutex must be acquired before Stage.RWMutex).
		c.Lock()
		inQuest := make(map[uint32]bool)
		for _, sess := range c.sessions {
			if sess.stage != nil && len(sess.stage.id) > 4 && sess.stage.id[3:5] == "Qs" {
				inQuest[sess.charID] = true
			}
		}
		c.Unlock()

		cIP := net.ParseIP(c.IP).To4()
		cPort := c.Port
		c.stages.Range(func(_ string, stage *Stage) bool {
			if len(results) >= max {
				return false
			}
			if !strings.HasPrefix(stage.id, stagePrefix) {
				return true
			}
			stage.RLock()
			bin0 := stage.rawBinaryData[stageBinaryKey{1, 0}]
			bin0Copy := make([]byte, len(bin0))
			copy(bin0Copy, bin0)
			bin1 := stage.rawBinaryData[stageBinaryKey{1, 1}]
			bin1Copy := make([]byte, len(bin1))
			copy(bin1Copy, bin1)
			bin3 := stage.rawBinaryData[stageBinaryKey{1, 3}]
			bin3Copy := make([]byte, len(bin3))
			copy(bin3Copy, bin3)

			questReserved := 0
			for charID := range stage.reservedClientSlots {
				if inQuest[charID] {
					questReserved++
				}
			}

			results = append(results, StageSnapshot{
				ServerIP:      cIP,
				ServerPort:    cPort,
				StageID:       stage.id,
				ClientCount:   len(stage.clients) + len(stage.reservedClientSlots),
				Reserved:      len(stage.reservedClientSlots),
				QuestReserved: questReserved,
				MaxPlayers:    stage.maxPlayers,
				RawBinData0:   bin0Copy,
				RawBinData1:   bin1Copy,
				RawBinData3:   bin3Copy,
			})
			stage.RUnlock()
			return true
		})
	}
	return results
}

func (r *LocalChannelRegistry) NotifyMailToCharID(charID uint32, sender *Session, mail *Mail) {
	session := r.FindSessionByCharID(charID)
	if session != nil {
		SendMailNotification(sender, mail, session)
	}
}
