-- ============================================================
-- Asylum Compagnon — GearCompassDatabase.lua
-- Migration ciblée et paramètres persistants minimaux de l'export GearCompass.
-- ============================================================

local _, NS = ...

local DB = {}
NS.GearCompassDB = DB

local JSON = NS.GearCompassJSON
local Checksum = NS.GearCompassChecksum

local DEFAULT_SETTINGS = {
    includeEquipped = true,
    includeBags = true,
    includeNonEquippable = false,
}
DB.DEFAULT_SETTINGS = DEFAULT_SETTINGS

local function ExportVersionAtLeast(value, requiredMajor, requiredMinor, requiredPatch)
    if type(value) ~= "string" then return false end
    local major, minor, patch = value:match(
        '"addon":%{"name":"AsylumCompagnon","version":"(%d+)%.(%d+)%.(%d+)"')
    major, minor, patch = tonumber(major), tonumber(minor), tonumber(patch)
    if not major or not minor or not patch then return false end
    if major ~= requiredMajor then return major > requiredMajor end
    if minor ~= requiredMinor then return minor > requiredMinor end
    return patch >= requiredPatch
end

local debugEnabled = false
local LEGACY_DB_KEY = "tank" .. "gear"

local function IsCurrentExport(value)
    if type(value) ~= "string" then return false end
    -- Le site accepte encore schema v1, mais l'addon ne doit pas réafficher un
    -- ancien export comme s'il contenait les domaines structurés du contrat v2.
    local hasCharacterStats = value:find('"character_stats":', 1, true) ~= nil
    local hasPermanentSource = value:find(
        '"character_stats_source":"equipped_items_and_gems"', 1, true) ~= nil
    local hasCanonicalMeta = value:find('"character_stats_meta":{', 1, true) ~= nil
        and value:find('"method":"equipped_items_and_gems"', 1, true) ~= nil
        and value:find('"includes_enchantments":false', 1, true) ~= nil
        and value:find('"includes_temporary_auras":false', 1, true) ~= nil
        and value:find('"includes_gems":', 1, true) ~= nil
        and value:find('"complete":', 1, true) ~= nil
        and value:find('"unresolved_gems":', 1, true) ~= nil
    local hasV2Domains = value:find('"client":', 1, true) ~= nil
        and value:find('"resources":', 1, true) ~= nil
        and value:find('"upgrade_state":', 1, true) ~= nil
        and value:find('"weekly_rewards":', 1, true) ~= nil
        and value:find('"collection_status":', 1, true) ~= nil
    local checksumValid = Checksum and Checksum.Verify
        and select(1, Checksum.Verify(value)) == true
    -- Depuis 0.46.5, aucun `|` littéral ne doit subsister dans le JSON brut :
    -- il serait interprété comme du balisage WoW par l'EditBox puis perdu à la
    -- copie. Les anciens exports restent valides mathématiquement, mais ne sont
    -- plus sûrs à copier et doivent donc être régénérés.
    local wowEditBoxSafe = value:find("|", 1, true) == nil
    -- La 0.46.8 ajoute l'identité structurée des gemmes et l'état explicite de
    -- la sortie catalyseur. Un ancien cache resterait valide au checksum mais ne
    -- respecterait pas ce contrat additif : imposer une régénération unique.
    local hasGemAndCatalystIdentity = ExportVersionAtLeast(value, 0, 46, 8)
    local hasProspectiveHistory = ExportVersionAtLeast(value, 0, 47, 0)
        and value:find('"bonus_roll_history":[', 1, true) ~= nil
        and value:find('"bonus_roll_history_complete":false', 1, true) ~= nil
        and value:find('"bonus_roll_history_scope":"prospective"', 1, true) ~= nil
        and value:find('"bonus_roll_history_started_at":', 1, true) ~= nil
    -- La 0.47.2 complète weekly_rewards avec les runs M+ factuels de la semaine.
    -- Un cache plus ancien doit être régénéré pour ne pas laisser croire au
    -- site qu'une liste absente signifie qu'aucune clé n'a été terminée.
    local hasWeeklyMythicHistory = ExportVersionAtLeast(value, 0, 47, 2)
        and value:find('"mythic_plus_runs":[', 1, true) ~= nil
        and value:find('"mythic_plus_runs_meta":{', 1, true) ~= nil
        and value:find('"scope":"current_week_completed"', 1, true) ~= nil
    return hasCharacterStats and hasPermanentSource and hasCanonicalMeta
        and hasV2Domains and checksumValid and wowEditBoxSafe
        and hasGemAndCatalystIdentity and hasProspectiveHistory
        and hasWeeklyMythicHistory
        and value:find('"integrity":{', 1, true) ~= nil
        and value:find('"export_type":"gearcompass"', 1, true) ~= nil
        and value:find('"schema_version":2', 1, true) ~= nil
        and value:find('"addon":', 1, true) ~= nil
        and value:find('"character":', 1, true) ~= nil
        and value:find('"errors":', 1, true) ~= nil
        and value:find('"items":', 1, true) ~= nil
        and value:find('"game":', 1, true) == nil
        and value:find('"scan":', 1, true) == nil
        and value:find('"adjustments":', 1, true) == nil
