AddonDiagnostics = AddonDiagnostics or {}
AddonDiagnostics.EnhancedDiagnosticsSnapshots = {}
local EnhancedDiagnosticsSnapshots = AddonDiagnostics.EnhancedDiagnosticsSnapshots

local MAX_PENDING_SNAPSHOT_CALLBACKS = 32
local MAX_SNAPSHOT_PROVIDERS = 32
local MAX_SNAPSHOTS_PER_SESSION = 128
local MAX_SNAPSHOTS_PER_PROVIDER = 16
local MAX_SNAPSHOTS_PER_PROVIDER_PER_SECOND = 4
local MAX_SNAPSHOTS_GLOBAL_PER_SECOND = 64
local MAX_SNAPSHOT_FIELDS = 16
local MAX_PROVIDER_ID_CHARACTERS = 80
local MAX_SNAPSHOT_FIELD_NAME_CHARACTERS = 80
local MAX_SNAPSHOT_STRING_CHARACTERS = 160
local MAX_SNAPSHOT_EVIDENCE_CHARACTERS = 24000
local MAX_INCIDENT_SNAPSHOT_REQUESTS_PER_PROVIDER_PER_MINUTE = 12
local MAX_INCIDENT_SNAPSHOT_REQUESTS_GLOBAL_PER_MINUTE = 48
local MAX_INCIDENT_SNAPSHOT_EVIDENCE_CHARACTERS = 6000

local snapshotSessionActive = false
local snapshotSessionStartedAt = nil
local snapshotRequestCount = 0
local snapshotCount = 0
local droppedSnapshotCount = 0
local rejectedSnapshotCount = 0
local snapshotEvidenceTruncated = false
local lastSnapshotReason = nil
local snapshotEvidence = ""
local snapshotProviderCount = 0
local snapshotProviderIDs = {}
local snapshotProviderCallbacks = {}
local snapshotProviderRequestCounts = {}
local snapshotProviderWindowStarts = {}
local snapshotProviderWindowCounts = {}
local snapshotGlobalWindowStart = nil
local snapshotGlobalWindowCount = 0
local incidentSnapshotProviderWindowStarts = {}
local incidentSnapshotProviderWindowCounts = {}
local incidentSnapshotGlobalWindowStart = nil
local incidentSnapshotGlobalWindowCount = 0
local incidentSnapshotRequestCount = 0
local incidentSnapshotAcceptedCount = 0
local incidentSnapshotDroppedCount = 0
local incidentSnapshotRejectedCount = 0
local lastIncidentSnapshotReason = nil
local pendingSnapshotProviderCount = 0
local pendingSnapshotProviderIDs = {}
local pendingSnapshotProviderCallbacks = {}
local lastObservedErrorCount = 0
local originalRegisterProvider = nil
local originalSnapshotSessionStart = nil
local originalSnapshotSessionStop = nil
local originalSnapshotMarkProblemNow = nil
local snapshotBootstrapFrame = nil

local function RejectSnapshot(reason)
    rejectedSnapshotCount = rejectedSnapshotCount + 1
    lastSnapshotReason = reason
    return false, reason
end

local function DropSnapshot(reason)
    droppedSnapshotCount = droppedSnapshotCount + 1
    snapshotEvidenceTruncated = true
    lastSnapshotReason = reason
    return false, reason
end

local function IsAccessibleValue(value)
    if type(issecretvalue) == "function" and issecretvalue(value) then
        return false
    end

    if type(canaccessvalue) == "function" and not canaccessvalue(value) then
        return false
    end

    return true
end

local function IsSafeSnapshotText(value, maximumCharacters, allowEmpty)
    if not IsAccessibleValue(value) or type(value) ~= "string" then
        return false
    end

    if not allowEmpty and value == "" then
        return false
    end

    if string.len(value) > maximumCharacters then
        return false
    end

    if string.find(value, "\n", 1, true) then
        return false
    end

    if string.find(value, "\r", 1, true) then
        return false
    end

    if string.find(value, "|", 1, true) then
        return false
    end

    return true
