local addonName, TOAST = ...

-- Release status is intentionally separate from Crew State records.  It
-- deals only with local UX policy; comms.lua validates and supplies trusted
-- peer announcements.

local function EnsureDB()
    TOAST_DB = TOAST_DB or {}
    if type(TOAST_DB.releaseStatus) ~= "table" then TOAST_DB.releaseStatus = {} end
    local status = TOAST_DB.releaseStatus
    if type(status.policyConfirmations) ~= "table" then status.policyConfirmations = {} end
    return status
end

local function VersionParts(value)
    if type(value) ~= "string" or value == "" then return nil end
    local numeric, qualifier = value:match("^([0-9][0-9%.]*)(%-?[%w%.]*)$")
    if not numeric then return nil end
    local parts = {}
    for number in numeric:gmatch("%d+") do parts[#parts + 1] = tonumber(number) end
    if #parts == 0 then return nil end
    qualifier = qualifier:lower():gsub("^-", "")
    local stage = qualifier == "" and 4
        or qualifier:find("rc", 1, true) and 3
        or qualifier:find("beta", 1, true) and 2
        or qualifier:find("alpha", 1, true) and 1
        or 0
    return parts, stage, qualifier
end

-- Only a plain major.minor.patch build is a public release. Pre-release
-- clients still participate in capability discovery, but must never make a
-- stable client advertise or queue an alpha, beta, RC, or unknown qualifier
-- as an available update.
local function IsReleaseVersion(value)
    if type(value) ~= "string" then return false end
    local major, minor, patch = value:match("^([0-9]+)%.([0-9]+)%.([0-9]+)$")
    if not major then return false end

    local function ValidIdentifier(identifier)
        return identifier == "0" or identifier:match("^[1-9][0-9]*$") ~= nil
    end

    return ValidIdentifier(major) and ValidIdentifier(minor) and ValidIdentifier(patch)
end

TOAST.IsReleaseVersion = IsReleaseVersion

function TOAST.CompareVersions(left, right)
    local leftParts, leftStage, leftQualifier = VersionParts(left)
    local rightParts, rightStage, rightQualifier = VersionParts(right)
    if not leftParts or not rightParts then return nil end
    local length = math.max(#leftParts, #rightParts)
    for index = 1, length do
        local a, b = leftParts[index] or 0, rightParts[index] or 0
        if a ~= b then return a < b and -1 or 1 end
    end
    if leftStage ~= rightStage then return leftStage < rightStage and -1 or 1 end
    if leftQualifier ~= rightQualifier then return leftQualifier < rightQualifier and -1 or 1 end
    return 0
end

local function Policy()
    local config = TOAST.ReleasePolicy or {}
    local required = math.max(2, math.floor(tonumber(config.confirmationsRequired) or 2))
    if type(config.policyId) ~= "string" or config.policyId == ""
        or #config.policyId > 32 or not IsReleaseVersion(config.minimumVersion)
        or not TOAST.CompareVersions(TOAST.VERSION, config.minimumVersion) then
        return nil
    end
    return {
        policyId = config.policyId,
        minimumVersion = config.minimumVersion,
        forceUpdate = config.forceUpdate == true,
        confirmationsRequired = required,
    }
end

function TOAST.GetLocalReleasePolicy()
    local policy = Policy()
    if not policy then return nil end
    local copy = {}
    for key, value in pairs(policy) do copy[key] = value end
    return copy
end

local function CountConfirmations(record)
    local count = 0
    for _, seenAt in pairs(record.senders or {}) do
        if type(seenAt) == "number" and GetServerTime() - seenAt < 7 * 24 * 60 * 60 then
            count = count + 1
        end
    end
    return count
end

local function BlockingPolicy()
    local status = EnsureDB()
    for _, record in pairs(status.policyConfirmations) do
        if type(record) == "table" and record.forceUpdate == true
            and IsReleaseVersion(record.minimumVersion)
            and TOAST.CompareVersions(TOAST.VERSION, record.minimumVersion) == -1
            and CountConfirmations(record) >= math.max(2, tonumber(record.confirmationsRequired) or 2) then
            return record
        end
    end
    return nil
end

function TOAST.IsUpdateBlocked()
    return BlockingPolicy() ~= nil
end

local function ShowReleaseScreen(kind, text)
    if TOAST.ShowReleaseScreen then
        TOAST.ShowReleaseScreen(kind, text)
    else
        print("|cffffb347TOAST update|r: " .. text)
    end
end

function TOAST.ShowWeeklyDataUpdateNotice(text)
    print("|cffffd100TOAST data update|r: " .. (text
        or "New weekly TOAST crew data is available. Update your addon through CurseForge to refresh the leaderboard."))
end

function TOAST.ShowUpdateRequired()
    local policy = BlockingPolicy()
    if not policy then return end
    ShowReleaseScreen("required", string.format(
        "This TOAST version (%s) is no longer supported. Install version %s or newer.",
        TOAST.VERSION, policy.minimumVersion))
end

local function QueueUpdateAvailable(version)
    if not IsReleaseVersion(version) then return false end
    local status = EnsureDB()
    local pending = status.pendingSoftNoticeVersion
    if pending and TOAST.CompareVersions(pending, version) ~= -1 then return end
    status.pendingSoftNoticeVersion = version
    return true
end

function TOAST.ObservePeerVersion(sender, version)
    if not sender or not IsReleaseVersion(version) then return end
    local comparison = TOAST.CompareVersions(TOAST.VERSION, version)
    if comparison == -1 then QueueUpdateAvailable(version) end
end

function TOAST.ObserveUpdatePolicy(sender, policyId, minimumVersion, forceUpdate)
    if type(sender) ~= "string" or type(policyId) ~= "string" or #policyId == 0 or #policyId > 32
        or not IsReleaseVersion(minimumVersion)
        or TOAST.CompareVersions(TOAST.VERSION, minimumVersion) == nil then
        return false
    end
    if forceUpdate ~= true then return false end

    local status = EnsureDB()
    local record = status.policyConfirmations[policyId]
    if type(record) ~= "table" or record.minimumVersion ~= minimumVersion then
        record = {
            minimumVersion = minimumVersion,
            forceUpdate = true,
            confirmationsRequired = 2,
            senders = {},
        }
        status.policyConfirmations[policyId] = record
    end
    record.senders[sender:lower()] = GetServerTime()

    return true
end

function TOAST.ShouldAnnounceUpdatePolicy()
    if not IsReleaseVersion(TOAST.VERSION) then return nil end
    local policy = Policy()
    return policy and policy.forceUpdate and policy or nil
end

local function WeeklyDataIsStale()
    local resetStart = C_DateAndTime and C_DateAndTime.GetWeeklyResetStartTime
    if not resetStart or type(TOAST.leaderboard_updated) ~= "string" then return false end
    local ok, reset = pcall(resetStart)
    if not ok or type(reset) ~= "number" or GetServerTime() < reset + 2 * 60 * 60 then return false end
    local updatedDate = TOAST.leaderboard_updated:match("^(%d%d%d%d%-%d%d%-%d%d)")
    local resetDate = date("%Y-%m-%d", reset)
    return updatedDate ~= nil and updatedDate < resetDate, resetDate
end

function TOAST.CheckReleaseStatus()
    if TOAST.IsUpdateBlocked() then
        TOAST.ShowUpdateRequired()
        return false
    end
    local status = EnsureDB()

    -- Changelog delivery is local-version based, not message based. A player
    -- who just installed an update must see its notes on their first /toast,
    -- even when no other TOAST client happens to be online.
    if status.lastChangelogVersion ~= TOAST.VERSION then
        status.lastChangelogVersion = TOAST.VERSION
        ShowReleaseScreen("changelog")
        return true
    end

    if status.pendingSoftNoticeVersion then
        local version = status.pendingSoftNoticeVersion
        if not IsReleaseVersion(version) then
            -- Older builds could persist a prerelease peer as a pending soft
            -- notice. Drop it silently so upgrading to this logic cannot show
            -- a stale alpha/beta/RC prompt on the next /toast.
            status.pendingSoftNoticeVersion = nil
        elseif TOAST.CompareVersions(TOAST.VERSION, version) ~= -1 then
            -- The local update already meets or exceeds the remembered peer
            -- notice, so do not show a stale update prompt later.
            status.pendingSoftNoticeVersion = nil
        else
            status.pendingSoftNoticeVersion = nil
            status.lastSoftNoticeVersion = version
            ShowReleaseScreen("version", string.format(
                "Installed version: v%s\nAvailable version: v%s"
                    .. "\n\nOpen CurseForge and update TOAST, then restart World of Warcraft or use /reload.",
                TOAST.VERSION, version))
            return true
        end
    end
    local stale, resetDate = WeeklyDataIsStale()
    if stale then
        if status.lastDataNoticeWeek ~= resetDate then
            status.lastDataNoticeWeek = resetDate
            TOAST.ShowWeeklyDataUpdateNotice(
                "New weekly TOAST crew data is available. Update your addon through CurseForge to refresh the leaderboard.")
        end
    end
    return true
end
