#include "Stdafx.h"
#include "Common/FSecure/C3/Internals/BackendCommons.h"
#include "Common/FSecure/Crypto/Base64.h"
#include "GateRelay.h"
#include "NodeRelay.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Anonymous namespace with helpers.
namespace
{
	/// Obtains Gateway's encryption keys. If the key file exists, it's contents will be Base64 decoded and validated. Otherwise they will be generated.
	/// @param keysFilePath path to the keys file.
	/// @return std::tuple with a flag (true if keys were read, false if generated) and all the keys.
	std::tuple<bool, FSecure::Crypto::SignatureKeys, FSecure::Crypto::SymmetricKey> ReadFromFileOrGenerateGatewayKeys(std::filesystem::path const& keysFilePath)
	{
		// Check if file exists.
		if (std::filesystem::exists(keysFilePath))
			try
			{
				// Parse keys.
				auto keys = json::parse(std::ifstream{ keysFilePath, std::ios::in });

				// Helper lambda to read patch entries. TODO: rewrite as a template lambda when C++20 is out.
				auto ParseEntry = [&keys](auto* typePtr, auto const& entryName) -> std::remove_pointer_t<decltype(typePtr)>
				{
					try
					{
						return cppcodec::base64_rfc4648::decode<FSecure::ByteVector>(keys[entryName].template get<std::string>());
					}
					catch (std::exception& exception)
					{
						throw std::runtime_error{ OBF("Couldn't parse ") + std::string(entryName) + OBF(" element from patch. ") + exception.what() };
					}
					catch (...)
					{
						throw std::runtime_error{ OBF("Caught an unknown exception while parsing ") + std::string(entryName) + OBF(" element from patch.") };
					}
				};

				// Parse and return keys.
				return std::make_tuple(true, FSecure::Crypto::SignatureKeys{ ParseEntry((FSecure::Crypto::PrivateSignature*)nullptr, OBF("Private signature")),
					ParseEntry((FSecure::Crypto::PublicSignature*)nullptr, OBF("Public signature")) }, ParseEntry((FSecure::Crypto::SymmetricKey*)nullptr, OBF("Broadcast key")));
			}
			catch (const std::exception & exception)
			{
				throw std::runtime_error{ OBF_STR("Incorrect key file. ") + exception.what() };
			}

		// File doesn't exist. Generate all the keys.
		auto singatures = FSecure::Crypto::GenerateSignatureKeys();
		auto broadcastKey = FSecure::Crypto::GenerateSymmetricKey();

		// Create json tree.
		auto keys = json{};
		keys[OBF("Private signature")] = cppcodec::base64_rfc4648::encode(singatures.first.ToByteVector().data(), FSecure::Crypto::PrivateSignature::Size);
		keys[OBF("Public signature")] = cppcodec::base64_rfc4648::encode(singatures.second.ToByteVector().data(), FSecure::Crypto::PublicSignature::Size);
		keys[OBF("Broadcast key")] = cppcodec::base64_rfc4648::encode(broadcastKey.ToByteVector().data(), FSecure::Crypto::SymmetricKey::Size);

		// Store them in provided file and return.
		std::ofstream{ keysFilePath, std::ios::out | std::ios::trunc } << keys.dump(4);									// dump(4) adds 4 space indentation to file.

		return std::make_tuple(false, singatures, broadcastKey);
	}

