-- ╔============================================================══╗
-- ║     DAS BUCH DER GEFALLENEN v9.5.0                          ║
-- ║     The Book of the Fallen                                   ║
-- ║     Classic Hardcore Edition                                 ║
-- ║     Autor: PsYke86                                           ║
-- ╚============================================================══╝

local addonName, addon = ...

-- Globale Addon-Tabelle initialisieren
local GDL = {}
addon.GDL = GDL
_G["GuildDeathLog"] = GDL
_G["GuildDeathLogDB"] = _G["GuildDeathLogDB"] or {}

-- ============================================================══
-- SUPPORTER (Freiwillige Unterstützung) - Code Redeem
-- Hinweis: Addons können Patreon/PayPal NICHT direkt prüfen.
-- Der Code muss extern vergeben werden (Discord-Bot o.ä.).
-- ============================================================══

-- SUPPORT_SECRET und ALPHABET werden weiter unten definiert (bei getMonthlyToken)

-- ── Bit-Arithmetik (WoW Classic: bit.bxor/rshift built-in) ──
local bxor   = bit.bxor
local rshift = bit.rshift
local MAXINT = 4294967296  -- 2^32

-- Sichere 32-bit Multiplikation (Lua double: max 2^53, brauchen bis 2^56)
local function mul32(a, b)
    local al = a % 65536
    local ah = math.floor(a / 65536) % 65536
    return ((al * b) % MAXINT + ((ah * b) % MAXINT) * 65536) % MAXINT
end

-- FNV-1a Hash (32-bit, identisch mit Python-Bot)
local function strHash(s)
    local h = 2166136261
    for i = 1, #s do
        h = bxor(h, string.byte(s, i))
        h = mul32(h, 16777619)
    end
    return h
end

-- Mix-Funktion
local function mix(n)
    n = bxor(n, rshift(n, 15))
    n = mul32(n, 2246822519)
    n = bxor(n, rshift(n, 13))
    n = mul32(n, 3266489917)
    n = bxor(n, rshift(n, 16))
    return n % MAXINT
end

-- ── Supporter Code ────────────────────────────────────────────
local SUPPORT_SECRET = "GDL6x!9pK#mR2vQ8"
local ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"

-- Gueltige Tier-Codes und ihre Prioritaet (hoeher = mehr Features)
local TIER_INFO = {
    ["B"] = { priority = 10, label = "Bronze",  color = "|cffCD7F32" },
    ["S"] = { priority = 20, label = "Silber",  color = "|cffC0C0C0" },
    ["G"] = { priority = 30, label = "Gold",    color = "|cffFFD700" },
}

-- discordIdStr MUSS String sein (Discord IDs > 2^53, Lua double verliert Praezision)
-- tierCode: "" fuer Legacy, "B"/"S"/"G" fuer neues Format
local function getMonthlyToken(yyyymm, discordIdStr, tierCode)
    tierCode = tierCode or ""
    local seed = strHash(SUPPORT_SECRET .. tierCode .. yyyymm .. discordIdStr .. string.reverse(SUPPORT_SECRET))
    local n = seed
    local token = ""
    for _ = 1, 8 do
        n = mix(n + seed)
        local idx = (n % 32) + 1
        token = token .. ALPHABET:sub(idx, idx)
    end
    return token
end

local function getChecksum(yyyymm, token, discordIdStr, tierCode)
    tierCode = tierCode or ""
    local raw = tierCode .. yyyymm .. token .. discordIdStr .. SUPPORT_SECRET
    return strHash(raw) % 1000000
end

