BattlePetStatisticsDB = BattlePetStatisticsDB or nil

local frame = CreateFrame("Frame")
BattlePetStatistics_CoreFrame = frame

local currentOpponentKey         = nil
local currentOpponentDisplayName = nil
local battleIsPVP                = false
local battleResolved             = false
local playerDidForfeit           = false
local lastKnownEnemyDead         = false
local lastKnownAllyDead          = false
local debugMode                  = false

-- Session stats: reset each login, NOT saved to DB
local sessionWins   = 0
local sessionLosses = 0

-- A "session" is counted once per login, but only once the player has
-- started at least 2 PvP pet battles during that login. sessionCounted
-- guards against incrementing more than once per login.
local pvpBattlesStartedThisLogin = 0
local sessionCounted             = false

local function Debug(msg)
    if debugMode then
        print("|cff33ff99BPS|r: " .. tostring(msg))
    end
end

-- ===================== DB helpers =====================
function EnsureDB()
    if type(BattlePetStatisticsDB) ~= "table" then
        BattlePetStatisticsDB = {}
    end
    BattlePetStatisticsDB.language      = BattlePetStatisticsDB.language or "AUTO"
    BattlePetStatisticsDB.stats         = BattlePetStatisticsDB.stats or {}
    BattlePetStatisticsDB.minimap       = BattlePetStatisticsDB.minimap or { hide = false, angle = 200 }
    BattlePetStatisticsDB.tradeWinEnabled   = (BattlePetStatisticsDB.tradeWinEnabled ~= nil and BattlePetStatisticsDB.tradeWinEnabled) or false
    BattlePetStatisticsDB.alias         = BattlePetStatisticsDB.alias or ""
    -- When to show the TradeWin alias popup: "startup" / "after_first_battle" /
    -- "never" / "if_not_renamed" (only if the pets haven't been renamed yet).
    BattlePetStatisticsDB.showWindowMode = BattlePetStatisticsDB.showWindowMode or "after_first_battle"
    -- Whether to restore the player's pets to their default names on logout.
    if BattlePetStatisticsDB.restoreNamesOnLogout == nil then
        BattlePetStatisticsDB.restoreNamesOnLogout = false
    end
    BattlePetStatisticsDB.currentGoalId = BattlePetStatisticsDB.currentGoalId or (BPS_GOALS and BPS_GOALS[1].id)
    BattlePetStatisticsDB.sessionCount  = BattlePetStatisticsDB.sessionCount or 0
    BattlePetStatisticsDB.display = BattlePetStatisticsDB.display or {}
    local d = BattlePetStatisticsDB.display
    if d.showOverall       == nil then d.showOverall       = true  end
    if d.showWinrate       == nil then d.showWinrate       = true  end
    if d.showGoal          == nil then d.showGoal          = true  end
    if d.showVs            == nil then d.showVs            = true  end
    if d.showVsStats       == nil then d.showVsStats       = true  end
    if d.showVsWinrate     == nil then d.showVsWinrate     = true  end
    if d.showPrediction    == nil then d.showPrediction    = true  end
    if d.showForfeitButton == nil then d.showForfeitButton = true  end
end

local function GetStatEntry(key)
    EnsureDB()
    if not BattlePetStatisticsDB.stats[key] then
        BattlePetStatisticsDB.stats[key] = { wins = 0, losses = 0 }
    end
    return BattlePetStatisticsDB.stats[key]
end

-- ===================== Public getters =====================
function BPS_GetCurrentOpponentDisplayName()
    return currentOpponentDisplayName
end

function BPS_GetStatsFor(key)
    if not key then return 0, 0 end
    local e = GetStatEntry(key)
    return e.wins, e.losses
end

-- Session totals (reset each login)
function BPS_GetSessionTotals()
    return sessionWins, sessionLosses
end

function BPS_GetSessionCount()
    EnsureDB()
    return BattlePetStatisticsDB.sessionCount or 0
end

function BPS_GetAllStats()
    EnsureDB()
    return BattlePetStatisticsDB.stats
end

function BPS_ResetStats()
    EnsureDB()
    BattlePetStatisticsDB.stats = {}
    sessionWins, sessionLosses = 0, 0
end

function BPS_SetStatsFor(key, wins, losses)
    if not key then return end
    GetStatEntry(key)
    BattlePetStatisticsDB.stats[key].wins   = wins
    BattlePetStatisticsDB.stats[key].losses = losses
end

function BPS_DeleteStatsFor(key)
    if not key then return end
    EnsureDB()
    BattlePetStatisticsDB.stats[key] = nil
end

-- ===================== Win/loss recording =====================
local function RecordResult(key, won)
    if not key then return end
    local e = GetStatEntry(key)
    if won then
        e.wins = e.wins + 1
        sessionWins = sessionWins + 1
    else
        e.losses = e.losses + 1
        sessionLosses = sessionLosses + 1
    end
    Debug(string.format("Recorded %s for %s (session %d/%d)", won and "WIN" or "LOSS", key, sessionWins, sessionLosses))
end

-- ===================== Forfeit hook =====================
if C_PetBattles and C_PetBattles.ForfeitGame then
    hooksecurefunc(C_PetBattles, "ForfeitGame", function()
        playerDidForfeit = true
        Debug("Player forfeited")
    end)
end

-- ===================== Death cache =====================
local function TeamFullyDead(teamId)
    local n = C_PetBattles.GetNumPets(teamId) or 0
    if n == 0 then return nil end
    for i = 1, n do
        if not C_PetBattles.IsPetDead(teamId, i) then return false end
    end
    return true
end

local function RefreshDeathCache()
    local ok1, ed = pcall(TeamFullyDead, Enum.BattlePetOwner.Enemy)
    if ok1 and ed ~= nil then lastKnownEnemyDead = ed end
    local ok2, ad = pcall(TeamFullyDead, Enum.BattlePetOwner.Ally)
    if ok2 and ad ~= nil then lastKnownAllyDead = ad end
end

-- ===================== PvP detection =====================
local function DetectPVP()
    local ok1, isPvp = pcall(function() return C_PetBattles.IsPVPMatch() end)
    if ok1 and isPvp then return true end

    local ok2, isWild = pcall(function() return C_PetBattles.IsWildBattle() end)
    if ok2 and isWild then return false end

    local ok3, isNPC = pcall(function() return C_PetBattles.IsPlayerNPC(Enum.BattlePetOwner.Enemy) end)
    if ok3 and not isNPC then return true end

    return false
end

-- ===================== Opponent identity =====================
local function IsEnemyPetRenamed(idx)
    local okS, speciesID = pcall(C_PetBattles.GetPetSpeciesID, Enum.BattlePetOwner.Enemy, idx)
    if not okS or not speciesID or speciesID == 0 then return false, nil end

    local okN, displayName = pcall(C_PetBattles.GetName, Enum.BattlePetOwner.Enemy, idx)
    if not okN or not displayName or displayName == "" then return false, nil end

    if C_PetJournal and C_PetJournal.GetPetInfoBySpeciesID then
        local defaultName = select(1, C_PetJournal.GetPetInfoBySpeciesID(speciesID))
        if defaultName and defaultName ~= displayName then
            return true, displayName
        end
    end
    return false, displayName
end

local function ResolveOpponent()
    local L = BPS_GetLocale()
    local renamed1, name1 = IsEnemyPetRenamed(1)
    if renamed1 and name1 then return name1, name1 end

    local renamed2 = select(1, IsEnemyPetRenamed(2))
    local renamed3 = select(1, IsEnemyPetRenamed(3))

    if not renamed1 and not renamed2 and not renamed3 then
        local abbrevs = {}
        for i = 1, 3 do
            local ok, n = pcall(C_PetBattles.GetName, Enum.BattlePetOwner.Enemy, i)
            if ok and n and n ~= "" then
                table.insert(abbrevs, n:sub(1, 3):upper())
            end
        end
        local label = string.format("%s (%s)", L.VS_RANDOM, table.concat(abbrevs, "/"))
        return label, label
    end

    local ok, n = pcall(C_PetBattles.GetName, Enum.BattlePetOwner.Enemy, 1)
    if ok and n then return n, n end
    return nil, nil
end

local function TryGrabOpponent()
    if not battleIsPVP then
        if DetectPVP() then battleIsPVP = true else return end
    end
    if currentOpponentKey then return end
    local key, display = ResolveOpponent()
    if key then
        currentOpponentKey = key
        currentOpponentDisplayName = display
        Debug("Opponent: " .. key)
        if BPS_ShowBattleFrame then BPS_ShowBattleFrame() end
    end
end

-- ===================== Win detection via achievements =====================
-- Primary method: compare achievement criteria count before and after battle.
-- Fallback: death state / forfeit (for battles where we're post-max-achievement).

local function ResolveWinLoss()
    -- 1) Try achievement snapshot comparison (most reliable)
    local achievementResult = BPS_DetectWinFromAchievements()
    if achievementResult ~= nil then
        Debug("Win detection via achievement snapshot: " .. tostring(achievementResult))
        return achievementResult
    end

    Debug("Achievement snapshot unchanged, falling back to death/forfeit detection")

    -- 2) Fallback: death state
    local ed = lastKnownEnemyDead
    local ad = lastKnownAllyDead

    if ed and not ad then return true  end
    if ad and not ed then return false end
    if ed and ad       then return nil  end  -- double KO

    -- 3) Forfeit fallback (nobody died)
    Debug("Forfeit fallback: playerForfeit=" .. tostring(playerDidForfeit))
    return not playerDidForfeit