end

local function IsSafeProviderID(providerID)
    return IsSafeSnapshotText(providerID, MAX_PROVIDER_ID_CHARACTERS, false)
end

local function FormatSnapshotValue(value)
    if not IsAccessibleValue(value) then
        return nil, "protected or secret snapshot value"
    end

    local valueType = type(value)

    if valueType == "boolean" then
        if value then
            return "true", nil
        end
        return "false", nil
    end

    if valueType == "number" then
        if value ~= value or value == math.huge or value == -math.huge then
            return nil, "invalid snapshot number"
        end
        return string.format("%.17g", value), nil
    end

    if valueType == "string" then
        if not IsSafeSnapshotText(value, MAX_SNAPSHOT_STRING_CHARACTERS, true) then
            return nil, "invalid snapshot string"
        end
        return value, nil
    end

    return nil, "unsupported snapshot value type"
end

local function BuildSnapshotFields(fields)
    if not IsAccessibleValue(fields) or type(fields) ~= "table" then
        return nil, "snapshot callback must return a table"
    end

    local formattedFields = {}
    local fieldName = nil

    for index = 1, MAX_SNAPSHOT_FIELDS + 1 do
        fieldName = next(fields, fieldName)

        if fieldName == nil then
            table.sort(formattedFields)
            return table.concat(formattedFields, ", "), nil
        end

        if index > MAX_SNAPSHOT_FIELDS then
            return nil, "snapshot field limit exceeded"
        end

        if not IsSafeSnapshotText(fieldName, MAX_SNAPSHOT_FIELD_NAME_CHARACTERS, false) then
            return nil, "invalid snapshot field name"
        end

        if string.find(fieldName, "=", 1, true) then
            return nil, "invalid snapshot field-name delimiter"
        end

        local valueText, valueReason = FormatSnapshotValue(rawget(fields, fieldName))
        if valueText == nil then
            return nil, valueReason
        end

        table.insert(formattedFields, string.format("%s=%s", fieldName, valueText))
    end

    return nil, "snapshot field limit exceeded"
end

local function IsRecording()
    if not AddonDiagnostics.SessionController or not AddonDiagnostics.SessionController.IsRecording then
        return false
    end

    return AddonDiagnostics.SessionController:IsRecording() == true
end

local function GetCapturedLuaErrorCount()
    if not AddonDiagnostics.UniversalObserver or not AddonDiagnostics.UniversalObserver.GetLuaErrorCaptureStats then
        return 0
    end

    local capturedCount = AddonDiagnostics.UniversalObserver:GetLuaErrorCaptureStats()
    return tonumber(capturedCount) or 0
end

local function ResetSnapshotSession()
    snapshotSessionActive = true
    snapshotSessionStartedAt = GetTime()
    snapshotRequestCount = 0
    snapshotCount = 0
    droppedSnapshotCount = 0
    rejectedSnapshotCount = 0
    snapshotEvidenceTruncated = false
    lastSnapshotReason = nil
    snapshotEvidence = ""
    snapshotProviderRequestCounts = {}
    snapshotProviderWindowStarts = {}
    snapshotProviderWindowCounts = {}
    snapshotGlobalWindowStart = nil
    snapshotGlobalWindowCount = 0
    lastObservedErrorCount = GetCapturedLuaErrorCount()
end

local function StopSnapshotSession()
    snapshotSessionActive = false
    snapshotSessionStartedAt = nil
end

local function GetSnapshotElapsed()
    if not snapshotSessionStartedAt then
        return 0
    end

    return math.max(0, GetTime() - snapshotSessionStartedAt)
end

local function IsValidSnapshotReason(reason)
    return reason == "recording_started"
        or reason == "error_observed"
        or reason == "user_marker"
        or reason == "recording_stopped"
end