function GDL:ValidateSupporterCode(code)
    if not code or code == "" then
        return false, "Kein Code eingegeben"
    end

    code = code:upper():gsub("%s", "")

    local prefix, tierCode, yyyymm, token, cc

    -- Neues Format: GDL-B-YYYYMM-TTTTTTTT-CCCCCC (v4)
    prefix, tierCode, yyyymm, token, cc = code:match("^(%u+)%-([BSG])%-(%d%d%d%d%d%d)%-([%u%d][%u%d][%u%d][%u%d][%u%d][%u%d][%u%d][%u%d])%-(%d%d%d%d%d%d)$")

    -- Legacy-Format: GDL-YYYYMM-TTTTTTTT-CCCCCC (v3)
    if not yyyymm then
        yyyymm, token, cc = code:match("^GDL%-(%d%d%d%d%d%d)%-([%u%d][%u%d][%u%d][%u%d][%u%d][%u%d][%u%d][%u%d])%-(%d%d%d%d%d%d)$")
        if yyyymm then
            prefix   = "GDL"
            tierCode = ""  -- Legacy hat keinen Tier-Code im Hash
        end
    end

    if not yyyymm then
        return false, "Ungueltiger Code. Format: GDL-B-YYYYMM-TTTTTTTT-CCCCCC"
    end

    -- Nur GDL-Codes akzeptieren (andere Addons haben eigene Secrets)
    if prefix ~= "GDL" then
        return false, "Dieser Code ist nicht fuer Guild Chronicle"
    end

    -- Ablauf pruefen
    local codeYear  = tonumber(yyyymm:sub(1, 4))
    local codeMonth = tonumber(yyyymm:sub(5, 6))
    local y, m = tonumber(date("%Y")), tonumber(date("%m"))
    if codeYear < y or (codeYear == y and codeMonth < m) then
        return false, "Code abgelaufen (" .. yyyymm:sub(1,4) .. "/" .. yyyymm:sub(5,6) .. ")"
    end

    -- Discord-ID als STRING laden
    local discordIdStr = GuildDeathLogDB and GuildDeathLogDB.discordId
    if not discordIdStr or discordIdStr == "" then
        return false, "Discord-ID nicht gesetzt. Eingabe: /gdl discord DEINE_ID"
    end

    -- Token pruefen
    local expectedToken = getMonthlyToken(yyyymm, discordIdStr, tierCode)
    if token ~= expectedToken then
        return false, "Code passt nicht zu deiner Discord-ID"
    end

    -- Pruefziffer
    local expectedCC = getChecksum(yyyymm, token, discordIdStr, tierCode)
    if tonumber(cc) ~= expectedCC then
        return false, "Ungueltige Pruefziffer"
    end

    -- Tier-Info zurueckgeben
    local tier = TIER_INFO[tierCode] or TIER_INFO["B"]
    return true, yyyymm, tierCode, tier
end

function GDL:RedeemSupporterCode(code)
    local ok, result, tierCode, tierInfo = self:ValidateSupporterCode(code)
    if not ok then
        print("|cffFF4444[GDL]|r Ungueltiger Code: " .. result)
        return false, result
    end

    local yyyymm = result
    tierCode = tierCode or "B"
    tierInfo = tierInfo or TIER_INFO["B"]
    local expYear  = tonumber(yyyymm:sub(1,4))
    local expMonth = tonumber(yyyymm:sub(5,6))

    -- Ablaufdatum: letzter Tag des Monats
    local daysInMonth = {31,28,31,30,31,30,31,31,30,31,30,31}
    if expMonth == 2 and (expYear % 4 == 0 and (expYear % 100 ~= 0 or expYear % 400 == 0)) then
        daysInMonth[2] = 29
    end
    local expDay = daysInMonth[expMonth]

    -- Speichern (inkl. Tier-Info)
    GuildDeathLogDB.supporterCode    = code
    GuildDeathLogDB.supporterExpires = string.format("%04d-%02d-%02d", expYear, expMonth, expDay)
    GuildDeathLogDB.supporterActive  = true
    GuildDeathLogDB.supporterTier    = tierCode
    GuildDeathLogDB.supporterPriority = tierInfo.priority

    local tierLabel = tierInfo.label or "Supporter"
    local tierColor = tierInfo.color or "|cffFFD100"
    print(string.format("%s[GDL]|r [OK] %s aktiv bis %02d.%02d.%04d - Vielen Dank! <3",
        tierColor, tierLabel, expDay, expMonth, expYear))

    -- Titel-Modul live aktivieren (kein Relog nötig)
    local Titles = GDL:GetModule("Titles")
    if Titles and Titles.initialized then
        Titles.initialized = false
        C_Timer.After(0.5, function()
            Titles:Initialize()
            if Titles.RefreshAllNameplateTitles then
                C_Timer.After(1, function() Titles:RefreshAllNameplateTitles() end)
            end
        end)
    end

    -- UI aktualisieren falls offen
    if GDL.UpdateSupporterStatus then GDL:UpdateSupporterStatus() end
    if GDL.UpdateMilestones then GDL:UpdateMilestones() end

    return true, string.format("Supporter+ aktiv bis %02d.%02d.%04d - Danke! <3", expDay, expMonth, expYear)
