--[[
============================================================================
Core/BlizzardData/Diagnostics.lua  (added in v15.2.0 — Audit Batch 4 Part 2)
============================================================================

Data verification, pipeline tracing, and collection statistics for the
BlizzardData orchestrator. Used primarily by /etb verify, /etb diagverify,
and /etb stats slash commands.

Methods owned here (6):
  VerifyDataCompleteness  — sample N items per category and verify they
                            have icons, names, source IDs
  VerifyDataPipeline      — trace where data is coming from (compact vs
                            Blizzard API) for a given category
  CompareItemCounts       — diff item count between BlizzardData and the
                            Blizzard wardrobe APIs (sanity check)
  GetCollectionStats      — collected/uncollected counts for one category
  GetAllStats             — aggregate stats across all categories
  PrintStats              — print a summary of all stats to chat

External dependencies pulled from owner (BD):
  owner.GetCachedItemName       — name resolution helper
  owner:GetItemsForCategory     — main item fetcher (delegator dispatch)
  owner:GetCategoryAppearances  — Blizzard API wrapper (delegator dispatch)

Closure / shadowing notes: All synchronous methods, no closures.
============================================================================
]]--
local ADDON_NAME, NS = ...
local ETB = NS.ETB
NS.ETB.BlizzardData = NS.ETB.BlizzardData or {}
NS.ETB.BlizzardData.Diagnostics = NS.ETB.BlizzardData.Diagnostics or {}
local Diagnostics = NS.ETB.BlizzardData.Diagnostics

-- =============================================================================
-- METHODS (extracted from Core/BlizzardData.lua in v15.2.0)
-- =============================================================================

function Diagnostics:VerifyDataCompleteness(owner, categoryID, sampleSize)
    sampleSize = sampleSize or 10  -- Check first 10 appearances by default
    
    ETB:Print("|cff00ffff[BlizzardData]|r Verifying data completeness for category " .. tostring(categoryID) .. "...")
    
    local appearances = owner:GetCategoryAppearances(categoryID)
    if #appearances == 0 then
        ETB:Print("|cffff0000[BlizzardData]|r No appearances found for category " .. tostring(categoryID))
        return false
    end
    
    local checked = 0
    local missingFields = {}
    local fieldCounts = {}
    
    for i, appearance in ipairs(appearances) do
        if checked >= sampleSize then break end
        
        -- Validate appearance is a table
        if type(appearance) == "table" then
            -- Check appearance object fields
            local appearanceFields = {}
            for k, v in pairs(appearance) do
                appearanceFields[k] = true
                fieldCounts["appearance." .. k] = (fieldCounts["appearance." .. k] or 0) + 1
            end
            
            -- Check if we're capturing all appearance fields
            if appearance.visualID and not appearanceFields.visualID then
                table.insert(missingFields, "appearance.visualID")
            end
            if appearance.appearanceID and not appearanceFields.appearanceID then
                table.insert(missingFields, "appearance.appearanceID")
            end
            if appearance.isCollected ~= nil and not appearanceFields.isCollected then
                table.insert(missingFields, "appearance.isCollected")
            end
            if appearance.isUsable ~= nil and not appearanceFields.isUsable then
                table.insert(missingFields, "appearance.isUsable")
            end
            
            -- Check sources
            local sources = owner:GetAppearanceSources(appearance.visualID)
            if #sources == 0 then
                ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: Appearance visualID %d has no sources", appearance.visualID))
            else
                for _, source in ipairs(sources) do
                    -- Validate source is a table
                    if type(source) == "table" then
                        -- Check source object fields
                        local sourceFields = {}
                        for k, v in pairs(source) do
                            sourceFields[k] = true
                            fieldCounts["source." .. k] = (fieldCounts["source." .. k] or 0) + 1
                        end
                        
                        -- Check if we're capturing all source fields
                        if source.itemID and not sourceFields.itemID then
                            table.insert(missingFields, "source.itemID")
                        end
                        if source.sourceID and not sourceFields.sourceID then
                            table.insert(missingFields, "source.sourceID")
                        end
                        if source.categoryID and not sourceFields.categoryID then
                            table.insert(missingFields, "source.categoryID")
                        end
                        if source.invType and not sourceFields.invType then
                            table.insert(missingFields, "source.invType")
                        end
                        if source.isCollected ~= nil and not sourceFields.isCollected then
                            table.insert(missingFields, "source.isCollected")
                        end
                        if source.isHideVisual ~= nil and not sourceFields.isHideVisual then
                            table.insert(missingFields, "source.isHideVisual")
                        end
                        
                        -- Try GetAppearanceSourceInfo if available
                        if C_TransmogCollection and C_TransmogCollection.GetAppearanceSourceInfo and source.sourceID then
                            local success, sourceInfo = pcall(C_TransmogCollection.GetAppearanceSourceInfo, source.sourceID)
                            if success and sourceInfo and type(sourceInfo) == "table" then
                                for k, v in pairs(sourceInfo) do
                                    fieldCounts["sourceInfo." .. k] = (fieldCounts["sourceInfo." .. k] or 0) + 1
                                end
                            end
                        end
                        
                        -- Build item entry and check what we store
                        local item = owner:BuildItemEntry(source, appearance, categoryID)
                        if type(item) == "table" then
                            local itemFields = {}
                            for k, v in pairs(item) do
                                itemFields[k] = true
                                fieldCounts["item." .. k] = (fieldCounts["item." .. k] or 0) + 1
                            end
                            
                            -- Verify critical fields are present
                            if not item.itemID then
                                table.insert(missingFields, "item.itemID")
                            end
                            if not item.visualID and appearance.visualID then
                                table.insert(missingFields, "item.visualID")
                            end
                            if not item.name or item.name == "Loading..." then
                                ETB:Print(string.format("|cffffff00[BlizzardData]|r Item %d name not loaded", item.itemID))
                            end
                        end
                    else
                        ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: Source is not a table (type: %s)", type(source)))
                    end
                end
            end
        else
            ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: Appearance at index %d is not a table (type: %s)", i, type(appearance)))
        end
        
        checked = checked + 1
    end
    
    -- Print summary
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Checked %d appearances", checked))
    ETB:Print("|cff00ffff[BlizzardData]|r Field frequency:")
    for field, count in pairs(fieldCounts) do
        if count == checked then
            ETB:Print(string.format("  |cff00ff00✓|r %s: %d/%d", field, count, checked))
        else
            ETB:Print(string.format("  |cffffff00⚠|r %s: %d/%d", field, count, checked))
        end
    end
    
    if #missingFields > 0 then
        ETB:Print("|cffff0000[BlizzardData]|r Missing fields detected:")
        for _, field in ipairs(missingFields) do
            ETB:Print("  - " .. field)
        end
        return false
    else
        ETB:Print("|cff00ff00[BlizzardData]|r All critical fields present!")
        return true
    end
