#include "HealthTelemetry.h"

#include <iostream>

namespace
{
int gFailures = 0;

void Expect(bool condition, const char* message)
{
	if (condition)
		return;

	std::cerr << "FAIL: " << message << "\n";
	++gFailures;
}

void TestRuntimeEventQueueMetrics()
{
	HealthTelemetry telemetry;
	telemetry.RecordRuntimeEventQueueMetrics("runtime-events", 3, 64, 2, 12.5);

	const HealthTelemetry::RuntimeEventMetricsSnapshot metrics = telemetry.GetRuntimeEventMetricsSnapshot();
	Expect(metrics.queue.queueName == "runtime-events", "queue metrics store queue name");
	Expect(metrics.queue.depth == 3, "queue metrics store depth");
	Expect(metrics.queue.capacity == 64, "queue metrics store capacity");
	Expect(metrics.queue.droppedCount == 2, "queue metrics store dropped count");
	Expect(metrics.queue.oldestEventAgeMilliseconds == 12.5, "queue metrics store oldest event age");
}

void TestRuntimeEventDispatchStats()
{
	HealthTelemetry telemetry;
	telemetry.RecordRuntimeEventDispatchStats(2, 5, 1, 0.75);
	telemetry.RecordRuntimeEventDispatchStats(3, 6, 0, 0.25);

	const HealthTelemetry::Snapshot snapshot = telemetry.GetSnapshot();
	Expect(snapshot.runtimeEvents.dispatch.dispatchCallCount == 2, "dispatch stats count dispatch calls");
	Expect(snapshot.runtimeEvents.dispatch.dispatchedEventCount == 5, "dispatch stats accumulate dispatched events");
	Expect(snapshot.runtimeEvents.dispatch.handlerInvocationCount == 11, "dispatch stats accumulate handler invocations");
	Expect(snapshot.runtimeEvents.dispatch.handlerFailureCount == 1, "dispatch stats accumulate handler failures");
	Expect(snapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds == 0.25, "dispatch stats store latest duration");
	Expect(snapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds == 0.75, "dispatch stats store max duration");
}

void TestRuntimeEventTryRecord()
{
	HealthTelemetry telemetry;
	Expect(telemetry.TryRecordRuntimeEventQueueMetrics("runtime-events", 1, 4, 0, -5.0), "try queue metrics succeeds when uncontended");
	Expect(telemetry.TryRecordRuntimeEventDispatchStats(1, 2, 0, -1.0), "try dispatch stats succeeds when uncontended");

	const HealthTelemetry::RuntimeEventMetricsSnapshot metrics = telemetry.GetRuntimeEventMetricsSnapshot();
	Expect(metrics.queue.oldestEventAgeMilliseconds == 0.0, "queue age is clamped to non-negative values");
	Expect(metrics.dispatch.lastDispatchDurationMilliseconds == 0.0, "dispatch duration is clamped to non-negative values");
}

void TestPersistenceWriteHealth()
{
	HealthTelemetry telemetry;
	telemetry.RecordPersistenceWriteResult(false, "runtime-state", "runtime/runtime_state.json", "UpdateLayerParameter",
		"disk full", true);

	HealthTelemetry::PersistenceSnapshot persistence = telemetry.GetPersistenceSnapshot();
	Expect(persistence.writeFailureCount == 1, "persistence health counts write failures");
	Expect(!persistence.lastWriteSucceeded, "persistence health records failed write state");
	Expect(persistence.unsavedChanges, "persistence health reports unsaved changes after failure");
	Expect(persistence.newerRequestPending, "persistence health records pending newer request");
	Expect(persistence.lastTargetKind == "runtime-state", "persistence health records target kind");
	Expect(persistence.lastReason == "UpdateLayerParameter", "persistence health records reason");
	Expect(persistence.lastErrorMessage == "disk full", "persistence health records error message");

	Expect(telemetry.TryRecordPersistenceWriteResult(true, "runtime-state", "runtime/runtime_state.json", "flush", "", false),
		"try persistence health succeeds when uncontended");
	persistence = telemetry.GetPersistenceSnapshot();
	Expect(persistence.writeSuccessCount == 1, "persistence health counts write successes");
	Expect(persistence.lastWriteSucceeded, "persistence health records successful write state");
	Expect(!persistence.unsavedChanges, "persistence health clears unsaved changes after latest successful write with no pending request");
}

void TestBackendPlayoutHealth()
{
	HealthTelemetry telemetry;
	telemetry.RecordBackendPlayoutHealth(
		"Degraded",
		"Dropped",
		1,
		4,
		12,
		0,
		3,
		4,
		10,
		2,
		1,
		8.5,
		7.25,
		12.0,
		1.0,
		6.5,
		0.5,
		8,
		11,
		3,
		2,
		2,
		1,
		2,
		5,
		3,
		1,
		true,
		"Output underrun");

	const HealthTelemetry::BackendPlayoutSnapshot playout = telemetry.GetBackendPlayoutSnapshot();
	Expect(playout.lifecycleState == "Degraded", "backend playout health stores lifecycle state");
	Expect(playout.completionResult == "Dropped", "backend playout health stores completion result");
	Expect(playout.readyQueueDepth == 1, "backend playout health stores ready queue depth");
	Expect(playout.readyQueueCapacity == 4, "backend playout health stores ready queue capacity");
	Expect(playout.minReadyQueueDepth == 0, "backend playout health stores min ready queue depth");
	Expect(playout.maxReadyQueueDepth == 3, "backend playout health stores max ready queue depth");
	Expect(playout.readyQueueZeroDepthCount == 4, "backend playout health stores zero-depth queue samples");
	Expect(playout.readyQueueDroppedCount == 2, "backend playout health stores queue dropped count");
	Expect(playout.readyQueueUnderrunCount == 1, "backend playout health stores queue underrun count");
	Expect(playout.outputRenderMilliseconds == 8.5, "backend playout health stores output render duration");
	Expect(playout.smoothedOutputRenderMilliseconds == 7.25, "backend playout health stores smoothed output render duration");
	Expect(playout.maxOutputRenderMilliseconds == 12.0, "backend playout health stores max output render duration");
	Expect(playout.outputFrameAcquireMilliseconds == 1.0, "backend playout health stores output frame acquire duration");
	Expect(playout.outputFrameRenderRequestMilliseconds == 6.5, "backend playout health stores output render request duration");
	Expect(playout.outputFrameEndAccessMilliseconds == 0.5, "backend playout health stores output frame end access duration");
	Expect(playout.completedFrameIndex == 8, "backend playout health stores completed index");
	Expect(playout.scheduledFrameIndex == 11, "backend playout health stores scheduled index");
	Expect(playout.scheduledLeadFrames == 3, "backend playout health stores synthetic scheduled lead");
	Expect(playout.measuredLagFrames == 2, "backend playout health stores measured lag");
	Expect(playout.catchUpFrames == 2, "backend playout health stores catch-up frames");
	Expect(playout.lateStreak == 1, "backend playout health stores late streak");
	Expect(playout.dropStreak == 2, "backend playout health stores drop streak");
	Expect(playout.lateFrameCount == 5, "backend playout health stores late frame count");
	Expect(playout.droppedFrameCount == 3, "backend playout health stores dropped frame count");
	Expect(playout.flushedFrameCount == 1, "backend playout health stores flushed frame count");
	Expect(playout.degraded, "backend playout health stores degraded state");
	Expect(playout.statusMessage == "Output underrun", "backend playout health stores status message");

	Expect(telemetry.TryRecordBackendPlayoutHealth(
		"Running",
		"Completed",
		2,
		4,
		13,
		1,
		3,
		4,
		11,
		2,
		1,
		-5.0,
		-4.0,
		-3.0,
		-2.0,
		-1.0,
		-0.5,
		9,
		12,
		3,
		0,
		0,
		0,
		0,
		5,
		3,
		1,
		false,
		""),
		"try backend playout health succeeds when uncontended");
	const HealthTelemetry::Snapshot snapshot = telemetry.GetSnapshot();
	Expect(snapshot.backendPlayout.lifecycleState == "Running", "full health snapshot includes backend playout state");
	Expect(!snapshot.backendPlayout.degraded, "full health snapshot includes backend degraded state");
}

void TestOutputRenderPipelineTiming()
{
	HealthTelemetry telemetry;
	telemetry.RecordOutputRenderQueueWait(2.5);
	telemetry.RecordOutputRenderPipelineTiming(1.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.1, 0.2, 0.3, 0.4, 3.5, true, true, false);
	Expect(telemetry.TryRecordOutputRenderPipelineTiming(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, false, false, true),
		"try output render timing succeeds when uncontended");

	const HealthTelemetry::BackendPlayoutSnapshot playout = telemetry.GetBackendPlayoutSnapshot();
	Expect(playout.outputRenderQueueWaitMilliseconds == 2.5, "output render timing stores queue wait");
	Expect(playout.outputRenderDrawMilliseconds == 0.0, "output render timing clamps draw duration");
	Expect(playout.outputReadbackFenceWaitMilliseconds == 0.0, "output render timing clamps fence wait duration");
	Expect(playout.outputReadbackMapMilliseconds == 0.0, "output render timing clamps map duration");
	Expect(playout.outputReadbackCopyMilliseconds == 0.0, "output render timing clamps readback copy duration");
	Expect(playout.outputCachedCopyMilliseconds == 0.0, "output render timing clamps cached copy duration");
	Expect(playout.outputAsyncQueueMilliseconds == 0.0, "output render timing clamps async queue duration");
	Expect(playout.outputAsyncQueueBufferMilliseconds == 0.0, "output render timing clamps async queue buffer duration");
	Expect(playout.outputAsyncQueueSetupMilliseconds == 0.0, "output render timing clamps async queue setup duration");
	Expect(playout.outputAsyncQueueReadPixelsMilliseconds == 0.0, "output render timing clamps async queue read pixels duration");
	Expect(playout.outputAsyncQueueFenceMilliseconds == 0.0, "output render timing clamps async queue fence duration");
	Expect(playout.outputSyncReadMilliseconds == 0.0, "output render timing clamps sync read duration");
	Expect(playout.outputAsyncReadbackMissCount == 1, "output render timing counts async readback misses");
	Expect(playout.outputCachedFallbackCount == 1, "output render timing counts cached fallbacks");
	Expect(playout.outputSyncFallbackCount == 1, "output render timing counts sync fallbacks");
}

void TestSystemMemoryPlayoutStats()
{
	HealthTelemetry telemetry;
	telemetry.RecordSystemMemoryPlayoutStats(2, 3, 1, 4, 5, 6, 12.5, 24.0);

	HealthTelemetry::BackendPlayoutSnapshot playout = telemetry.GetBackendPlayoutSnapshot();
	Expect(playout.systemFramePoolFree == 2, "system-memory playout stores free frame count");
	Expect(playout.systemFramePoolReady == 3, "system-memory playout stores ready frame count");
	Expect(playout.systemFramePoolScheduled == 1, "system-memory playout stores scheduled frame count");
	Expect(playout.systemFrameUnderrunCount == 4, "system-memory playout stores underrun count");
	Expect(playout.systemFrameRepeatCount == 5, "system-memory playout stores repeat count");
	Expect(playout.systemFrameDropCount == 6, "system-memory playout stores drop count");
	Expect(playout.systemFrameAgeAtScheduleMilliseconds == 12.5, "system-memory playout stores schedule age");
	Expect(playout.systemFrameAgeAtCompletionMilliseconds == 24.0, "system-memory playout stores completion age");

	Expect(telemetry.TryRecordSystemMemoryPlayoutStats(1, 0, 2, 7, 8, 9, -1.0, -2.0),
		"try system-memory playout stats succeeds when uncontended");
	playout = telemetry.GetBackendPlayoutSnapshot();
	Expect(playout.systemFramePoolFree == 1, "try system-memory playout stores free frame count");
	Expect(playout.systemFramePoolReady == 0, "try system-memory playout stores ready frame count");
	Expect(playout.systemFramePoolScheduled == 2, "try system-memory playout stores scheduled frame count");
	Expect(playout.systemFrameUnderrunCount == 7, "try system-memory playout stores underrun count");
	Expect(playout.systemFrameRepeatCount == 8, "try system-memory playout stores repeat count");
	Expect(playout.systemFrameDropCount == 9, "try system-memory playout stores drop count");
	Expect(playout.systemFrameAgeAtScheduleMilliseconds == 0.0, "system-memory playout clamps negative schedule age");
	Expect(playout.systemFrameAgeAtCompletionMilliseconds == 0.0, "system-memory playout clamps negative completion age");
}

void TestDeckLinkBufferTelemetry()
{
	HealthTelemetry telemetry;
	telemetry.RecordDeckLinkBufferTelemetry(true, 4, 5, 0.25, 2);

	HealthTelemetry::BackendPlayoutSnapshot playout = telemetry.GetBackendPlayoutSnapshot();
	Expect(playout.actualDeckLinkBufferedFramesAvailable, "DeckLink buffer telemetry records availability");
	Expect(playout.actualDeckLinkBufferedFrames == 4, "DeckLink buffer telemetry stores actual device depth");
	Expect(playout.targetDeckLinkBufferedFrames == 5, "DeckLink buffer telemetry stores target device depth");
	Expect(playout.deckLinkScheduleCallMilliseconds == 0.25, "DeckLink buffer telemetry stores schedule call duration");
	Expect(playout.deckLinkScheduleFailureCount == 2, "DeckLink buffer telemetry stores schedule failures");

	Expect(telemetry.TryRecordDeckLinkBufferTelemetry(false, 9, 3, -1.0, 7),
		"try DeckLink buffer telemetry succeeds when uncontended");
	playout = telemetry.GetBackendPlayoutSnapshot();
	Expect(!playout.actualDeckLinkBufferedFramesAvailable, "DeckLink buffer telemetry records unavailable device depth");
	Expect(playout.actualDeckLinkBufferedFrames == 0, "unavailable DeckLink device depth clears actual count");
	Expect(playout.targetDeckLinkBufferedFrames == 3, "try DeckLink buffer telemetry stores target device depth");
	Expect(playout.deckLinkScheduleCallMilliseconds == 0.0, "DeckLink buffer telemetry clamps negative schedule call duration");
	Expect(playout.deckLinkScheduleFailureCount == 7, "try DeckLink buffer telemetry stores schedule failures");
}
}

int main()
{
	TestRuntimeEventQueueMetrics();
	TestRuntimeEventDispatchStats();
	TestRuntimeEventTryRecord();
	TestPersistenceWriteHealth();
	TestBackendPlayoutHealth();
	TestOutputRenderPipelineTiming();
	TestSystemMemoryPlayoutStats();
	TestDeckLinkBufferTelemetry();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " HealthTelemetry test failure(s).\n";
		return 1;
	}

	std::cout << "HealthTelemetry tests passed.\n";
	return 0;
}