end

function GDL:IsSupporterActive()
    if not GuildDeathLogDB.supporterActive or not GuildDeathLogDB.supporterExpires then
        return false
    end
    local expY, expM, expD = GuildDeathLogDB.supporterExpires:match("(%d+)-(%d+)-(%d+)")
    if not expY then return false end
    local y  = tonumber(date("%Y"))
    local m  = tonumber(date("%m"))
    local d  = tonumber(date("%d"))
    expY, expM, expD = tonumber(expY), tonumber(expM), tonumber(expD)
    if y > expY then return false end
    if y == expY and m > expM then return false end
    if y == expY and m == expM and d > expD then return false end
    return true
end

function GDL:GetSupporterExpiry()
    if not GuildDeathLogDB.supporterExpires then return nil end
    local expY, expM, expD = GuildDeathLogDB.supporterExpires:match("(%d+)-(%d+)-(%d+)")
    if not expY then return nil end
    return string.format("%02d.%02d.%04d", tonumber(expD), tonumber(expM), tonumber(expY))
end

-- Gibt den aktiven Tier-Code zurueck ("B", "S", "G") oder nil
function GDL:GetSupporterTier()
    if not self:IsSupporterActive() then return nil end
    return GuildDeathLogDB.supporterTier or "B"  -- Legacy = Bronze
end

-- Gibt die Tier-Prioritaet zurueck (10=Bronze, 20=Silber, 30=Gold, 0=kein Supporter)
function GDL:GetSupporterPriority()
    if not self:IsSupporterActive() then return 0 end
    return GuildDeathLogDB.supporterPriority or 10
end

-- Gibt Tier-Label und Farbe zurueck
function GDL:GetSupporterTierInfo()
    local code = self:GetSupporterTier()
    if not code then return nil end
    return TIER_INFO[code] or TIER_INFO["B"]
end

-- Prueft ob der aktive Tier mindestens den geforderten Level hat
-- minTier: "B", "S", oder "G"
function GDL:HasSupporterTier(minTier)
    if not self:IsSupporterActive() then return false end
    local required = TIER_INFO[minTier]
    if not required then return false end
    local current = self:GetSupporterPriority()
    return current >= required.priority
end

-- Supporter-Feature pruefen + optionale Meldung ausgeben
function GDL:RequireSupporter(minTier)
    minTier = minTier or "B"
    if self:HasSupporterTier(minTier) then return true end
    local isDE = GetLocale() == "deDE"
    local tierLabel = TIER_INFO[minTier] and TIER_INFO[minTier].label or "Supporter"
    if not self:IsSupporterActive() then
        self:Print(isDE
            and "|cffFF8800Supporter-Feature:|r Bitte einen gueltigen Supporter-Code eingeben (/gdl redeem CODE)."
            or  "|cffFF8800Supporter feature:|r Please enter a valid supporter code (/gdl redeem CODE).")
    else
        self:Print(isDE
            and ("|cffFF8800" .. tierLabel .. "-Feature:|r Dieses Feature erfordert mindestens " .. tierLabel .. "-Status.")
            or  ("|cffFF8800" .. tierLabel .. " feature:|r This feature requires at least " .. tierLabel .. " tier."))
    end
    return false
