GuildHelper = GuildHelper or {}

local addon = GuildHelper

addon.FederationService = addon.FederationService or {}
addon.FederationService.runtime = addon.FederationService.runtime or {
    partyPeers = {},
    lastDiscoveryAt = 0,
}

local PARTY_DISCOVERY_COOLDOWN_SECONDS = 3

local function trimString(value)
    return tostring(value or ""):match("^%s*(.-)%s*$")
end

local function isPlaceholderGuildKey(guildKey)
    local normalized = string.lower(tostring(guildKey or "")):gsub("%s+", "")
    return normalized == "guildname-realm" or normalized == "guild-realm"
end

local function getRecordGuildKey(record)
    return record and (record.guildKey or record.name) or nil
end

local function getLocalRequestedAt(record)
    return record and (record.requestedAt or record.addedAt) or nil
end

local function markFederationChanged()
    local tableMeta = addon.Store:GetSync().tableMeta.federation

    if tableMeta then
        tableMeta.updatedAt = time()
    end
end

local function ensureRecordShape(record, guildKey)
    if type(record) ~= "table" then
        return nil
    end

    record.guildKey = guildKey or record.guildKey or record.name
    record.name = record.name or record.guildKey

    if not record.requestedAt and record.addedAt then
        record.requestedAt = record.addedAt
    end

    if not record.requestedBy and record.addedBy then
        record.requestedBy = record.addedBy
    end

    return record
end