local function FindSnapshotProviderIndex(providerID)
    for index = 1, snapshotProviderCount do
        if rawget(snapshotProviderIDs, index) == providerID then
            return index
        end
    end

    return nil
end

local function ActivateSnapshotCallback(providerID, callback)
    local broker = AddonDiagnostics.EnhancedDiagnosticsBroker
    if not broker or not broker.SupportsCapability then
        return false
    end

    if not IsSafeProviderID(providerID) then
        return false
    end

    if not IsAccessibleValue(callback) or type(callback) ~= "function" then
        return false
    end

    if not broker:SupportsCapability(providerID, "snapshots") then
        return false
    end

    if FindSnapshotProviderIndex(providerID) ~= nil then
        return true
    end

    if snapshotProviderCount >= MAX_SNAPSHOT_PROVIDERS then
        return false
    end

    snapshotProviderCount = snapshotProviderCount + 1
    table.insert(snapshotProviderIDs, providerID)
    table.insert(snapshotProviderCallbacks, callback)
    return true
end

local function GetSnapshotCallbackMetadata(provider)
    if not IsAccessibleValue(provider) or type(provider) ~= "table" then
        return nil, nil
    end

    local providerID = rawget(provider, "providerID")
    local callback = rawget(provider, "GetDiagnosticSnapshot")

    if not IsSafeProviderID(providerID) then
        return nil, nil
    end

    if not IsAccessibleValue(callback) or type(callback) ~= "function" then
        return nil, nil
    end

    return providerID, callback
end

local function CapturePendingSnapshotCallback(provider)
    if pendingSnapshotProviderCount >= MAX_PENDING_SNAPSHOT_CALLBACKS then
        return
    end

    local providerID, callback = GetSnapshotCallbackMetadata(provider)
    if providerID == nil or callback == nil then
        return
    end

    pendingSnapshotProviderCount = pendingSnapshotProviderCount + 1
    table.insert(pendingSnapshotProviderIDs, providerID)
    table.insert(pendingSnapshotProviderCallbacks, callback)
end

local function CaptureExistingPendingCallbacks()
    if type(EnhancedDiagnostics) ~= "table" or type(EnhancedDiagnostics.pendingProviders) ~= "table" then
        return
    end

    for index = 1, MAX_PENDING_SNAPSHOT_CALLBACKS do
        local provider = rawget(EnhancedDiagnostics.pendingProviders, index)
        if provider == nil then
            return
        end

        CapturePendingSnapshotCallback(provider)
    end
end

local function ActivatePendingSnapshotCallbacks()
    for index = 1, pendingSnapshotProviderCount do
        local providerID = rawget(pendingSnapshotProviderIDs, index)
        local callback = rawget(pendingSnapshotProviderCallbacks, index)

        if providerID ~= nil and callback ~= nil then
            ActivateSnapshotCallback(providerID, callback)
        end
    end

    pendingSnapshotProviderCount = 0
    pendingSnapshotProviderIDs = {}
    pendingSnapshotProviderCallbacks = {}
end

local function CheckSnapshotRate(providerID, elapsed)
    local providerRequestCount = rawget(snapshotProviderRequestCounts, providerID) or 0
    if providerRequestCount >= MAX_SNAPSHOTS_PER_PROVIDER then
        return false, "provider snapshot session limit reached"
    end

    if snapshotRequestCount >= MAX_SNAPSHOTS_PER_SESSION then
        return false, "snapshot session limit reached"
    end

    if snapshotGlobalWindowStart == nil or elapsed - snapshotGlobalWindowStart >= 1 then
        snapshotGlobalWindowStart = elapsed
        snapshotGlobalWindowCount = 0
    end

    local providerWindowStart = rawget(snapshotProviderWindowStarts, providerID)
    if providerWindowStart == nil or elapsed - providerWindowStart >= 1 then
        rawset(snapshotProviderWindowStarts, providerID, elapsed)
        rawset(snapshotProviderWindowCounts, providerID, 0)
    end

    local providerWindowCount = rawget(snapshotProviderWindowCounts, providerID) or 0
    if providerWindowCount >= MAX_SNAPSHOTS_PER_PROVIDER_PER_SECOND then
        return false, "provider snapshot rate limit reached"
    end

    if snapshotGlobalWindowCount >= MAX_SNAPSHOTS_GLOBAL_PER_SECOND then
        return false, "global snapshot rate limit reached"
    end

    return true, nil