end


-- Compat-Layer Daten uebernehmen (aus Compat.lua via addon._compat)
-- WICHTIG: addon._compat NICHT auf nil setzen! Diese Datei laedt VOR den
-- Modulen, und mindestens Deathlog.lua und GuildCompetition.lua lesen
-- addon._compat zur Ladezeit (file-scope). Ein nil hier wuerde deren
-- Compat-Wrapper stillschweigend auf die Fallback-Pfade degradieren.
if addon._compat then
    for k, v in pairs(addon._compat) do
        GDL[k] = v
    end
end

-- Version IMMER aus der TOC-Metadata lesen -> eine einzige Quelle, kein Desync mehr.
local _getMeta = (C_AddOns and C_AddOns.GetAddOnMetadata) or GetAddOnMetadata
GDL.version = (_getMeta and _getMeta(addonName, "Version")) or "9.17.0"
GDL.addonName = addonName
GDL.modules = {}
GDL.currentGuildName = nil
GDL.playerName = nil
GDL.playerRealm = nil

-- Expansion-String im Addon-Header (aus Compat.lua uebernommen)
GDL.expansionLabel = GDL.expansionName or "Classic"

-- ============================================================══
-- CORE FUNKTIONEN
-- ============================================================══

function GDL:Print(msg)
    print("|cffFFD100[Chronicle]|r " .. (msg or ""))
end

function GDL:Debug(msg)
    -- An Debug-Modul senden (fuer Debug-Fenster)
    local Debug = self.modules["Debug"]
    if Debug and Debug.Log then
        Debug:Log("DEBUG", msg, "AAAAAA")
    end

    -- Persistent ins Logbuch (zeitgenau, ueberlebt Reloads)
    if self.Log then self:Log("DEBUG", msg) end

    -- Im Chat anzeigen wenn aktiviert
    if GuildDeathLogDB.settings and GuildDeathLogDB.settings.debugPrint then
        print("|cffAAAAAA[GDL-DEBUG]|r " .. (msg or ""))
    end
end

function GDL:RegisterModule(name, module)
    if not name or not module then return end
    if self.modules[name] then return end
    self.modules[name] = module
end

function GDL:GetModule(name)
    return self.modules[name]
end

-- Fallback L() Funktion bis Locale geladen ist
function GDL:L(key)
    local Locale = self.modules["Locale"]
    if Locale and Locale.L and Locale.L[key] then
        return Locale.L[key]
    end
    return key or ""
end

function GDL:GetClassName(classId)
    local Locale = self.modules["Locale"]
    if Locale and Locale.L and Locale.L.CLASSES then
        return Locale.L.CLASSES[classId] or "Unknown"
    end
    return "Unknown"
end

-- Prüft ob Spieler Gilden-Offizier ist (Rang 0-2)
function GDL:IsGuildOfficer()
    if not IsInGuild() then return false end
    local _, _, rankIndex = GetGuildInfo("player")
    return rankIndex and rankIndex <= 2  -- 0 = GM, 1 = Erster Offizier, 2 = Offizier
end

-- Passwort verifizieren
function GDL:VerifyPassword(inputPassword)
    if not GuildDeathLogDB.adminPassword then
        return false, "Kein Passwort gesetzt"
    end
    return inputPassword == GuildDeathLogDB.adminPassword, nil
end

-- Tod aus der Liste löschen
function GDL:DeleteDeath(deathIndex)
    local guildData = self:GetGuildData()
    if not guildData or not guildData.deaths then return false end
    
    if deathIndex < 1 or deathIndex > #guildData.deaths then
        return false
    end
    
    local death = guildData.deaths[deathIndex]
    local deathName = death and death.name or "Unbekannt"
    local deathTimestamp = death and death.timestamp or 0
    
    table.remove(guildData.deaths, deathIndex)
    self:Print("|cffFF6666" .. deathName .. "|r wurde aus der Liste entfernt.")
    
    -- DELETE an andere Gildenmitglieder syncen!
    local Sync = self:GetModule("Sync")
    if Sync and Sync.BroadcastDelete then
        Sync:BroadcastDelete(deathName, deathTimestamp)
        self:Print("|cff888888Loeschung wird an Gildenmitglieder gesendet...|r")
    end
    
    -- UI aktualisieren
    local UI = self:GetModule("UI")
    if UI and UI.mainFrame and UI.mainFrame:IsShown() then
        UI:UpdateChronicle()
    end
    
    return true