	/// Obtains Gateway's configuration.
	/// @param configurationFilePath path to the configuration file.
	/// @return std::tuple with read configuration.
	auto ReadGatewayConfigurationFile(std::filesystem::path const& configurationFilePath)
	{
		// Parse json configuration file.
		if (!std::filesystem::exists(configurationFilePath))
			throw std::invalid_argument{ OBF("Gateway configuration file not found.") };
		auto configuration = json::parse(std::ifstream{ configurationFilePath, std::ios::in });

		// Unfortunetly json::value throws for most value_types. This helper will return default in such case.
		auto jsonValueClosure = [&configuration](auto const& key, auto const& defaultValue)
		{
			return configuration.is_object() ? configuration.value(key, defaultValue) : defaultValue; };

		return std::make_tuple
		(
			jsonValueClosure(OBF("API Bridge IP"), OBF("127.0.0.1")),
			jsonValueClosure(OBF("API Bridge port"), std::uint16_t{ 2323 }),
			FSecure::C3::BuildId{ jsonValueClosure(OBF("BuildId"), FSecure::C3::BuildId::GenerateRandom().ToString()) },
			FSecure::C3::AgentId{ jsonValueClosure(OBF("AgentId"), FSecure::C3::AgentId::GenerateRandom().ToString()) },
			jsonValueClosure("Name", "")
		);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::shared_ptr<FSecure::C3::Relay> FSecure::C3::Utils::CreateGatewayFromConfigurationFiles(LoggerCallback callbackOnLog, InterfaceFactory& interfaceFactory, std::filesystem::path const& keysFileName, std::filesystem::path const& configurationFileName)
{
	// Identify and store the directory that the executable is running in (-not- the current directory) using WinAPI.
	wchar_t executableFilePath[MAX_PATH];
	if (!GetModuleFileNameW(nullptr, executableFilePath, MAX_PATH))
		throw std::runtime_error{ OBF("Could not find process image path.") };

	// Read both input files.
	callbackOnLog({ OBF("Reading input files..."), LogMessage::Severity::Information }, "");

	auto [apiBridgeIp, apiBrigdePort, buildId, agentId, name] = ReadGatewayConfigurationFile(std::filesystem::path{ executableFilePath }.remove_filename() / configurationFileName);
	auto [wereKeysReadOrGenerated, signatures, broadcastKey] = ReadFromFileOrGenerateGatewayKeys(std::filesystem::path{ executableFilePath }.remove_filename() / keysFileName);
	auto snapshotPath = std::filesystem::path{ executableFilePath }.remove_filename() / OBF("GatewaySnapshot.json");

	// Create and run the Gateway.
	if (!wereKeysReadOrGenerated)
		callbackOnLog({ OBF("Generated new keys/signatures and stored them on disk."), LogMessage::Severity::Information }, "");

	callbackOnLog({ OBF("Starting Gateway..."), LogMessage::Severity::Information }, "");
	return FSecure::C3::Core::GateRelay::CreateAndRun(callbackOnLog, interfaceFactory, apiBridgeIp, apiBrigdePort, signatures, broadcastKey, buildId, snapshotPath, agentId, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::shared_ptr<FSecure::C3::Relay> FSecure::C3::Utils::CreateNodeRelayFromImagePatch(LoggerCallback callbackOnLog, InterfaceFactory& interfaceFactory, ByteView buildId, ByteView gatewaySignature, ByteView broadcastKey, std::vector<ByteVector> const& gatewayInitialPackets)
{
	return Core::NodeRelay::CreateAndRun(callbackOnLog, interfaceFactory, gatewaySignature, broadcastKey, gatewayInitialPackets, buildId.Read<BuildId>());
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::string FSecure::C3::Utils::ConvertLogMessageToConsoleText(std::string_view relayName, LogMessage const& message, std::string_view sender)
{
	// Format message as: "[relay]|@> [InterfaceID] message", where @ is different for each severity.
	std::string retVal = sender.empty() ? (std::string(relayName) + '|') : ""s;

	switch (message.m_Severity)
	{
	case FSecure::C3::LogMessage::Severity::Error: retVal += OBF("Error> "); break;
	case FSecure::C3::LogMessage::Severity::Information: retVal += OBF("Info> "); break;
	case FSecure::C3::LogMessage::Severity::Warning: retVal += OBF("Warning> "); break;
	case FSecure::C3::LogMessage::Severity::DebugInformation: retVal += OBF("Debug> "); break;
	default: retVal += OBF("???> ");
	}

	retVal += !sender.empty() ? '[' + std::string(sender) + "] " : "";
	return retVal += message.m_Body;
}