end

-- Verify data pipeline completeness for a category
function Diagnostics:VerifyDataPipeline(owner, categoryID)
    ETB:Print("|cff00ffff[BlizzardData]|r Verifying data pipeline for category " .. tostring(categoryID) .. "...")
    
    -- Step 1: Get raw appearances from API
    local appearances = owner:GetCategoryAppearances(categoryID)
    local totalAppearances = #appearances
    local collectedAppearances = 0
    local uncollectedAppearances = 0
    
    for _, app in ipairs(appearances) do
        if app.isCollected then
            collectedAppearances = collectedAppearances + 1
        else
            uncollectedAppearances = uncollectedAppearances + 1
        end
    end
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Step 1 - API Appearances: %d total (%d collected, %d uncollected)", 
        totalAppearances, collectedAppearances, uncollectedAppearances))
    
    -- Step 2: Get all sources (no filtering)
    local totalSources = 0
    local sourcesByCollection = { collected = 0, uncollected = 0 }
    
    for _, app in ipairs(appearances) do
        local sources = owner:GetAppearanceSources(app.visualID)
        totalSources = totalSources + #sources
        for _, source in ipairs(sources) do
            if source.isCollected then
                sourcesByCollection.collected = sourcesByCollection.collected + 1
            else
                sourcesByCollection.uncollected = sourcesByCollection.uncollected + 1
            end
        end
    end
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Step 2 - Total Sources: %d (%d collected, %d uncollected)", 
        totalSources, sourcesByCollection.collected, sourcesByCollection.uncollected))
    
    -- Step 3: Get items with NO filtering (all data)
    local allItems = owner:GetItemsForCategory(categoryID, { limit = 100000 })
    local allItemsCollected = 0
    local allItemsUncollected = 0
    
    for _, item in ipairs(allItems) do
        if item.isCollected then
            allItemsCollected = allItemsCollected + 1
        else
            allItemsUncollected = allItemsUncollected + 1
        end
    end
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Step 3 - Items (no filters): %d total (%d collected, %d uncollected)", 
        #allItems, allItemsCollected, allItemsUncollected))
    
    -- Step 4: Verify categorization
    local miscategorized = 0
    local catInfo = ETB.CATEGORY_INFO[categoryID]
    if catInfo then
        for _, item in ipairs(allItems) do
            -- Check if item's categoryID matches
            if item.categoryID and item.categoryID ~= categoryID then
                miscategorized = miscategorized + 1
                ETB:Print(string.format("|cffffff00[BlizzardData]|r Warning: Item %d has categoryID %d but was requested for category %d", 
                    item.itemID, item.categoryID, categoryID))
            end
        end
    end
    
    if miscategorized > 0 then
        ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: %d items may be miscategorized", miscategorized))
    else
        ETB:Print("|cff00ff00[BlizzardData]|r All items properly categorized!")
    end
    
    -- Step 5: Compare with Blizzard's count
    local coverage = totalAppearances > 0 and (#allItems / totalAppearances * 100) or 0
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Coverage: %.1f%% (%d items / %d appearances)", 
        coverage, #allItems, totalAppearances))
    
    if coverage < 95 then
        ETB:Print("|cffff0000[BlizzardData]|r Warning: Coverage below 95% - may be missing data!")
        return false
    else
        ETB:Print("|cff00ff00[BlizzardData]|r Data pipeline verification complete!")
        return true
    end
end

-- Compare our item count with Blizzard's for a category
function Diagnostics:CompareItemCounts(owner, categoryID)
    ETB:Print("|cff00ffff[BlizzardData]|r Comparing item counts for category " .. tostring(categoryID) .. "...")
    
    local appearances = owner:GetCategoryAppearances(categoryID)
    local blizzardCount = #appearances
    
    -- Count unique visualIDs
    local uniqueVisuals = {}
    for _, app in ipairs(appearances) do
        if app.visualID then
            uniqueVisuals[app.visualID] = true
        end
    end
    local uniqueVisualCount = 0
    for _ in pairs(uniqueVisuals) do
        uniqueVisualCount = uniqueVisualCount + 1
    end
    
    -- Count total sources
    local totalSources = 0
    for _, app in ipairs(appearances) do
        local sources = owner:GetAppearanceSources(app.visualID)
        totalSources = totalSources + #sources
    end
    
    -- Get our filtered count
    local ourItems = owner:GetItemsForCategory(categoryID, { limit = 100000 })
    local ourCount = #ourItems
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Blizzard API:"))
    ETB:Print(string.format("  Appearances: %d", blizzardCount))
    ETB:Print(string.format("  Unique Visuals: %d", uniqueVisualCount))
    ETB:Print(string.format("  Total Sources: %d", totalSources))
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Our Addon:"))
    ETB:Print(string.format("  Items: %d", ourCount))
    
    local percentage = totalSources > 0 and (ourCount / totalSources * 100) or 0
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Coverage: %.1f%% (%d/%d)", percentage, ourCount, totalSources))
    
    if ourCount < totalSources * 0.95 then
        ETB:Print("|cffff0000[BlizzardData]|r Warning: We're showing less than 95% of available sources!")
        return false
    else
        ETB:Print("|cff00ff00[BlizzardData]|r Coverage looks good!")
        return true
    end
end


function Diagnostics:GetCollectionStats(owner, categoryID)
    local appearances = owner:GetCategoryAppearances(categoryID)
    local total, collected, usable = #appearances, 0, 0
    
    for _, app in ipairs(appearances) do
        if app.isCollected then collected = collected + 1 end
        if app.isUsable then usable = usable + 1 end
    end
    
    return {
        total = total,
        collected = collected,
        usable = usable,
        percent = total > 0 and (collected / total * 100) or 0,
    }
end

function Diagnostics:GetAllStats(owner)
    local total, collected = 0, 0
    local byCategory = {}
    
    for categoryID in pairs(ETB.CATEGORY_INFO) do
        local stats = owner:GetCollectionStats(categoryID)
        byCategory[categoryID] = stats
        total = total + stats.total
        collected = collected + stats.collected
    end
    
    return {
        total = total,
        collected = collected,
        percent = total > 0 and (collected / total * 100) or 0,
        byCategory = byCategory,
    }
end


function Diagnostics:PrintStats(owner)
    ETB:Print("=== BLIZZARD DATA STATS ===")
    ETB:Print(string.format("Category queries: %d", owner.stats.categoryQueries))
    ETB:Print(string.format("Source queries: %d", owner.stats.sourceQueries))
    ETB:Print(string.format("Cache hits/misses: %d/%d", owner.stats.cacheHits, owner.stats.cacheMisses))
    
    if owner.itemCache then
        local s = owner.itemCache:GetStats()
        ETB:Print(string.format("Item cache: %d/%d (%.1f%% hit rate)", s.size, s.maxSize, s.hitRate))
    end
end

