#include "RuntimeStatePersistence.h"

#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

namespace
{
int gFailures = 0;

using RenderCadenceCompositor::RuntimeLayerModelSnapshot;
using RenderCadenceCompositor::RuntimeLayerReadModel;
using RenderCadenceCompositor::SerializeRuntimeStateSnapshot;

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

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

std::filesystem::path MakeTestRoot()
{
	const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
	std::filesystem::path root = std::filesystem::temp_directory_path() / ("render-cadence-runtime-state-persistence-tests-" + std::to_string(stamp));
	std::filesystem::create_directories(root);
	return root;
}

std::string ReadTextFile(const std::filesystem::path& path)
{
	std::ifstream input(path, std::ios::binary);
	std::ostringstream buffer;
	buffer << input.rdbuf();
	return buffer.str();
}

ShaderParameterDefinition FloatParameter()
{
	ShaderParameterDefinition definition;
	definition.id = "gain";
	definition.type = ShaderParameterType::Float;
	return definition;
}

ShaderParameterDefinition BooleanParameter()
{
	ShaderParameterDefinition definition;
	definition.id = "enabled";
	definition.type = ShaderParameterType::Boolean;
	return definition;
}

RuntimeLayerModelSnapshot MakeSnapshot(const std::string& layerId, double gain, bool enabled)
{
	RuntimeLayerReadModel layer;
	layer.id = layerId;
	layer.shaderId = "solid";
	layer.shaderName = "Solid";
	layer.bypass = !enabled;
	layer.parameterDefinitions = { FloatParameter(), BooleanParameter() };

	ShaderParameterValue gainValue;
	gainValue.numberValues = { gain };
	layer.parameterValues["gain"] = gainValue;

	ShaderParameterValue enabledValue;
	enabledValue.booleanValue = enabled;
	layer.parameterValues["enabled"] = enabledValue;

	RuntimeLayerModelSnapshot snapshot;
	snapshot.displayLayers.push_back(layer);
	return snapshot;
}

void TestRuntimeStateSerialization()
{
	const std::string serialized = SerializeRuntimeStateSnapshot(MakeSnapshot("layer-a", 0.75, false));
	JsonValue parsed;
	std::string error;
	Expect(ParseJson(serialized, parsed, error), "serialized runtime state parses");
	const JsonValue* layers = parsed.find("layers");
	Expect(layers && layers->isArray() && layers->asArray().size() == 1, "serialized runtime state contains one layer");
	const JsonValue& layer = layers->asArray().front();
	Expect(layer.find("id") && layer.find("id")->asString() == "layer-a", "serialized runtime state preserves layer id");
	Expect(layer.find("shaderId") && layer.find("shaderId")->asString() == "solid", "serialized runtime state preserves shader id");
	Expect(layer.find("bypass") && layer.find("bypass")->asBoolean() == true, "serialized runtime state preserves bypass flag");
	const JsonValue* values = layer.find("parameterValues");
	Expect(values && values->isObject(), "serialized runtime state contains parameter values");
	Expect(values->find("gain") && values->find("gain")->asNumber() == 0.75, "serialized runtime state preserves float values");
	Expect(values->find("enabled") && values->find("enabled")->asBoolean() == false, "serialized runtime state preserves boolean values");
}

void TestRuntimeStateWriterFlushesLatestSnapshot()
{
	const std::filesystem::path root = MakeTestRoot();
	const std::filesystem::path path = root / "runtime_state.json";

	RenderCadenceCompositor::RuntimeStatePersistenceWriter writer;
	writer.Start(path, std::chrono::milliseconds(1000));
	writer.RequestSave(MakeSnapshot("layer-old", 0.25, true));
	writer.RequestSave(MakeSnapshot("layer-new", 0.9, false));
	writer.Stop(true);

	JsonValue parsed;
	std::string error;
	Expect(ParseJson(ReadTextFile(path), parsed, error), "flushed runtime state parses");
	const JsonValue& layer = parsed.find("layers")->asArray().front();
	Expect(layer.find("id")->asString() == "layer-new", "writer flushes the latest pending snapshot");
	Expect(layer.find("parameterValues")->find("gain")->asNumber() == 0.9, "writer flush keeps latest parameter value");

	std::filesystem::remove_all(root);
}

void TestRuntimeStateWriterDebouncesAndWritesInBackground()
{
	const std::filesystem::path root = MakeTestRoot();
	const std::filesystem::path path = root / "nested" / "runtime_state.json";

	{
		RenderCadenceCompositor::RuntimeStatePersistenceWriter writer;
		writer.Start(path, std::chrono::milliseconds(20));
		writer.RequestSave(MakeSnapshot("layer-bg", 0.5, true));
		std::this_thread::sleep_for(std::chrono::milliseconds(120));
		writer.Stop(true);
	}

	JsonValue parsed;
	std::string error;
	Expect(ParseJson(ReadTextFile(path), parsed, error), "background runtime state write parses");
	Expect(parsed.find("layers")->asArray().front().find("id")->asString() == "layer-bg", "background writer creates the runtime state file");

	std::filesystem::remove_all(root);
}
}

int main()
{
	TestRuntimeStateSerialization();
	TestRuntimeStateWriterFlushesLatestSnapshot();
	TestRuntimeStateWriterDebouncesAndWritesInBackground();

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

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