end

local function CommitSnapshotAttempt(providerID)
    rawset(
        snapshotProviderRequestCounts,
        providerID,
        (rawget(snapshotProviderRequestCounts, providerID) or 0) + 1
    )
    rawset(
        snapshotProviderWindowCounts,
        providerID,
        (rawget(snapshotProviderWindowCounts, providerID) or 0) + 1
    )
    snapshotRequestCount = snapshotRequestCount + 1
    snapshotGlobalWindowCount = snapshotGlobalWindowCount + 1
end

local function CallSnapshotCallback(callback, reason)
    local callbackOkay, fields = pcall(callback, reason)
    if not callbackOkay then return nil, "snapshot callback failed" end
    return BuildSnapshotFields(fields)
end

local function CheckIncidentSnapshotRate(providerID, now)
    if incidentSnapshotGlobalWindowStart == nil or now - incidentSnapshotGlobalWindowStart >= 60 then
        incidentSnapshotGlobalWindowStart = now
        incidentSnapshotGlobalWindowCount = 0
    end
    local providerStart = rawget(incidentSnapshotProviderWindowStarts, providerID)
    if providerStart == nil or now - providerStart >= 60 then
        rawset(incidentSnapshotProviderWindowStarts, providerID, now)
        rawset(incidentSnapshotProviderWindowCounts, providerID, 0)
    end
    if (rawget(incidentSnapshotProviderWindowCounts, providerID) or 0) >= MAX_INCIDENT_SNAPSHOT_REQUESTS_PER_PROVIDER_PER_MINUTE then
        return false, "incident provider snapshot rate limit reached"
    end
    if incidentSnapshotGlobalWindowCount >= MAX_INCIDENT_SNAPSHOT_REQUESTS_GLOBAL_PER_MINUTE then
        return false, "incident global snapshot rate limit reached"
    end
    return true, nil
end

local function CommitIncidentSnapshotAttempt(providerID)
    rawset(incidentSnapshotProviderWindowCounts, providerID, (rawget(incidentSnapshotProviderWindowCounts, providerID) or 0) + 1)
    incidentSnapshotGlobalWindowCount = incidentSnapshotGlobalWindowCount + 1
    incidentSnapshotRequestCount = incidentSnapshotRequestCount + 1
end

local function AppendSnapshotEvidence(line)
    local nextEvidence = line

    if snapshotEvidence ~= "" then
        nextEvidence = string.format("%s\n%s", snapshotEvidence, line)
    end

    if string.len(nextEvidence) > MAX_SNAPSHOT_EVIDENCE_CHARACTERS then
        return false
    end

    snapshotEvidence = nextEvidence
    return true
end

function EnhancedDiagnosticsSnapshots:RequestProviderSnapshot(providerID, reason)
    if not IsRecording() then
        return false, "not recording"
    end

    if not snapshotSessionActive then
        ResetSnapshotSession()
    end

    if not IsValidSnapshotReason(reason) then
        return RejectSnapshot("invalid snapshot reason")
    end

    local providerIndex = FindSnapshotProviderIndex(providerID)
    if providerIndex == nil then
        return RejectSnapshot("snapshot callback unavailable")
    end

    local callback = rawget(snapshotProviderCallbacks, providerIndex)
    if not IsAccessibleValue(callback) or type(callback) ~= "function" then
        return RejectSnapshot("snapshot callback unavailable")
    end

    local elapsed = GetSnapshotElapsed()
    local withinRate, rateReason = CheckSnapshotRate(providerID, elapsed)
    if not withinRate then
        return DropSnapshot(rateReason)
    end

    CommitSnapshotAttempt(providerID)

    local fieldText, fieldReason = CallSnapshotCallback(callback, reason)
    if fieldText == nil then return RejectSnapshot(fieldReason) end

    local line = string.format("%.3fs | %s | SNAPSHOT | %s", elapsed, providerID, reason)
    if fieldText ~= "" then
        line = string.format("%s | %s", line, fieldText)
    end

    if not AppendSnapshotEvidence(line) then
        return DropSnapshot("snapshot evidence character limit reached")
    end

    snapshotCount = snapshotCount + 1
    lastSnapshotReason = nil
    return true, "recorded"
