AddonDiagnostics = AddonDiagnostics or {}
AddonDiagnostics.DiagnosticIntegrity = {}
local DiagnosticIntegrity = AddonDiagnostics.DiagnosticIntegrity
local Sanitizer = AddonDiagnostics.Sanitizer

local MAX_DIAGNOSTIC_INTEGRITY_CHARACTERS = 8192
local MAX_INTEGRITY_REASON_CHARACTERS = 120

local diagnosticIntegrityEvidence = ""
local diagnosticIntegrityReady = false
local originalIntegritySessionStart = nil
local originalIntegritySessionStop = nil

local function SafeCount(value)
    return math.max(0, tonumber(value) or 0)
end

local function BooleanText(value)
    return tostring(value == true)
end

local function BoundIntegrityReason(reason)
    if type(reason) ~= "string" or reason == "" then
        return "none"
    end

    if string.len(reason) > MAX_INTEGRITY_REASON_CHARACTERS then
        return string.sub(reason, 1, MAX_INTEGRITY_REASON_CHARACTERS)
    end

    return reason
end

local function ResetDiagnosticIntegrity()
    diagnosticIntegrityEvidence = ""
    diagnosticIntegrityReady = false
end

local function BuildDiagnosticIntegrityEvidence()
    local controller = AddonDiagnostics.SessionController
    local observer = AddonDiagnostics.UniversalObserver
    local timeline = AddonDiagnostics.SessionTimeline
    local broker = AddonDiagnostics.EnhancedDiagnosticsBroker
    local snapshots = AddonDiagnostics.EnhancedDiagnosticsSnapshots

    local controllerAvailable = controller ~= nil
        and type(controller.IsSessionEnvironmentTruncated) == "function"
        and type(controller.GetSessionLoadedAddonCount) == "function"
        and type(controller.GetSessionDroppedLoadedAddonCount) == "function"
        and type(controller.GetPerformanceSnapshotCount) == "function"
        and type(controller.IsPerformanceEvidenceTruncated) == "function"
        and type(controller.GetMarkerCount) == "function"
        and type(controller.GetDroppedMarkerCount) == "function"
    local observerAvailable = observer ~= nil and type(observer.GetLuaErrorCaptureStats) == "function"
    local timelineAvailable = timeline ~= nil and type(timeline.GetTimelineStats) == "function"
    local brokerAvailable = broker ~= nil
        and type(broker.GetProviderCount) == "function"
        and type(broker.GetRejectedProviderCount) == "function"
        and type(broker.GetLastRejectedReason) == "function"
        and type(broker.IsProviderSummaryTruncated) == "function"
        and type(broker.DidPendingRegistrationOverflow) == "function"
        and type(broker.GetBreadcrumbStats) == "function"
        and type(broker.GetSpanStats) == "function"
    local snapshotsAvailable = snapshots ~= nil and type(snapshots.GetSnapshotStats) == "function"

    local loadedAddonCount = 0
    local droppedLoadedAddonCount = 0
    local environmentTruncated = false
    local performanceSnapshotCount = 0
    local performanceTruncated = false
    local markerCount = 0
    local droppedMarkerCount = 0

    if controllerAvailable then
        loadedAddonCount = SafeCount(controller:GetSessionLoadedAddonCount())
        droppedLoadedAddonCount = SafeCount(controller:GetSessionDroppedLoadedAddonCount())
        environmentTruncated = controller:IsSessionEnvironmentTruncated() == true
        performanceSnapshotCount = SafeCount(controller:GetPerformanceSnapshotCount())
        performanceTruncated = controller:IsPerformanceEvidenceTruncated() == true
        markerCount = SafeCount(controller:GetMarkerCount())
        droppedMarkerCount = SafeCount(controller:GetDroppedMarkerCount())
    end

    local luaErrorCount = 0
    local droppedLuaErrorCount = 0
    local luaErrorTruncated = false
    local luaErrorCaptureAvailable = false
    local luaErrorHandlerChanged = false
    local luaErrorForwardFailed = false

    if observerAvailable then
        local captured, dropped, _, _, truncated, captureAvailable, handlerChanged, forwardFailed =
            observer:GetLuaErrorCaptureStats()
        luaErrorCount = SafeCount(captured)
        droppedLuaErrorCount = SafeCount(dropped)
        luaErrorTruncated = truncated == true
        luaErrorCaptureAvailable = captureAvailable == true
        luaErrorHandlerChanged = handlerChanged == true
        luaErrorForwardFailed = forwardFailed == true
    end

    local timelineCount = 0
    local droppedTimelineCount = 0
    local timelineTruncated = false

    if timelineAvailable then
        local count, dropped, truncated = timeline:GetTimelineStats()
        timelineCount = SafeCount(count)
        droppedTimelineCount = SafeCount(dropped)
        timelineTruncated = truncated == true
    end

    local providerCount = 0
    local rejectedProviderCount = 0
    local providerSummaryTruncated = false
    local pendingRegistrationOverflow = false
    local providerReason = "none"
    local breadcrumbCount = 0
    local droppedBreadcrumbCount = 0
    local coalescedBreadcrumbCount = 0
    local hardCapacityDroppedBreadcrumbCount = 0
    local rateLimitedBreadcrumbCount = 0
    local rejectedBreadcrumbCount = 0
    local breadcrumbTruncated = false
    local breadcrumbReason = "none"
    local spanCount = 0
    local droppedSpanCount = 0
    local rejectedSpanCount = 0
    local spanTruncated = false
    local spanReason = "none"

    if brokerAvailable then
        providerCount = SafeCount(broker:GetProviderCount())
        rejectedProviderCount = SafeCount(broker:GetRejectedProviderCount())
        providerSummaryTruncated = broker:IsProviderSummaryTruncated() == true
        pendingRegistrationOverflow = broker:DidPendingRegistrationOverflow() == true
        providerReason = BoundIntegrityReason(broker:GetLastRejectedReason())

        local breadcrumbRecorded, breadcrumbDropped, breadcrumbRejected, breadcrumbWasTruncated, breadcrumbLastReason,
            breadcrumbCoalesced, breadcrumbHardCapacityDropped, breadcrumbRateLimited = broker:GetBreadcrumbStats()
        breadcrumbCount = SafeCount(breadcrumbRecorded)
        droppedBreadcrumbCount = SafeCount(breadcrumbDropped)
        coalescedBreadcrumbCount = SafeCount(breadcrumbCoalesced)
        hardCapacityDroppedBreadcrumbCount = SafeCount(breadcrumbHardCapacityDropped)
        rateLimitedBreadcrumbCount = SafeCount(breadcrumbRateLimited)
        rejectedBreadcrumbCount = SafeCount(breadcrumbRejected)
        breadcrumbTruncated = breadcrumbWasTruncated == true
        breadcrumbReason = BoundIntegrityReason(breadcrumbLastReason)

        local spanCompleted, _, spanDropped, spanRejected, spanWasTruncated, spanLastReason = broker:GetSpanStats()
        spanCount = SafeCount(spanCompleted)
        droppedSpanCount = SafeCount(spanDropped)
        rejectedSpanCount = SafeCount(spanRejected)
        spanTruncated = spanWasTruncated == true
        spanReason = BoundIntegrityReason(spanLastReason)
    end

    local snapshotCount = 0
    local droppedSnapshotCount = 0
    local rejectedSnapshotCount = 0
    local snapshotTruncated = false
    local snapshotReason = "none"

    if snapshotsAvailable then
        local recorded, dropped, rejected, truncated, reason = snapshots:GetSnapshotStats()
        snapshotCount = SafeCount(recorded)
        droppedSnapshotCount = SafeCount(dropped)
        rejectedSnapshotCount = SafeCount(rejected)
        snapshotTruncated = truncated == true
        snapshotReason = BoundIntegrityReason(reason)
    end

    local degraded = not controllerAvailable
        or not observerAvailable
        or not timelineAvailable
        or not brokerAvailable
        or not snapshotsAvailable
        or environmentTruncated
        or droppedLoadedAddonCount > 0
        or performanceSnapshotCount < 2
        or performanceTruncated
        or droppedMarkerCount > 0
        or not luaErrorCaptureAvailable
        or droppedLuaErrorCount > 0
        or luaErrorTruncated
        or luaErrorHandlerChanged
        or luaErrorForwardFailed
        or droppedTimelineCount > 0
        or timelineTruncated
        or rejectedProviderCount > 0
        or providerSummaryTruncated
        or pendingRegistrationOverflow
        or droppedBreadcrumbCount > 0
        or rejectedBreadcrumbCount > 0
        or breadcrumbTruncated
        or droppedSpanCount > 0
        or rejectedSpanCount > 0
        or spanTruncated
        or droppedSnapshotCount > 0
        or rejectedSnapshotCount > 0
        or snapshotTruncated

    local status = "complete"
    if degraded then
        status = "degraded"
    end

    local output = string.format(
        "Recorder-source integrity: %s\nIntegrity sources available: session=%s lua_errors=%s timeline=%s enhanced=%s snapshots=%s\nEnvironment: loaded=%d dropped=%d truncated=%s\nPerformance: snapshots=%d expected=2 truncated=%s\nProblem markers: recorded=%d dropped=%d\nLua errors: captured=%d dropped=%d truncated=%s capture_available=%s handler_changed=%s forward_failed=%s\nTimeline: events=%d dropped=%d truncated=%s\nEnhanced Diagnostics provider coverage: registered=%d rejected=%d summary_truncated=%s pending_overflow=%s reason=%s\nEnhanced Diagnostics breadcrumbs: retained_unique=%d coalesced_repetitions=%d hard_capacity_dropped=%d rate_limited=%d rejected=%d truncated=%s reason=%s\nEnhanced Diagnostics spans: completed=%d dropped=%d rejected=%d truncated=%s reason=%s\nEnhanced Diagnostics snapshots: recorded=%d dropped=%d rejected=%d truncated=%s reason=%s\nIntegrity summary truncated: false",
        status,
        BooleanText(controllerAvailable),
        BooleanText(observerAvailable),
        BooleanText(timelineAvailable),
        BooleanText(brokerAvailable),
        BooleanText(snapshotsAvailable),
        loadedAddonCount,
        droppedLoadedAddonCount,
        BooleanText(environmentTruncated),
        performanceSnapshotCount,
        BooleanText(performanceTruncated),
        markerCount,
        droppedMarkerCount,
        luaErrorCount,
        droppedLuaErrorCount,
        BooleanText(luaErrorTruncated),
        BooleanText(luaErrorCaptureAvailable),
        BooleanText(luaErrorHandlerChanged),
        BooleanText(luaErrorForwardFailed),
        timelineCount,
        droppedTimelineCount,
        BooleanText(timelineTruncated),
        providerCount,
        rejectedProviderCount,
        BooleanText(providerSummaryTruncated),
        BooleanText(pendingRegistrationOverflow),
        providerReason,
        breadcrumbCount,
        coalescedBreadcrumbCount,
        hardCapacityDroppedBreadcrumbCount,
        rateLimitedBreadcrumbCount,
        rejectedBreadcrumbCount,
        BooleanText(breadcrumbTruncated),
        breadcrumbReason,
        spanCount,
        droppedSpanCount,
        rejectedSpanCount,
        BooleanText(spanTruncated),
        spanReason,
        snapshotCount,
        droppedSnapshotCount,
        rejectedSnapshotCount,
        BooleanText(snapshotTruncated),
        snapshotReason
    )

    if string.len(output) > MAX_DIAGNOSTIC_INTEGRITY_CHARACTERS then
        output = "Recorder-source integrity: degraded\nIntegrity summary truncated: true\nReason: diagnostic integrity summary limit reached"
    end

    diagnosticIntegrityEvidence = output
    diagnosticIntegrityReady = true