end
DB.IsCurrentExport = IsCurrentExport

function DB.Migrate(root)
    if type(root) ~= "table" then return nil, "DB_ROOT_MISSING" end
    -- Reprend une seule fois les préférences enregistrées sous l'ancien nom,
    -- puis supprime cette clé afin que seules les données GearCompass subsistent.
    if type(root.gearcompass) ~= "table" then
        root.gearcompass = type(root[LEGACY_DB_KEY]) == "table" and root[LEGACY_DB_KEY] or {}
    end
    root[LEGACY_DB_KEY] = nil
    local gearcompass = root.gearcompass

    if type(gearcompass.settings) ~= "table" then gearcompass.settings = {} end
    for key, value in pairs(DEFAULT_SETTINGS) do
        if gearcompass.settings[key] == nil then gearcompass.settings[key] = value end
    end
    for key in pairs(gearcompass.settings) do
        if DEFAULT_SETTINGS[key] == nil then gearcompass.settings[key] = nil end
    end
    -- L'historique des jets est volontairement une SavedVariable par
    -- personnage. Une ancienne copie au niveau du compte ne peut pas être
    -- attribuée honnêtement au personnage courant et est donc abandonnée.
    gearcompass.bonusRollHistory = nil

    -- Les exports riches antérieurs ne correspondent plus au contrat compact :
    -- ne jamais les réafficher comme s'ils venaient de la version actuelle.
    if gearcompass.lastExport ~= nil and not IsCurrentExport(gearcompass.lastExport) then
        gearcompass.lastExport = nil
    end

    -- Périmètre persistant strict : réglages et dernier export seulement.
    for key in pairs(gearcompass) do
        if key ~= "settings" and key ~= "lastExport" then
            gearcompass[key] = nil
        end
    end
    return gearcompass
end

function DB.Init()
    if not NS.db then return nil end
    return DB.Migrate(NS.db)
end

function DB.Get()
    return (NS.db and NS.db.gearcompass) or DB.Init()
end

function DB.GetSettings()
    local data = DB.Get()
    return data and data.settings or DEFAULT_SETTINGS
end

function DB.IsDebugEnabled()
    return debugEnabled
end

function DB.ToggleDebug()
    debugEnabled = not debugEnabled
    return debugEnabled
end

function NS.GearCompassDebug(message, ...)
    if not debugEnabled then return end
    local text = tostring(message or "")
    if select("#", ...) > 0 then
        local ok, formatted = pcall(string.format, text, ...)
        if ok then text = formatted end
    end
    if NS.Msg then NS.Msg("|cff8b8fa3[GearCompass]|r " .. text) end
end