end

function EnhancedDiagnosticsSnapshots:CaptureIncidentSnapshot(providerID)
    if not IsSafeProviderID(providerID) then
        incidentSnapshotRejectedCount = incidentSnapshotRejectedCount + 1
        lastIncidentSnapshotReason = "invalid incident snapshot provider"
        return false, "", lastIncidentSnapshotReason
    end
    local providerIndex = FindSnapshotProviderIndex(providerID)
    if providerIndex == nil then
        incidentSnapshotRejectedCount = incidentSnapshotRejectedCount + 1
        lastIncidentSnapshotReason = "incident snapshot callback unavailable"
        return false, "", lastIncidentSnapshotReason
    end
    local callback = rawget(snapshotProviderCallbacks, providerIndex)
    if not IsAccessibleValue(callback) or type(callback) ~= "function" then
        incidentSnapshotRejectedCount = incidentSnapshotRejectedCount + 1
        lastIncidentSnapshotReason = "incident snapshot callback unavailable"
        return false, "", lastIncidentSnapshotReason
    end
    local now = GetTime()
    local withinRate, rateReason = CheckIncidentSnapshotRate(providerID, now)
    if not withinRate then
        incidentSnapshotDroppedCount = incidentSnapshotDroppedCount + 1
        lastIncidentSnapshotReason = rateReason
        return false, "", rateReason
    end
    CommitIncidentSnapshotAttempt(providerID)
    local fieldText, fieldReason = CallSnapshotCallback(callback, "error_observed")
    if fieldText == nil then
        incidentSnapshotRejectedCount = incidentSnapshotRejectedCount + 1
        lastIncidentSnapshotReason = fieldReason
        return false, "", fieldReason
    end
    local line = string.format("%s | SNAPSHOT | error_observed", providerID)
    if fieldText ~= "" then line = string.format("%s | %s", line, fieldText) end
    if string.len(line) > MAX_INCIDENT_SNAPSHOT_EVIDENCE_CHARACTERS then
        incidentSnapshotDroppedCount = incidentSnapshotDroppedCount + 1
        lastIncidentSnapshotReason = "incident snapshot evidence character limit reached"
        return false, "", lastIncidentSnapshotReason
    end
    incidentSnapshotAcceptedCount = incidentSnapshotAcceptedCount + 1
    lastIncidentSnapshotReason = nil
    return true, line, nil
end

function EnhancedDiagnosticsSnapshots:GetIncidentSnapshotStats()
    return incidentSnapshotRequestCount, incidentSnapshotAcceptedCount, incidentSnapshotDroppedCount, incidentSnapshotRejectedCount, lastIncidentSnapshotReason
end

function EnhancedDiagnosticsSnapshots:RequestAllSnapshots(reason)
    local recorded = 0

    for index = 1, snapshotProviderCount do
        local providerID = rawget(snapshotProviderIDs, index)
        if providerID ~= nil then
            local accepted = EnhancedDiagnosticsSnapshots:RequestProviderSnapshot(providerID, reason)
            if accepted then
                recorded = recorded + 1
            end
        end
    end

    return recorded
end

function EnhancedDiagnosticsSnapshots:GetSnapshotCount()
    return snapshotCount
end

function EnhancedDiagnosticsSnapshots:GetSnapshotEvidence()
    return snapshotEvidence