end

-- ===================== Event handling =====================
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("PLAYER_LOGOUT")
frame:RegisterEvent("PET_BATTLE_OPENING_START")
frame:RegisterEvent("PET_BATTLE_OPENING_DONE")
frame:RegisterEvent("PET_BATTLE_HEALTH_CHANGED")
frame:RegisterEvent("PET_BATTLE_PET_ROUND_RESULTS")
frame:RegisterEvent("PET_BATTLE_OVER")
frame:RegisterEvent("PET_BATTLE_CLOSE")

frame:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        if (...) == "BattlePetStatistics" then
            EnsureDB()
            print("|cff33ff99Battle Pet Statistics|r loaded. /bps to open options.")
        end

    elseif event == "PLAYER_ENTERING_WORLD" then
        EnsureDB()
        if BPS_MaybeShowSetupPrompt then
            C_Timer.After(3, BPS_MaybeShowSetupPrompt)
        end

    elseif event == "PLAYER_LOGOUT" then
        -- Session totals are in-memory only; they reset automatically on next login.
        -- Nothing to do here — we explicitly do NOT save them to DB.
        Debug("Logout: session was " .. sessionWins .. "W / " .. sessionLosses .. "L")
        if BattlePetStatisticsDB and BattlePetStatisticsDB.restoreNamesOnLogout and BPS_RestoreOwnPetNames then
            BPS_RestoreOwnPetNames()
        end

    elseif event == "PET_BATTLE_OPENING_START" then
        currentOpponentKey = nil
        currentOpponentDisplayName = nil
        battleIsPVP = false
        battleResolved = false
        playerDidForfeit = false
        lastKnownEnemyDead = false
        lastKnownAllyDead = false

        -- Snapshot achievements BEFORE the battle counts anything
        BPS_SnapshotAchievements()
        Debug("Achievement snapshot taken")

        if DetectPVP() then
            battleIsPVP = true
            TryGrabOpponent()

            -- A "session" only counts once the player has started at least 2
            -- PvP pet battles during this login.
            pvpBattlesStartedThisLogin = pvpBattlesStartedThisLogin + 1
            if pvpBattlesStartedThisLogin >= 2 and not sessionCounted then
                sessionCounted = true
                EnsureDB()
                BattlePetStatisticsDB.sessionCount = (BattlePetStatisticsDB.sessionCount or 0) + 1
            end
        end

    elseif event == "PET_BATTLE_OPENING_DONE" then
        TryGrabOpponent()
        RefreshDeathCache()

    elseif event == "PET_BATTLE_HEALTH_CHANGED" or event == "PET_BATTLE_PET_ROUND_RESULTS" then
        if battleIsPVP then RefreshDeathCache() end

    elseif event == "PET_BATTLE_OVER" then
        if battleIsPVP and not currentOpponentKey then
            TryGrabOpponent()
        end
        RefreshDeathCache()

        if battleIsPVP and not battleResolved and currentOpponentKey then
            -- Capture locally: PET_BATTLE_CLOSE can fire (and reset the
            -- module-level currentOpponentKey to nil) before this delayed
            -- callback runs, especially after a forfeit which closes the
            -- battle UI almost immediately.
            local opponentKeyAtResolution = currentOpponentKey

            -- Give the server a short moment to update achievement criteria
            -- before we compare snapshot vs current counts.
            C_Timer.After(1.5, function()
                if battleResolved then return end  -- guard against double-fire
                battleResolved = true

                local won = ResolveWinLoss()
                if won ~= nil then
                    RecordResult(opponentKeyAtResolution, won)
                    -- Check if a goal was just completed and fire the celebration if needed
                    local completedGoal = BPS_AutoAdvanceGoal()
                    if completedGoal and BPS_OnGoalCompleted then
                        BPS_OnGoalCompleted(completedGoal)
                    end
                end

                BPS_RenameOwnPetsForTradeWin()

                -- "after_first_battle" mode: this fires once the very first
                -- PvP battle of the login session has just resolved.
                if pvpBattlesStartedThisLogin == 1 and BPS_MaybeShowAliasAfterBattle then
                    BPS_MaybeShowAliasAfterBattle()
                end

                if BPS_UpdateBattleFrame  then BPS_UpdateBattleFrame()  end
                if BPS_ShowRequeueButton  then BPS_ShowRequeueButton()   end
            end)
        end

    elseif event == "PET_BATTLE_CLOSE" then
        battleIsPVP = false
        currentOpponentKey = nil
        currentOpponentDisplayName = nil
        if BPS_HideBattleFrame then BPS_HideBattleFrame() end
    end
