-- WarBoard_Deserter.lua
-- Anti-exploit for the bounty system. A player who escapes a fight with the quest-abandon
-- teleport trick WHILE they have a bounty on them is marked a DESERTER: while marked they
-- bank no gold or war credit, and each repeat offence stacks another hour, capped at 10h.
--
-- Detection (adapted from JARVIS's Survival Protocol): hook C_QuestLog.AbandonQuest — if
-- the player is in combat when a quest is abandoned, open a 5s suspicion window; if a
-- loading screen begins inside that window (PLAYER_LEAVING_WORLD) and the player re-enters
-- the world ALIVE (PLAYER_ENTERING_WORLD, not a login/reload, not dead), it was a teleport
-- escape. Self-detected on the offender's OWN client (no server), then announced to peers
-- so hunters see the status; honest clients self-enforce the no-earning penalty (the
-- enforcement lives in WarEffort_Tracker's WE.SubmitRecord, which drops earning records
-- while WB.Deserter.IsActive()).

local addonName, WB = ...
WB.Deserter = WB.Deserter or {}
local D = WB.Deserter

local PENALTY_STEP = 3600        -- +1 hour per offence
local PENALTY_MAX  = 10 * 3600   -- never marked more than 10 hours out

local db

local function EnsureDB()
    WarBoardCharDB = WarBoardCharDB or {}
    db = WarBoardCharDB   -- db.deserterUntil persists my own penalty across reloads
end

-- ── Status queries ───────────────────────────────────────────────────────────────
function D.MyUntil()  return (db and tonumber(db.deserterUntil)) or 0 end
function D.IsActive() return D.MyUntil() > time() end   -- am I currently a deserter?

-- ── Expiry notice (local "you can earn again") ─────────────────────────────────────
local expiryTimer
local function ScheduleExpiryNotice()
    if expiryTimer then expiryTimer:Cancel(); expiryTimer = nil end
    local left = D.MyUntil() - time()
    if left <= 0 or not C_Timer then return end
    expiryTimer = C_Timer.NewTimer(left + 1, function()
        if D.IsActive() then return end   -- got extended again in the meantime
        print(WB.TAG .. " your |cff44ff44deserter status has ended|r \226\128\148 you can earn gold and war credit again.")
        if WB.WarEffort and WB.WarEffort.RefreshUI then WB.WarEffort.RefreshUI() end
    end)
end

-- ── Announce my own status to peers ───────────────────────────────────────────────
local function BroadcastSelf()
    if not (WB.Comm and WB.Comm.BroadcastDeserter) then return end
    WB.Comm.BroadcastDeserter({
        guid    = UnitGUID("player"),
        name    = UnitNameUnmodified("player"),
        realm   = GetNormalizedRealmName() or "",
        faction = WB.faction or UnitFactionGroup("player") or "Horde",
        u       = D.MyUntil(),
    })
end

-- ── Mark myself a deserter (on detection) ──────────────────────────────────────────
local function MarkSelf()
    if not db then EnsureDB() end
    local now  = time()
    local base = math.max(D.MyUntil(), now)                 -- stack onto any remaining time
    db.deserterUntil = math.min(base + PENALTY_STEP, now + PENALTY_MAX)
    local hours = math.max(1, math.floor((db.deserterUntil - now) / 3600 + 0.5))
    print(("%s |cffff3030You have been marked a DESERTER|r for escaping with a quest-abandon teleport \226\128\148 no gold or war credit for ~%d hour(s)."):format(WB.TAG, hours))
    if RaidNotice_AddMessage and RaidWarningFrame and ChatTypeInfo then
        RaidNotice_AddMessage(RaidWarningFrame, "War Board: Quest-abandon escape detected \226\128\148 marked DESERTER.", ChatTypeInfo["RAID_WARNING"])
    end
    ScheduleExpiryNotice()
    BroadcastSelf()
    if WB.WarEffort and WB.WarEffort.RefreshUI then WB.WarEffort.RefreshUI() end
end

-- ── Detection (JARVIS-derived) ─────────────────────────────────────────────────────
local suspiciousAbandonTime    = nil
local suspiciousLoadingPending = false

-- hooksecurefunc fires after AbandonQuest() runs, whatever the source (macro/addon/UI),
-- and only on real abandons, never completions.
if C_QuestLog and C_QuestLog.AbandonQuest then
    hooksecurefunc(C_QuestLog, "AbandonQuest", function()
        if UnitAffectingCombat("player") then
            suspiciousAbandonTime = GetTime()
        end
    end)
end

local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:RegisterEvent("PLAYER_LOGIN")
f:RegisterEvent("PLAYER_LEAVING_WORLD")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, a1, a2)
    if event == "ADDON_LOADED" then
        if a1 == addonName then EnsureDB() end

    elseif event == "PLAYER_LOGIN" then
        EnsureDB()
        ScheduleExpiryNotice()
        -- Re-announce a still-active penalty so peers + the bounty board reflect it.
        if C_Timer then C_Timer.After(10, function() if D.IsActive() then BroadcastSelf() end end) end

    elseif event == "PLAYER_LEAVING_WORLD" then
        -- A loading screen is starting. Check the timing window NOW, before it begins, so
        -- the screen's duration never affects the result.
        if suspiciousAbandonTime and (GetTime() - suspiciousAbandonTime) <= 5 then
            suspiciousLoadingPending = true
        end
        suspiciousAbandonTime = nil

    elseif event == "PLAYER_ENTERING_WORLD" then
        local isInitialLogin, isReloadingUi = a1, a2
        if isInitialLogin or isReloadingUi then suspiciousLoadingPending = false; return end
        if not suspiciousLoadingPending then return end
        suspiciousLoadingPending = false
        if UnitIsDeadOrGhost("player") then return end          -- died legitimately, ignore
        -- Only an offence in the BOUNTY context: the escape only counts while a hunter is
        -- actually after you (a live bounty on you).
        if WB.Bounties and WB.Bounties.GetBountyOnMe and WB.Bounties.GetBountyOnMe() then
            MarkSelf()
        end
    end
end)
