-- RealUI Diagnostic and Troubleshooting Tools
-- Provides taint logging, performance monitoring, and system health checks

local _, private = ...

-- RealUI --
local RealUI = private.RealUI
local L = RealUI.L -- luacheck: ignore

local DiagnosticTools = {}
private.DiagnosticTools = DiagnosticTools

-- Diagnostic state
local diagnosticState = {
    taintLoggingEnabled = false,
    performanceMonitoringActive = false,
    lastHealthCheck = 0,
    diagnosticHistory = {}
}

-- Initialize diagnostic tools
function DiagnosticTools:Initialize()
    self.initialized = true

    -- Check initial taint logging state
    diagnosticState.taintLoggingEnabled = (_G.GetCVar("taintLog") ~= "0")

    RealUI.Debug("DiagnosticTools", "Initialized")
end

-- Taint Logging Toggle
-- level: optional "0", "1" or "2". Omitted toggles between off and 1.
--   1 = log the taint behind blocked actions. Small file, no measurable cost,
--       so it can be left on for a whole session while chasing an intermittent
--       ADDON_ACTION_FORBIDDEN (which is the only way to catch one).
--   2 = also log every taint event. Very large and visibly stuttery; only
--       worth it when level 1 does not name the culprit.
function DiagnosticTools:ToggleTaintLogging(level)
    local taintLog = _G.GetCVar("taintLog")

    local newValue
    if level == "0" or level == "1" or level == "2" then
        newValue = level
    else
        newValue = (taintLog ~= "0") and "0" or "1"
    end

    if newValue == taintLog then
        print(("Taint logging is already at level %s."):format(taintLog))
        return
    end

    _G.SetCVar("taintLog", newValue)

    diagnosticState.taintLoggingEnabled = (newValue ~= "0")

    local message = diagnosticState.taintLoggingEnabled
        and ("Taint logging enabled (level %s). Log: Logs\\taint.log — UI will reload."):format(newValue)
        or "Taint logging disabled. UI will reload."

    if RealUI.FeedbackSystem then
        RealUI.FeedbackSystem:ShowFeedback("info", "Taint Logging", message)
    else
        print(message)
    end

    _G.ReloadUI()
end

function DiagnosticTools:IsTaintLoggingEnabled()
    return diagnosticState.taintLoggingEnabled
end

-- Taint Scanner
-- Walks a dot separated path down from _G, e.g.
--   ItemUpgradeFrame
--   ItemUpgradeFrame.LeftItemPreviewFrame.NineSlice
local function ResolvePath(path)
    local obj = _G
    for part in path:gmatch("[^%.]+") do
        if type(obj) ~= "table" then
            return nil
        end
        obj = obj[part]
        if obj == nil then
            return nil
        end
    end
    return obj
end