end)

-- ===================== Keybinding handlers =====================
function BPS_Binding_Forfeit()
    if C_PetBattles.IsInBattle and C_PetBattles.IsInBattle() then
        C_PetBattles.ForfeitGame()
    end
end
function BPS_Binding_Requeue()
    pcall(C_PetBattles.StartPVPMatchmaking)
end
function BPS_Binding_EnterBattle()
    pcall(C_PetBattles.AcceptQueuedPVPMatch)
end

-- ===================== Slash commands =====================
SLASH_BPS1 = "/bps"
SLASH_BPS2 = "/petstats"
SlashCmdList["BPS"] = function(msg)
    msg = msg and msg:lower():trim() or ""
    if msg == "debug" then
        debugMode = not debugMode
        print("|cff33ff99BPS|r debug: " .. (debugMode and "ON" or "OFF"))
    elseif msg == "setup" then
        if BPS_ForceShowAliasPopup then BPS_ForceShowAliasPopup() end
    elseif msg == "test" then
        if BPS_ShowBattleFrame then BPS_ShowBattleFrame() end
    elseif msg == "achcheck" then
        print("|cff33ff99BPS|r: Achievement diagnostic --")
        for _, goal in ipairs(BPS_GOALS) do
            local id, name, _, completed, _, _, _, _, _, _, _, wasEarnedByMe, earnedBy =
                GetAchievementInfo(goal.achID)
            local _, _, critCompleted, quantity, reqQuantity =
                GetAchievementCriteriaInfo(goal.achID, 1)
            print(string.format(
                "  [%d] %s | completed=%s wasEarnedByMe=%s earnedBy=%s | crit: completed=%s quantity=%s/%s",
                goal.achID, tostring(name), tostring(completed), tostring(wasEarnedByMe),
                tostring(earnedBy), tostring(critCompleted), tostring(quantity), tostring(reqQuantity)
            ))
        end
    else
        if BPS_ToggleOptions then BPS_ToggleOptions() end
    end
end