end

function EnhancedDiagnosticsSnapshots:GetSnapshotStats()
    return snapshotCount,
        droppedSnapshotCount,
        rejectedSnapshotCount,
        snapshotEvidenceTruncated,
        lastSnapshotReason
end

local function CheckObservedErrors()
    local currentErrorCount = GetCapturedLuaErrorCount()
    if currentErrorCount > lastObservedErrorCount then
        EnhancedDiagnosticsSnapshots:RequestAllSnapshots("error_observed")
    end
    lastObservedErrorCount = currentErrorCount
end

local function WrappedRegisterProvider(self, provider)
    local registered, reason = originalRegisterProvider(self, provider)

    if registered and reason == "registered" then
        local providerID, callback = GetSnapshotCallbackMetadata(provider)
        if providerID ~= nil and callback ~= nil then
            ActivateSnapshotCallback(providerID, callback)
        end
    end

    return registered, reason
end

local function WrappedSnapshotSessionStart(self)
    local started = originalSnapshotSessionStart(self)
    if started then
        ResetSnapshotSession()
        EnhancedDiagnosticsSnapshots:RequestAllSnapshots("recording_started")
    end
    return started
end

local function WrappedSnapshotSessionStop(self)
    if IsRecording() and snapshotSessionActive then
        CheckObservedErrors()
        EnhancedDiagnosticsSnapshots:RequestAllSnapshots("recording_stopped")
    end

    local stopped, elapsed = originalSnapshotSessionStop(self)
    if stopped then
        StopSnapshotSession()
    end
    return stopped, elapsed
end

local function WrappedSnapshotMarkProblemNow(self)
    local marked, elapsed = originalSnapshotMarkProblemNow(self)
    if marked and snapshotSessionActive then
        CheckObservedErrors()
        EnhancedDiagnosticsSnapshots:RequestAllSnapshots("user_marker")
    end
    return marked, elapsed
end

local function AttachSnapshotIntegration()
    local broker = AddonDiagnostics.EnhancedDiagnosticsBroker
    local controller = AddonDiagnostics.SessionController

    if not broker or type(broker.RegisterProvider) ~= "function" then
        return false
    end

    if not controller
        or type(controller.Start) ~= "function"
        or type(controller.Stop) ~= "function"
        or type(controller.MarkProblemNow) ~= "function"
    then
        return false
    end

    if originalRegisterProvider ~= nil
        or originalSnapshotSessionStart ~= nil
        or originalSnapshotSessionStop ~= nil
        or originalSnapshotMarkProblemNow ~= nil
    then
        return true
    end

    originalRegisterProvider = broker.RegisterProvider
    broker.RegisterProvider = WrappedRegisterProvider
    ActivatePendingSnapshotCallbacks()

    originalSnapshotSessionStart = controller.Start
    originalSnapshotSessionStop = controller.Stop
    originalSnapshotMarkProblemNow = controller.MarkProblemNow
    controller.Start = WrappedSnapshotSessionStart
    controller.Stop = WrappedSnapshotSessionStop
    controller.MarkProblemNow = WrappedSnapshotMarkProblemNow
    return true
end

local function OnSnapshotBootstrapEvent(self, event, addonName)
    if event ~= "ADDON_LOADED" or addonName ~= "AddonDiagnostics" then
        return
    end

    if AttachSnapshotIntegration() then
        self:UnregisterEvent("ADDON_LOADED")
    end
end

if type(EnhancedDiagnostics) ~= "table" then
    EnhancedDiagnostics = {}
end

if type(EnhancedDiagnostics.pendingProviders) ~= "table" then
    EnhancedDiagnostics.pendingProviders = {}
end

CaptureExistingPendingCallbacks()

snapshotBootstrapFrame = CreateFrame("Frame")
snapshotBootstrapFrame:RegisterEvent("ADDON_LOADED")
snapshotBootstrapFrame:SetScript("OnEvent", OnSnapshotBootstrapEvent)