local function getGroupedUnitIds()
    local unitIds = {}

    if IsInRaid() then
        for index = 1, (GetNumGroupMembers() or 0) do
            unitIds[#unitIds + 1] = "raid" .. index
        end
    else
        for index = 1, (GetNumSubgroupMembers() or 0) do
            unitIds[#unitIds + 1] = "party" .. index
        end
    end

    return unitIds
end

local function getUnitFullName(unitId)
    if not UnitExists(unitId) or not UnitIsPlayer(unitId) then
        return nil
    end

    local unitName, unitRealm = UnitFullName(unitId)
    local isValidPeer, normalizedPeer = addon.Store:IsLikelyPlayerFullName(unitName, unitRealm or GetRealmName() or "Unknown")
    return isValidPeer and normalizedPeer or nil
end

function addon.FederationService:GetHomeGuild()
    return addon.Store:EnsureHomeGuild()
end

function addon.FederationService:GetLinkedGuilds()
    local linkedGuilds = addon.Store:GetGuild().federation.linkedGuilds

    for _, record in ipairs(linkedGuilds) do
        ensureRecordShape(record, getRecordGuildKey(record))
    end

    return linkedGuilds
end

function addon.FederationService:GetLinkedGuildRecord(guildKey)
    if not guildKey then
        return nil, nil
    end

    for index, record in ipairs(self:GetLinkedGuilds()) do
        local recordGuildKey = getRecordGuildKey(record)

        if recordGuildKey == guildKey then
            return ensureRecordShape(record, recordGuildKey), index
        end
    end

    return nil, nil
end

function addon.FederationService:GetAllGuildKeys()
    local keys = {}
    local seen = {}
    local homeGuild = self:GetHomeGuild()

    if homeGuild and homeGuild ~= "" then
        seen[homeGuild] = true
        keys[#keys + 1] = homeGuild
    end

    for _, record in ipairs(self:GetLinkedGuilds()) do
        local guildKey = getRecordGuildKey(record)

        if guildKey and not seen[guildKey] and self:IsApprovedLinkedGuild(guildKey) then
            seen[guildKey] = true
            keys[#keys + 1] = guildKey
        end
    end

    return keys
end

function addon.FederationService:IsLinkedGuild(guildKey)
    return self:GetLinkedGuildRecord(guildKey) ~= nil
end

function addon.FederationService:IsApprovedLinkedGuild(guildKey)
    local record = self:GetLinkedGuildRecord(guildKey)

    if not record then
        return false
    end

    return self:GetLinkStatus(record).key == "approved"
end

function addon.FederationService:IsVisibleGuild(guildKey)
    return guildKey == self:GetHomeGuild() or self:IsApprovedLinkedGuild(guildKey)
end

function addon.FederationService:CanManageLinkedGuilds()
    local _, _, rankIndex = GetGuildInfo("player")
    return tonumber(rankIndex) == 0
end

function addon.FederationService:ValidateGuildKey(guildKey)
    local normalizedGuildKey = trimString(guildKey)

    if normalizedGuildKey == "" then
        return false, nil, "guild key is required"
    end

    local guildName, realmName = normalizedGuildKey:match("^(.-)%-(.+)$")

    if not guildName or guildName == "" or not realmName or realmName == "" then
        return false, nil, "guild key must use GuildName-Realm"
    end

    if isPlaceholderGuildKey(normalizedGuildKey) then
        return false, nil, "replace the placeholder with a real guild key"
    end

    return true, normalizedGuildKey
end

function addon.FederationService:GetLinkStatus(record)
    local localRequested = getLocalRequestedAt(record)
    local localApproved = record and record.approvedAt or nil
    local remoteRequested = record and (record.remoteRequestedAt or record.remoteApprovedAt) or nil
    local remoteApproved = record and record.remoteApprovedAt or nil

    if localRequested and remoteRequested then
        if localApproved and remoteApproved then
            return { key = "approved", label = "Approved" }
        end

        if localApproved and not remoteApproved then
            return { key = "waiting-remote-approval", label = "Waiting for remote approval" }
        end

        if not localApproved and remoteApproved then
            return { key = "needs-your-approval", label = "Needs your approval" }
        end

        return { key = "needs-both-approval", label = "Needs both approvals" }
    end

    if localRequested then
        return { key = "waiting-remote-request", label = "Waiting for remote request" }
    end

    if remoteRequested then
        return { key = "remote-requested", label = "Requested by remote guild" }
    end

    return { key = "inactive", label = "Inactive" }
end

function addon.FederationService:UpsertLinkedGuildRecord(guildKey)
    local record = self:GetLinkedGuildRecord(guildKey)

    if record then
        return record
    end

    record = {
        guildKey = guildKey,
        name = guildKey,
    }
    table.insert(addon.Store:GetGuild().federation.linkedGuilds, record)
    return record
end

function addon.FederationService:CreateLocalRequest(guildKey)
    local isValid, normalizedGuildKey, errorMessage = self:ValidateGuildKey(guildKey)

    if not self:CanManageLinkedGuilds() then
        return false, "only the guild master can link guilds"
    end

    if not isValid then
        return false, errorMessage
    end

    local record = self:UpsertLinkedGuildRecord(normalizedGuildKey)

    if getLocalRequestedAt(record) then
        return true, nil, record
    end

    local now = time()
    local playerName = addon.Store:GetPlayerName()
    record.guildKey = normalizedGuildKey
    record.name = normalizedGuildKey
    record.requestedAt = now
    record.requestedBy = playerName
    record.addedAt = now
    record.addedBy = playerName
    record.approvedAt = nil
    record.approvedBy = nil
    markFederationChanged()

    return true, nil, record
end

function addon.FederationService:ApproveLocalLink(guildKey)
    local isValid, normalizedGuildKey, errorMessage = self:ValidateGuildKey(guildKey)

    if not self:CanManageLinkedGuilds() then
        return false, "only the guild master can approve linked guilds"
    end

    if not isValid then
        return false, errorMessage
    end

    local record = self:GetLinkedGuildRecord(normalizedGuildKey)

    if not record then
        return false, "linked guild request not found"
    end

    if not getLocalRequestedAt(record) then
        return false, "create a local request first"
    end

    if not record.remoteRequestedAt and not record.remoteApprovedAt then
        return false, "waiting for the remote guild master to request the link"
    end

    record.approvedAt = time()
    record.approvedBy = addon.Store:GetPlayerName()
    markFederationChanged()
    return true, nil, record
end

function addon.FederationService:AddLinkedGuild(guildKey)
    local ok, message = self:CreateLocalRequest(guildKey)
    return ok, message
end

function addon.FederationService:ApproveLinkedGuild(guildKey)
    local ok, message = self:ApproveLocalLink(guildKey)
    return ok, message
end

function addon.FederationService:RemoveLinkedGuild(guildKey)
    local normalizedGuildKey = trimString(guildKey)

    if not self:CanManageLinkedGuilds() or normalizedGuildKey == "" then
        return false
    end

    local linkedGuilds = addon.Store:GetGuild().federation.linkedGuilds

    for index = #linkedGuilds, 1, -1 do
        local record = linkedGuilds[index]
        local recordGuildKey = getRecordGuildKey(record)

        if recordGuildKey == normalizedGuildKey then
            table.remove(linkedGuilds, index)
            markFederationChanged()
            return true
        end
    end

    return false
end

function addon.FederationService:IsGroupedWithPeer(peerName)
    local normalizedPeerName = addon.Store:NormalizeFullName(peerName, GetRealmName() or "Unknown")

    if normalizedPeerName == "" then
        return false
    end

    for _, unitId in ipairs(getGroupedUnitIds()) do
        if getUnitFullName(unitId) == normalizedPeerName then
            return true
        end
    end

    return false
end

function addon.FederationService:ClearStalePartyPeers()
    for peerName in pairs(self.runtime.partyPeers) do
        if not self:IsGroupedWithPeer(peerName) then
            self.runtime.partyPeers[peerName] = nil
        end
    end
end

function addon.FederationService:GetPartyHandshakePeers()
    self:ClearStalePartyPeers()

    local peers = {}

    for _, peer in pairs(self.runtime.partyPeers) do
        peers[#peers + 1] = peer
    end

    table.sort(peers, function(left, right)
        return (left.homeGuild or left.peer or "") < (right.homeGuild or right.peer or "")
    end)

    return peers
end

function addon.FederationService:SendHandshakeEnvelope(peerName, op, payload)
    local isValidPeer, normalizedPeerName = addon.Store:IsLikelyPlayerFullName(peerName, GetRealmName() or "Unknown")

    if not isValidPeer then
        return false, "peer name is required"
    end

    return addon.Transport:Send(normalizedPeerName, "WHISPER", {
        op = op,
        sessionId = string.format("fed:%s:%d", op, time()),
        tableName = "federation",
        payload = addon.Json:Encode(payload or {}),
    })
end

function addon.FederationService:BuildHandshakeStatusPayload()
    return {
        player = addon.Store:GetPlayerName(),
        homeGuild = self:GetHomeGuild(),
        isGuildMaster = self:CanManageLinkedGuilds(),
        generatedAt = time(),
    }
end

function addon.FederationService:RequestPartyPeerDiscovery(force)
    if not self:CanManageLinkedGuilds() then
        return false, "only the guild master can discover linked guild peers"
    end

    local now = time()

    if not force and self.runtime.lastDiscoveryAt and (now - self.runtime.lastDiscoveryAt) < PARTY_DISCOVERY_COOLDOWN_SECONDS then
        return false, "party discovery cooldown active"
    end

    self.runtime.lastDiscoveryAt = now
    self:ClearStalePartyPeers()

    local sentAny = false

    for _, unitId in ipairs(getGroupedUnitIds()) do
        local peerName = getUnitFullName(unitId)

        if peerName and peerName ~= addon.Store:GetPlayerName() then
            local ok = self:SendHandshakeEnvelope(peerName, "FED_QUERY", {
                requester = addon.Store:GetPlayerName(),
            })

            if ok then
                sentAny = true
            end
        end
    end

    return sentAny, sentAny and nil or "no grouped peers found"
end

function addon.FederationService:HandleHandshakeQuery(sender)
    if not self:CanManageLinkedGuilds() or not self:IsGroupedWithPeer(sender) then
        return false
    end

    self:SendHandshakeEnvelope(sender, "FED_STATUS", self:BuildHandshakeStatusPayload())
    return true
end

function addon.FederationService:HandleHandshakeStatus(sender, payload)
    if not self:CanManageLinkedGuilds() or not self:IsGroupedWithPeer(sender) or type(payload) ~= "table" then
        return false
    end

    local isValidHomeGuild, normalizedHomeGuild = self:ValidateGuildKey(payload.homeGuild)

    if not isValidHomeGuild or not payload.isGuildMaster then
        self.runtime.partyPeers[sender] = nil
        return false
    end

    self.runtime.partyPeers[sender] = {
        peer = sender,
        player = payload.player or sender,
        homeGuild = normalizedHomeGuild,
        isGuildMaster = payload.isGuildMaster and true or false,
        generatedAt = tonumber(payload.generatedAt) or time(),
        lastSeenAt = time(),
    }

    return true
end

function addon.FederationService:HandleLinkRequest(sender, payload)
    if not self:CanManageLinkedGuilds() or not self:IsGroupedWithPeer(sender) or type(payload) ~= "table" then
        return false
    end

    local isValidGuildKey, normalizedGuildKey = self:ValidateGuildKey(payload.homeGuild)

    if not isValidGuildKey then
        return false
    end

    local record = self:UpsertLinkedGuildRecord(normalizedGuildKey)
    record.remoteRequestedAt = time()
    record.remoteRequestedBy = sender
    markFederationChanged()
    return true
end

function addon.FederationService:HandleLinkApproval(sender, payload)
    if not self:CanManageLinkedGuilds() or not self:IsGroupedWithPeer(sender) or type(payload) ~= "table" then
        return false
    end

    local isValidGuildKey, normalizedGuildKey = self:ValidateGuildKey(payload.homeGuild)

    if not isValidGuildKey then
        return false
    end

    local record = self:UpsertLinkedGuildRecord(normalizedGuildKey)
    record.remoteApprovedAt = time()
    record.remoteApprovedBy = sender
    markFederationChanged()
    return true
end

function addon.FederationService:RequestLinkWithPeer(peerName, guildKey)
    if not self:CanManageLinkedGuilds() then
        return false, "only the guild master can request linked guilds"
    end

    if not self:IsGroupedWithPeer(peerName) then
        return false, "group with the other guild master before requesting a link"
    end

    local ok, errorMessage = self:CreateLocalRequest(guildKey)

    if not ok then
        return false, errorMessage
    end

    return self:SendHandshakeEnvelope(peerName, "FED_REQUEST", {
        homeGuild = self:GetHomeGuild(),
        requestedBy = addon.Store:GetPlayerName(),
        requestedAt = time(),
    })
end

function addon.FederationService:ApproveLinkWithPeer(peerName, guildKey)
    if not self:CanManageLinkedGuilds() then
        return false, "only the guild master can approve linked guilds"
    end

    if not self:IsGroupedWithPeer(peerName) then
        return false, "group with the other guild master before approving a link"
    end

    local ok, errorMessage = self:ApproveLocalLink(guildKey)

    if not ok then
        return false, errorMessage
    end

    return self:SendHandshakeEnvelope(peerName, "FED_APPROVE", {
        homeGuild = self:GetHomeGuild(),
        approvedBy = addon.Store:GetPlayerName(),
        approvedAt = time(),
    })
end