-- Reports which fields of a table or frame have been written by an addon.
-- issecurevariable returns isSecure, taintingAddon — so an insecure field
-- names both the value a protected call could choke on and who dirtied it.
function DiagnosticTools:ScanTaint(input)
    local path = input and input:match("^%s*([%w_%.]+)")
    local obj, label

    if path then
        obj, label = ResolvePath(path), path
        if obj == nil then
            print(("|cffff0000Taint scan:|r no such object |cffffff00%s|r"):format(path))
            return
        end
    else
        local foci = _G.GetMouseFoci and _G.GetMouseFoci()
        obj = foci and foci[1]
        if not obj then
            print("Usage: |cff8080FF/taintscan|r |cff22dd22Global[.Sub.Path]|r — or hover a frame and run it with no argument.")
            return
        end
        label = (obj.GetDebugName and obj:GetDebugName()) or "<frame under mouse>"
    end

    if type(obj) ~= "table" then
        print(("|cffff0000Taint scan:|r |cffffff00%s|r is a %s, not a table or frame."):format(label, type(obj)))
        return
    end

    if obj.IsForbidden and obj:IsForbidden() then
        print(("|cffff0000Taint scan:|r |cffffff00%s|r is forbidden."):format(label))
        return
    end

    local insecure, total = {}, 0
    local ok = pcall(function()
        for key in next, obj do
            if type(key) == "string" then
                total = total + 1
                local isSecure, taintedBy = _G.issecurevariable(obj, key)
                if not isSecure then
                    insecure[#insecure + 1] = {key = key, by = taintedBy or "?"}
                end
            end
        end
    end)

    if not ok then
        print(("|cffff0000Taint scan:|r could not iterate |cffffff00%s|r."):format(label))
        return
    end

    table.sort(insecure, function(a, b) return a.key < b.key end)

    print(("|cff22dd22Taint scan:|r %s — %d of %d fields addon-written"):format(label, #insecure, total))

    -- A plain global can also be checked as a variable in its own right
    if path and not path:find("%.") then
        local isSecure, taintedBy = _G.issecurevariable(path)
        if not isSecure then
            print(("  |cffff8000_G.%s|r itself <- |cffff0000%s|r"):format(path, taintedBy or "?"))
        end
    end

    for i = 1, #insecure do
        print(("  |cffff8000%s|r <- |cffff0000%s|r"):format(insecure[i].key, insecure[i].by))
    end

    if #insecure == 0 then
        print("  |cff888888(nothing addon-written)|r")
    end
end

-- Performance Monitoring Commands
function DiagnosticTools:StartPerformanceMonitoring()
    if RealUI.PerformanceMonitor then
        local success = RealUI.PerformanceMonitor:StartMonitoring()
        diagnosticState.performanceMonitoringActive = success
        return success
    end
    return false
end

function DiagnosticTools:StopPerformanceMonitoring()
    if RealUI.PerformanceMonitor then
        local success = RealUI.PerformanceMonitor:StopMonitoring()
        if success then
            diagnosticState.performanceMonitoringActive = false
        end
        return success
    end
    return false
end

function DiagnosticTools:GetPerformanceStatus()
    if RealUI.PerformanceMonitor then
        return RealUI.PerformanceMonitor:GetMonitoringState()
    end
    return nil
end

-- System Health Check
function DiagnosticTools:PerformHealthCheck()
    local healthReport = {
        timestamp = _G.time(),
        checks = {},
        overallStatus = "healthy",
        warnings = {},
        errors = {}
    }

    -- Check 1: Memory Usage
    local memoryUsage = _G.collectgarbage("count")
    healthReport.checks.memory = {
        name = "Memory Usage",
        value = memoryUsage,
        unit = "KB",
        status = memoryUsage < 50000 and "good" or (memoryUsage < 100000 and "warning" or "critical")
    }
    if healthReport.checks.memory.status ~= "good" then
        table.insert(healthReport.warnings, "High memory usage detected")
    end

    -- Check 2: Frame Rate
    local fps = _G.GetFramerate()
    healthReport.checks.framerate = {
        name = "Frame Rate",
        value = fps,
        unit = "FPS",
        status = fps > 30 and "good" or (fps > 15 and "warning" or "critical")
    }
    if healthReport.checks.framerate.status ~= "good" then
        table.insert(healthReport.warnings, "Low frame rate detected")
    end

    -- Check 3: Addon Count
    local addonCount = _G.C_AddOns.GetNumAddOns()
    local enabledCount = 0
    for i = 1, addonCount do
        if _G.C_AddOns.GetAddOnEnableState(i) > 0 then
            enabledCount = enabledCount + 1
        end
    end
    healthReport.checks.addons = {
        name = "Enabled Addons",
        value = enabledCount,
        total = addonCount,
        status = enabledCount < 50 and "good" or (enabledCount < 100 and "warning" or "critical")
    }

    -- Check 4: Database Integrity
    if RealUI.db then
        local hasProfile = RealUI.db.profile ~= nil
        local hasChar = RealUI.db.char ~= nil
        local hasGlobal = RealUI.db.global ~= nil

        healthReport.checks.database = {
            name = "Database Integrity",
            hasProfile = hasProfile,
            hasChar = hasChar,
            hasGlobal = hasGlobal,
            status = (hasProfile and hasChar and hasGlobal) and "good" or "critical"
        }

        if healthReport.checks.database.status ~= "good" then
            table.insert(healthReport.errors, "Database corruption detected")
        end
    end

    -- Check 5: Module Status
    if RealUI.ModuleFramework then
        local frameworkStatus = RealUI.ModuleFramework:GetFrameworkStatus()
        healthReport.checks.modules = {
            name = "Module Framework",
            initialized = frameworkStatus.initialized,
            totalModules = frameworkStatus.totalModules,
            enabledModules = frameworkStatus.enabledModules,
            status = frameworkStatus.initialized and "good" or "critical"
        }
    end

    -- Check 6: Taint Status
    healthReport.checks.taint = {
        name = "Taint Logging",
        enabled = diagnosticState.taintLoggingEnabled,
        status = "info"
    }

    -- Determine overall status
    for _, check in pairs(healthReport.checks) do
        if check.status == "critical" then
            healthReport.overallStatus = "critical"
            break
        elseif check.status == "warning" and healthReport.overallStatus ~= "critical" then
            healthReport.overallStatus = "warning"
        end
    end

    -- Store in history
    diagnosticState.lastHealthCheck = _G.time()
    table.insert(diagnosticState.diagnosticHistory, 1, healthReport)

    -- Trim history
    while #diagnosticState.diagnosticHistory > 10 do
        table.remove(diagnosticState.diagnosticHistory)
    end

    return healthReport
end

-- Print health check results
function DiagnosticTools:PrintHealthCheck()
    local report = self:PerformHealthCheck()

    print("=== RealUI System Health Check ===")
    print(("Overall Status: %s"):format(report.overallStatus:upper()))
    print("")

    for key, check in pairs(report.checks) do
        if check.value then
            print(("%s: %s %s [%s]"):format(
                check.name,
                tostring(check.value),
                check.unit or "",
                check.status:upper()
            ))
        else
            print(("%s: [%s]"):format(check.name, check.status:upper()))
        end
    end

    if #report.warnings > 0 then
        print("")
        print("Warnings:")
        for _, warning in ipairs(report.warnings) do
            print("  - " .. warning)
        end
    end

    if #report.errors > 0 then
        print("")
        print("Errors:")
        for _, error in ipairs(report.errors) do
            print("  - " .. error)
        end
    end
end

-- System Status Reporting
function DiagnosticTools:GetSystemStatus()
    local status = {
        version = RealUI.verinfo.string,
        build = RealUI.verinfo.build,
        gameVersion = RealUI.verinfo.gameVersion,
        isRetail = RealUI.isRetail,
        initialized = RealUI.isInitialized,
        enabled = RealUI.isEnabled,
        layout = RealUI.cLayout,
        installStage = RealUI.db and RealUI.db.char.init.installStage or "unknown"
    }

    -- Add performance data if available
    if RealUI.PerformanceMonitor then
        local perfData = RealUI.PerformanceMonitor:GetPerformanceData()
        if perfData then
            status.performance = {
                monitoring = true,
                memoryUsage = perfData.memory and perfData.memory.current,
                cpuUsage = perfData.cpu and perfData.cpu.current
            }
        end
    end

    -- Add module data if available
    if RealUI.ModuleFramework then
        local frameworkStatus = RealUI.ModuleFramework:GetFrameworkStatus()
        status.modules = {
            initialized = frameworkStatus.initialized,
            total = frameworkStatus.totalModules,
            enabled = frameworkStatus.enabledModules
        }
    end

    return status
end

function DiagnosticTools:PrintSystemStatus()
    local status = self:GetSystemStatus()

    print("=== RealUI System Status ===")
    print(("Version: %s (Build: %s)"):format(status.version, status.build))
    print(("Game Version: %s"):format(status.gameVersion))
    print(("Retail: %s"):format(tostring(status.isRetail)))
    print(("Initialized: %s"):format(tostring(status.initialized)))
    print(("Enabled: %s"):format(tostring(status.enabled)))
    print(("Current Layout: %d"):format(status.layout))
    print(("Install Stage: %s"):format(tostring(status.installStage)))

    if status.performance then
        print("")
        print("Performance:")
        print(("  Monitoring: %s"):format(tostring(status.performance.monitoring)))
        if status.performance.memoryUsage then
            print(("  Memory: %s"):format(status.performance.memoryUsage))
        end
        if status.performance.cpuUsage then
            print(("  CPU: %s"):format(status.performance.cpuUsage))
        end
    end

    if status.modules then
        print("")
        print("Modules:")
        print(("  Initialized: %s"):format(tostring(status.modules.initialized)))
        print(("  Total: %d"):format(status.modules.total))
        print(("  Enabled: %d"):format(status.modules.enabled))
    end
end

function DiagnosticTools:PrintProfileScaleStatus()
    local function GetStoredProfiles(aceDB)
        if not aceDB then
            return {}
        end

        if type(aceDB.sv) == "table" and type(aceDB.sv.profiles) == "table" then
            return aceDB.sv.profiles
        end

        return aceDB.profiles or {}
    end

    local function BoolText(value)
        return value and "true" or "false"
    end

    local function PrintSkinsProfile(name, profileData)
        if not profileData then
            print(("Skins profile '%s': missing"):format(name))
            return
        end

        local uiModScale = tonumber(profileData.uiModScale)
        if uiModScale == nil then
            uiModScale = 1
        end

        local customScale = tonumber(profileData.customScale)
        if customScale == nil then
            customScale = 1
        end

        local isHighRes = profileData.isHighRes
        if type(isHighRes) ~= "boolean" then
            isHighRes = false
        end

        local isPixelScale = profileData.isPixelScale
        if type(isPixelScale) ~= "boolean" then
            isPixelScale = true
        end

        print((
            "Skins profile '%s': uiModScale=%.2f customScale=%.2f isHighRes=%s isPixelScale=%s"
        ):format(
            name,
            uiModScale,
            customScale,
            BoolText(isHighRes),
            BoolText(isPixelScale)
        ))
    end

    local currentSpecIndex = _G.C_SpecializationInfo.GetSpecialization()
    local currentSpecInfo = currentSpecIndex and RealUI.charInfo and RealUI.charInfo.specs and RealUI.charInfo.specs[currentSpecIndex] or nil
    local currentRole = currentSpecInfo and currentSpecInfo.role or "UNKNOWN"
    local currentSpecName = currentSpecInfo and currentSpecInfo.name or "Unknown"
    local currentProfile = RealUI.db and RealUI.db.GetCurrentProfile and RealUI.db:GetCurrentProfile() or "unknown"
    local scopeLinks = RealUI.db and RealUI.db.char and RealUI.db.char.scopeLinks or {}

    local skinsModule = RealUI:GetModule("Skins", true)
    local skinsDB = skinsModule and skinsModule.db or nil
    local currentSkinsProfile = skinsDB and skinsDB:GetCurrentProfile() or "unavailable"

    print("=== RealUI Profile Scale Status ===")
    print(("Character: %s - %s"):format(RealUI.charInfo.name or "Unknown", RealUI.charInfo.realm or "Unknown"))
    print(("Class: %s"):format((RealUI.charInfo.class and RealUI.charInfo.class.token) or "UNKNOWN"))
    print(("Current spec: %s [%s]"):format(currentSpecName, currentRole))
    print(("Core profile: %s"):format(currentProfile))
    print(("Skins scope linked: %s"):format(BoolText(scopeLinks and scopeLinks.skins == true)))
    print(("Action Bars scope linked: %s"):format(BoolText(scopeLinks and scopeLinks.bt4 ~= false)))

    if RealUI.DualSpecSystem and RealUI.DualSpecSystem.GetDefaultProfileForSpec then
        if currentSpecIndex then
            print(("Default Core profile for current spec: %s"):format(
                RealUI.DualSpecSystem:GetDefaultProfileForSpec(currentSpecIndex) or "unknown"
            ))
        end

        if RealUI.db and RealUI.db.char and RealUI.db.char.specProfiles then
            for specIndex = 1, #RealUI.charInfo.specs do
                local specInfo = RealUI.charInfo.specs[specIndex]
                print(("Spec %d %s [%s] -> %s"):format(
                    specIndex,
                    specInfo.name or "Unknown",
                    specInfo.role or "UNKNOWN",
                    RealUI.db.char.specProfiles[specIndex]
                        or RealUI.DualSpecSystem:GetDefaultProfileForSpec(specIndex)
                        or "unknown"
                ))
            end
        end
    end

    if not skinsDB then
        print("Skins database: unavailable")
        return
    end

    print(("Current Skins profile: %s"):format(currentSkinsProfile))

    local skinsProfiles = GetStoredProfiles(skinsDB)
    if currentSkinsProfile ~= "unavailable" then
        PrintSkinsProfile(currentSkinsProfile, skinsProfiles[currentSkinsProfile])
    end

    if currentSkinsProfile ~= "RealUI" then
        PrintSkinsProfile("RealUI", skinsProfiles["RealUI"])
    end
    if currentSkinsProfile ~= "RealUI-Healing" then
        PrintSkinsProfile("RealUI-Healing", skinsProfiles["RealUI-Healing"])
    end

    if _G.UIParent and _G.UIParent.GetScale then
        print(("UIParent scale: %.2f"):format(_G.UIParent:GetScale() or 0))
    end
end

-- Diagnostic Commands
function DiagnosticTools:RunDiagnostic(diagnosticType)
    if diagnosticType == "health" then
        self:PrintHealthCheck()
    elseif diagnosticType == "status" then
        self:PrintSystemStatus()
    elseif diagnosticType == "profile" or diagnosticType == "profiles" then
        self:PrintProfileScaleStatus()
    elseif diagnosticType == "performance" then
        if RealUI.PerformanceMonitor then
            RealUI.PerformanceMonitor:PrintStatus()
        else
            print("Performance Monitor not available")
        end
    elseif diagnosticType == "modules" then
        if RealUI.ModuleFramework then
            local modules = RealUI.ModuleFramework:GetRegisteredModules()
            print("=== Module Status ===")
            for name, info in pairs(modules) do
                print(("%s: %s [%s]"):format(name, info.state, info.type))
            end
        else
            print("Module Framework not available")
        end
    elseif diagnosticType == "all" then
        self:PrintSystemStatus()
        print("")
        self:PrintHealthCheck()
        print("")
        self:PrintProfileScaleStatus()
    else
        print("Unknown diagnostic type. Available: health, status, profile, profiles, performance, modules, all")
    end
end

-- Get diagnostic history
function DiagnosticTools:GetDiagnosticHistory(count)
    count = count or 5
    local history = {}
    for i = 1, math.min(count, #diagnosticState.diagnosticHistory) do
        table.insert(history, diagnosticState.diagnosticHistory[i])
    end
    return history
end

-- Clear diagnostic history
function DiagnosticTools:ClearDiagnosticHistory()
    diagnosticState.diagnosticHistory = {}
end

-- Export diagnostic report
function DiagnosticTools:ExportDiagnosticReport()
    local report = {
        timestamp = _G.date("%Y-%m-%d %H:%M:%S", _G.time()),
        systemStatus = self:GetSystemStatus(),
        healthCheck = self:PerformHealthCheck(),
        history = self:GetDiagnosticHistory(5)
    }

    -- Convert to string for easy copying
    local reportStr = "=== RealUI Diagnostic Report ===\n"
    reportStr = reportStr .. "Generated: " .. report.timestamp .. "\n\n"
    reportStr = reportStr .. "System Status:\n"
    reportStr = reportStr .. "  Version: " .. report.systemStatus.version .. "\n"
    reportStr = reportStr .. "  Build: " .. report.systemStatus.build .. "\n"
    reportStr = reportStr .. "  Layout: " .. report.systemStatus.layout .. "\n\n"
    reportStr = reportStr .. "Health Check:\n"
    reportStr = reportStr .. "  Overall: " .. report.healthCheck.overallStatus .. "\n"

    for key, check in pairs(report.healthCheck.checks) do
        if check.value then
            reportStr = reportStr .. ("  %s: %s %s [%s]\n"):format(
                check.name,
                tostring(check.value),
                check.unit or "",
                check.status
            )
        end
    end

    print(reportStr)
    return report
end

-- Register with RealUI
RealUI.DiagnosticTools = DiagnosticTools