end

function DiagnosticIntegrity:IsReady()
    return diagnosticIntegrityReady
end

function DiagnosticIntegrity:GetEvidence()
    return diagnosticIntegrityEvidence
end

function Sanitizer:IsDiagnosticIntegrityReady()
    return diagnosticIntegrityReady
end

function Sanitizer:GetSanitizedDiagnosticIntegrityEvidence()
    return diagnosticIntegrityEvidence
end

local function WrappedIntegritySessionStart(self)
    local started = originalIntegritySessionStart(self)
    if started then
        ResetDiagnosticIntegrity()
    end
    return started
end

local function WrappedIntegritySessionStop(self)
    local stopped, elapsed = originalIntegritySessionStop(self)
    if stopped then
        BuildDiagnosticIntegrityEvidence()
    end
    return stopped, elapsed
end

local function AttachDiagnosticIntegrityLifecycle()
    local controller = AddonDiagnostics.SessionController
    if not controller or type(controller.Start) ~= "function" or type(controller.Stop) ~= "function" then
        return
    end

    if originalIntegritySessionStart ~= nil or originalIntegritySessionStop ~= nil then
        return
    end

    originalIntegritySessionStart = controller.Start
    originalIntegritySessionStop = controller.Stop
    controller.Start = WrappedIntegritySessionStart
    controller.Stop = WrappedIntegritySessionStop
end

AttachDiagnosticIntegrityLifecycle()