end

-- Loeschen per IDENTITAET (Name + Zeitstempel) statt per Index.
-- Die Buchliste ist nach Zeit sortiert -> ein Anzeige-Index passt NICHT zur
-- unsortierten guildData.deaths. Per Identitaet trifft es immer den richtigen.
function GDL:DeleteDeathByIdentity(name, timestamp)
    local guildData = self:GetGuildData()
    if not guildData or not guildData.deaths then return false end
    local nLow = (name or ""):lower()
    local ts   = timestamp or 0
    for i, d in ipairs(guildData.deaths) do
        if ((d.name or ""):lower() == nLow) and ((d.timestamp or 0) == ts) then
            table.remove(guildData.deaths, i)
            self:Print("|cffFF6666" .. (name or "?") .. "|r wurde aus der Liste entfernt.")
            local Sync = self:GetModule("Sync")
            if Sync then
                -- lokal blacklisten, damit der Eintrag beim naechsten Resync
                -- nicht wieder hereinkommt
                if Sync.AddToBlacklist then Sync:AddToBlacklist(name, ts) end
                if Sync.BroadcastDelete then
                    Sync:BroadcastDelete(name, ts)
                    self:Print("|cff888888Loeschung wird an Gildenmitglieder gesendet...|r")
                end
            end
            local UI = self:GetModule("UI")
            if UI and UI.mainFrame and UI.mainFrame:IsShown() then
                UI:UpdateChronicle()
            end
            return true
        end
    end
    self:Print("|cffFF8800Eintrag nicht gefunden (evtl. schon entfernt).|r")
    return false
end

function GDL:FireModuleEvent(event, ...)
    for name, module in pairs(self.modules) do
        if module and module.OnEvent then
            pcall(module.OnEvent, module, event, ...)
        end
    end
end

function GDL:InitializeModules()
    for name, module in pairs(self.modules) do
        if module and module.Initialize then
            local ok, err = pcall(module.Initialize, module)
            if not ok then
                self:Print("|cffFF0000Fehler in " .. name .. ":|r " .. (err or "unknown"))
            end
        end
    end
end

-- ============================================================══
-- DATENBANK
-- ============================================================══

function GDL:InitDB()
    GuildDeathLogDB.guilds = GuildDeathLogDB.guilds or {}
    GuildDeathLogDB.settings = GuildDeathLogDB.settings or {}
    GuildDeathLogDB.adminPassword = GuildDeathLogDB.adminPassword or nil -- Gildenleiter-Passwort
    
    -- Version Check fuer Migration
    local currentVersion = GDL.version
    local savedVersion = GuildDeathLogDB.addonVersion or "0"
    
    -- BUG FIX: Settings NICHT mehr ueberschreiben bei Update!
    -- Nur bei komplett neuer Installation (Version "0") Defaults setzen
    if savedVersion == "0" then
        -- Erste Installation - Defaults setzen
        GuildDeathLogDB.settings.announce = true
        GuildDeathLogDB.settings.sound = true
        GuildDeathLogDB.settings.overlay = true
        GuildDeathLogDB.settings.mapMarkers = true
        GuildDeathLogDB.settings.guildTracker = true
        GuildDeathLogDB.settings.useBlizzardChannel = true
        GuildDeathLogDB.settings.useAddonChannel = true
        GuildDeathLogDB.settings.debugPrint = false
        GuildDeathLogDB.settings.milestoneAnnounce = true
        GuildDeathLogDB.settings.milestonePopup = true
    end
    
    if savedVersion ~= currentVersion then
        GuildDeathLogDB.addonVersion = currentVersion
                print("|cffFFD100[Chronicle]|r Guild Chronicle " .. (currentVersion or "") .. " geladen.")
    end
    
    -- Sicherstellen dass alle Keys existieren (OHNE bestehende zu ueberschreiben!)
    local defaults = {
        announce = true,
        sound = true,
        overlay = true,
        mapMarkers = true,
        guildTracker = true,
        nameplateTitles = true,
        useBlizzardChannel = true,
        useAddonChannel = true,
        debugPrint = false,
        overlayScale = 1.0,
        milestoneAnnounce = true,  -- NEU: Meilenstein-Chat-Nachrichten
        milestonePopup = true,     -- NEU: Meilenstein-Popup
    }
    
    for key, defaultValue in pairs(defaults) do
        if GuildDeathLogDB.settings[key] == nil then
            GuildDeathLogDB.settings[key] = defaultValue
        end
    end
    
    GuildDeathLogDB.syncUsers = GuildDeathLogDB.syncUsers or {}
    GuildDeathLogDB.lastSyncTimes = GuildDeathLogDB.lastSyncTimes or {}
