#include "PersistenceWriter.h"

#include <condition_variable>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <string>
#include <vector>

namespace
{
int gFailures = 0;

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

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

PersistenceSnapshot MakeRuntimeSnapshot(const std::string& contents)
{
	PersistenceSnapshot snapshot;
	snapshot.targetKind = PersistenceTargetKind::RuntimeState;
	snapshot.targetPath = std::filesystem::temp_directory_path() / "video-shader-persistence-writer-test.json";
	snapshot.contents = contents;
	snapshot.reason = "test";
	snapshot.debounceKey = "runtime-state";
	snapshot.debounceAllowed = true;
	return snapshot;
}

void TestDebouncedRequestsCoalesceToNewestSnapshot()
{
	std::mutex mutex;
	std::vector<PersistenceSnapshot> writtenSnapshots;
	PersistenceWriter writer(
		std::chrono::milliseconds(1000),
		[&](const PersistenceSnapshot& snapshot, std::string&) {
			std::lock_guard<std::mutex> lock(mutex);
			writtenSnapshots.push_back(snapshot);
			return true;
		});

	std::string error;
	Expect(writer.EnqueueSnapshot(MakeRuntimeSnapshot("first"), error), "first debounced snapshot enqueues");
	Expect(writer.EnqueueSnapshot(MakeRuntimeSnapshot("second"), error), "second debounced snapshot enqueues");

	PersistenceWriterMetrics metrics = writer.GetMetrics();
	Expect(metrics.pendingCount == 1, "debounced snapshots share one pending slot");
	Expect(metrics.enqueuedCount == 1, "first debounced snapshot counts as enqueue");
	Expect(metrics.coalescedCount == 1, "second debounced snapshot counts as coalesced");

	Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "flush drains debounced snapshots");

	{
		std::lock_guard<std::mutex> lock(mutex);
		Expect(writtenSnapshots.size() == 1, "flush writes one coalesced snapshot");
		Expect(!writtenSnapshots.empty() && writtenSnapshots[0].contents == "second", "coalesced writer keeps newest snapshot");
	}

	metrics = writer.GetMetrics();
	Expect(metrics.pendingCount == 0, "flush drains pending debounced snapshot");
	Expect(metrics.writtenCount == 1, "flush records one successful write");
}

void TestImmediateRequestsAreNotCoalesced()
{
	std::mutex mutex;
	std::vector<PersistenceSnapshot> writtenSnapshots;
	PersistenceWriter writer(
		std::chrono::milliseconds(1000),
		[&](const PersistenceSnapshot& snapshot, std::string&) {
			std::lock_guard<std::mutex> lock(mutex);
			writtenSnapshots.push_back(snapshot);
			return true;
		});

	PersistenceSnapshot first = MakeRuntimeSnapshot("first");
	first.debounceAllowed = false;
	PersistenceSnapshot second = MakeRuntimeSnapshot("second");
	second.debounceAllowed = false;

	std::string error;
	Expect(writer.EnqueueSnapshot(first, error), "first immediate snapshot enqueues");
	Expect(writer.EnqueueSnapshot(second, error), "second immediate snapshot enqueues");
	Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "flush drains immediate snapshots");

	{
		std::lock_guard<std::mutex> lock(mutex);
		Expect(writtenSnapshots.size() == 2, "immediate snapshots are written independently");
		Expect(writtenSnapshots.size() == 2 && writtenSnapshots[0].contents == "first" && writtenSnapshots[1].contents == "second",
			"immediate snapshots preserve order");
	}
}

void TestWriteFailureReportsStructuredResult()
{
	std::vector<PersistenceWriteResult> results;
	PersistenceWriter writer(
		std::chrono::milliseconds(1),
		[](const PersistenceSnapshot&, std::string& error) {
			error = "simulated failure";
			return false;
		});
	writer.SetResultCallback([&results](const PersistenceWriteResult& result) {
		results.push_back(result);
	});

	PersistenceSnapshot snapshot = MakeRuntimeSnapshot("payload");
	snapshot.debounceAllowed = false;
	snapshot.reason = "failure-test";

	std::string error;
	Expect(writer.EnqueueSnapshot(snapshot, error), "failing snapshot still enqueues");
	Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "flush reports failing snapshot result");

	Expect(results.size() == 1, "writer reports one failure result");
	Expect(!results.empty() && !results[0].succeeded, "writer result records failure");
	Expect(!results.empty() && results[0].reason == "failure-test", "writer result preserves reason");
	Expect(!results.empty() && results[0].errorMessage == "simulated failure", "writer result preserves error message");
	Expect(!results.empty() && !results[0].newerRequestPending, "writer result reports no newer pending request");
	Expect(writer.GetMetrics().failedCount == 1, "writer metrics count failed writes");
}

void TestShutdownFlushDrainsPendingSnapshotAndRejectsNewRequests()
{
	std::mutex mutex;
	std::vector<PersistenceSnapshot> writtenSnapshots;
	PersistenceWriter writer(
		std::chrono::milliseconds(1000),
		[&](const PersistenceSnapshot& snapshot, std::string&) {
			std::lock_guard<std::mutex> lock(mutex);
			writtenSnapshots.push_back(snapshot);
			return true;
		});

	std::string error;
	Expect(writer.EnqueueSnapshot(MakeRuntimeSnapshot("pending"), error), "pending snapshot enqueues before shutdown");
	Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "bounded shutdown flush completes");

	{
		std::lock_guard<std::mutex> lock(mutex);
		Expect(writtenSnapshots.size() == 1, "shutdown flush writes pending debounced snapshot");
		Expect(!writtenSnapshots.empty() && writtenSnapshots[0].contents == "pending", "shutdown flush preserves pending snapshot contents");
	}

	Expect(!writer.EnqueueSnapshot(MakeRuntimeSnapshot("late"), error), "writer rejects requests after shutdown flush");
}

void TestShutdownFlushTimeoutCanBeRetried()
{
	std::mutex mutex;
	std::condition_variable condition;
	bool sinkStarted = false;
	bool releaseSink = false;
	PersistenceWriter writer(
		std::chrono::milliseconds(1),
		[&](const PersistenceSnapshot&, std::string&) {
			std::unique_lock<std::mutex> lock(mutex);
			sinkStarted = true;
			condition.notify_all();
			condition.wait(lock, [&]() { return releaseSink; });
			return true;
		});

	PersistenceSnapshot snapshot = MakeRuntimeSnapshot("slow");
	snapshot.debounceAllowed = false;

	std::string error;
	Expect(writer.EnqueueSnapshot(snapshot, error), "slow snapshot enqueues");
	{
		std::unique_lock<std::mutex> lock(mutex);
		Expect(condition.wait_for(lock, std::chrono::seconds(1), [&]() { return sinkStarted; }),
			"slow sink starts before timeout test");
	}

	Expect(!writer.StopAndFlush(std::chrono::milliseconds(10), error), "bounded shutdown flush reports timeout");
	Expect(error.find("Timed out") != std::string::npos, "shutdown timeout returns a useful error");

	{
		std::lock_guard<std::mutex> lock(mutex);
		releaseSink = true;
	}
	condition.notify_all();
	error.clear();
	Expect(writer.StopAndFlush(std::chrono::seconds(1), error), "shutdown flush can complete after earlier timeout");
}
}

int main()
{
	TestDebouncedRequestsCoalesceToNewestSnapshot();
	TestImmediateRequestsAreNotCoalesced();
	TestWriteFailureReportsStructuredResult();
	TestShutdownFlushDrainsPendingSnapshotAndRejectsNewRequests();
	TestShutdownFlushTimeoutCanBeRetried();

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

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