function DB.SelfTestMigration()
    local oldExport = '{"addon":{},"character":{},"errors":[],"export_type":"gearcompass","game":{},"items":[],"scan":{},"schema_version":1}'
    -- Contrat compact 0.42 : l'absence des ajouts 0.43 doit rester valide.
    local compact042 = '{"addon":{"name":"AsylumCompagnon","version":"0.42.0"},"character":{},"errors":[],"export_type":"gearcompass","items":[],"schema_version":1}'
    local live0430 = '{"addon":{"name":"AsylumCompagnon","version":"0.43.0"},"character":{},"character_stats":{"critical_strike":1,"haste":2,"mastery":3,"versatility":4},"errors":[],"export_type":"gearcompass","items":[],"schema_version":1}'
    local permanent0431 = '{"addon":{"name":"AsylumCompagnon","version":"0.43.1"},"character":{},"character_stats":{"critical_strike":1,"haste":2,"mastery":3,"versatility":4},"character_stats_source":"equipped_items_and_gems","errors":[],"export_type":"gearcompass","items":[{"enchantment":{"applicable":true,"enchant_id":1,"name":null,"spell_id":null}}],"schema_version":1}'
    local canonical0432 = '{"addon":{"name":"AsylumCompagnon","version":"0.43.2"},"character":{},"character_stats":{"critical_strike":1,"haste":2,"mastery":3,"versatility":4},"character_stats_meta":{"complete":true,"includes_enchantments":false,"includes_gems":true,"includes_temporary_auras":false,"method":"equipped_items_and_gems"},"character_stats_source":"equipped_items_and_gems","errors":[],"export_type":"gearcompass","items":[{"enchantment":{"applicable":true,"enchant_id":1,"name":null,"spell_id":null}}],"schema_version":1}'
    local incomplete0432 = '{"addon":{"name":"AsylumCompagnon","version":"0.43.2"},"character":{},"character_stats":{"critical_strike":null,"haste":null,"mastery":null,"versatility":null},"character_stats_meta":{"complete":false,"includes_enchantments":false,"includes_gems":false,"includes_temporary_auras":false,"method":"equipped_items_and_gems"},"character_stats_source":"equipped_items_and_gems","errors":[],"export_type":"gearcompass","items":[],"schema_version":1}'
    local canonical0434 = '{"addon":{"name":"AsylumCompagnon","version":"0.43.4"},"character":{},"character_stats":{"critical_strike":1,"haste":2,"mastery":3,"versatility":4},"character_stats_meta":{"complete":true,"includes_enchantments":false,"includes_gems":true,"includes_temporary_auras":false,"method":"equipped_items_and_gems","unresolved_gems":[]},"character_stats_source":"equipped_items_and_gems","errors":[],"export_type":"gearcompass","items":[],"schema_version":1}'
    local contract0440 = '{"addon":{"name":"AsylumCompagnon","version":"0.44.0"},"character":{},"character_stats":{"critical_strike":1,"haste":2,"mastery":3,"versatility":4},"character_stats_meta":{"complete":true,"includes_enchantments":false,"includes_gems":true,"includes_temporary_auras":false,"method":"equipped_items_and_gems","unresolved_gems":[]},"character_stats_source":"equipped_items_and_gems","client":{},"collection_status":{},"errors":[],"export_type":"gearcompass","items":[],"resources":{},"schema_version":2,"upgrade_state":{},"weekly_rewards":{}}'
    local strictContractRoot = {
        addon = { name = "AsylumCompagnon", version = "0.46.7" },
        character = {},
        character_stats = {
            critical_strike = 1, haste = 2, mastery = 3, versatility = 4,
        },
        character_stats_meta = {
            complete = true, includes_enchantments = false, includes_gems = true,
            includes_temporary_auras = false, method = "equipped_items_and_gems",
            unresolved_gems = JSON.Array({}),
        },
        character_stats_source = "equipped_items_and_gems",
        client = {}, collection_status = {}, errors = JSON.Array({}),
        export_type = "gearcompass", items = JSON.Array({}), resources = {},
        schema_version = 2, upgrade_state = {}, weekly_rewards = {},
    }
    local contract0467 = Checksum and Checksum.Encode
        and Checksum.Encode(strictContractRoot)
    strictContractRoot.addon.version = "0.46.8"
    local contract0468 = Checksum and Checksum.Encode
        and Checksum.Encode(strictContractRoot)
    strictContractRoot.addon.version = "0.47.0"
    strictContractRoot.bonus_roll_history_complete = false
    strictContractRoot.bonus_roll_history_scope = "prospective"
    strictContractRoot.bonus_roll_history_started_at = 1784130000
    strictContractRoot.bonus_roll_history = JSON.Array({})
    local contract0470 = Checksum and Checksum.Encode
        and Checksum.Encode(strictContractRoot)
    strictContractRoot.addon.version = "0.47.2"
    strictContractRoot.weekly_rewards = {
        complete = true,
        activities = JSON.Array({}),
        mythic_plus_runs = JSON.Array({}),
        mythic_plus_runs_meta = {
            complete = true,
            source = "C_MythicPlus.GetRunHistory",
            scope = "current_week_completed",
            include_previous_weeks = false,
            include_incomplete_runs = false,
            current_season_only = true,
            returned_count = 0,
            count = 0,
        },
        errors = JSON.Array({}),
    }
    local contract0472 = Checksum and Checksum.Encode
        and Checksum.Encode(strictContractRoot)
    local fake = {}
    fake[LEGACY_DB_KEY] = {
        settings = { includeBags = false, includeBank = true, unknown = true },
        bankCache = {},
        lastExport = oldExport,
        lastExportAt = 123,
        lastExportSummary = {},
        storageVersion = 2,
        debug = true,
    }
    local data = DB.Migrate(fake)
    local exactRoot, exactSettings = true, true
    for key in pairs(data or {}) do
        if key ~= "settings" and key ~= "lastExport" then exactRoot = false end
    end
    for key in pairs(data and data.settings or {}) do
        if DEFAULT_SETTINGS[key] == nil then exactSettings = false end
    end
    local ok = data and data.settings.includeBags == false
        and data.settings.includeEquipped == true
        and data.settings.includeBank == nil
        and data.bankCache == nil
        and data.bonusRollHistory == nil
        and data.lastExport == nil
        and data.lastExportSummary == nil
        and fake[LEGACY_DB_KEY] == nil
        and fake.gearcompass == data
        and exactRoot and exactSettings
    data.lastExport = compact042
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(compact042)
    data.lastExport = live0430
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(live0430)
    data.lastExport = permanent0431
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(permanent0431)
    data.lastExport = canonical0432
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(canonical0432)
    data.lastExport = incomplete0432
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(incomplete0432)
    data.lastExport = canonical0434
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(canonical0434)
    data.lastExport = contract0440
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(contract0440)
    data.lastExport = contract0467
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(contract0467)
    data.lastExport = contract0468
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(contract0468)
    data.lastExport = contract0470
    DB.Migrate(fake)
    ok = ok and data.lastExport == nil and not IsCurrentExport(contract0470)
    data.lastExport = contract0472
    DB.Migrate(fake)
    local migratedAgain = DB.Migrate(fake)
    ok = ok and data.lastExport == contract0472 and IsCurrentExport(contract0472)
        and migratedAgain.bonusRollHistory == nil
    return not not ok, fake
end