end

function GDL:GetGuildData(guildName)
    guildName = guildName or self.currentGuildName
    if not guildName then return nil end
    
    if not GuildDeathLogDB.guilds[guildName] then
        GuildDeathLogDB.guilds[guildName] = {
            deaths = {},
            members = {},
            pendingSync = {},
            deletedDeaths = {},  -- WICHTIG: Blacklist fuer geloeschte Eintraege!
        }
    end
    
    -- Migration: Falls deletedDeaths fehlt bei alten Gilden
    if not GuildDeathLogDB.guilds[guildName].deletedDeaths then
        GuildDeathLogDB.guilds[guildName].deletedDeaths = {}
    end
    -- Migration: memberHistory (GRM-Pattern, seit v7.4.0)
    if not GuildDeathLogDB.guilds[guildName].memberHistory then
        GuildDeathLogDB.guilds[guildName].memberHistory = {}
    end
    
    return GuildDeathLogDB.guilds[guildName]
end

-- ============================================================══
-- HAUPT-EVENT-HANDLER
-- ============================================================══

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("PLAYER_LOGOUT")

eventFrame:SetScript("OnEvent", function(_, event, arg1)
    if event == "ADDON_LOADED" and arg1 == addonName then
        GDL:InitDB()
        GDL.playerName = UnitName("player")
        GDL.playerRealm = GetRealmName()
        
        C_Timer.After(0.5, function()
            GDL:InitializeModules()
        end)
        
        -- Eine einzige schöne Startup-Meldung mit Expansion-Info
        local expColor = GDL.isTBC and "|cffFF8C00" or (GDL.isWrath and "|cff88AABB" or "|cffFFD100")
        GDL:Print("|cffFFD100Buch der Gefallenen:|r |cff00FF00Aktiv|r |cff888888(v" .. GDL.version .. ")|r " .. expColor .. "[" .. (GDL.expansionLabel or "Classic") .. "]|r")
        
    elseif event == "PLAYER_ENTERING_WORLD" then
        C_Timer.After(2, function()
            GDL:FireModuleEvent("PLAYER_READY")
        end)
        
    elseif event == "PLAYER_LOGOUT" then
        GDL:FireModuleEvent("PLAYER_LOGOUT")
    end
    
    GDL:FireModuleEvent(event, arg1)
end)

-- ============================================================══
-- SLASH COMMANDS (Dispatch via SlashCommands.lua)
-- ============================================================══

SLASH_GDL1, SLASH_GDL2, SLASH_GDL3, SLASH_GDL4, SLASH_GDL5 = "/gdl", "/chronicle", "/gc", "/buch", "/book"

SlashCmdList["GDL"] = function(msg)
    GDL:HandleSlashCommand(msg)
end
