//
//
// Created by stephane bourque on 2022-10-26.
//

#include "Poco/AsyncChannel.h"
#include "Poco/ConsoleChannel.h"
#include "Poco/FileChannel.h"
#include "Poco/FormattingChannel.h"
#include "Poco/JSON/JSONException.h"
#include "Poco/Net/FTPSStreamFactory.h"
#include "Poco/Net/FTPStreamFactory.h"
#include "Poco/Net/HTTPSStreamFactory.h"
#include "Poco/Net/HTTPStreamFactory.h"
#include "Poco/Net/SSLManager.h"
#include "Poco/NullChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/SplitterChannel.h"

#include "framework/ALBserver.h"
#include "framework/AuthClient.h"
#include "framework/KafkaManager.h"
#include "framework/MicroService.h"
#include "framework/MicroServiceErrorHandler.h"
#include "framework/MicroServiceNames.h"
#include "framework/RESTAPI_ExtServer.h"
#include "framework/RESTAPI_GenericServerAccounting.h"
#include "framework/RESTAPI_IntServer.h"
#include "framework/UI_WebSocketClientServer.h"
#include "framework/WebSocketLogger.h"
#include "framework/utils.h"

#ifdef  USE_MEDUSA_CLIENT
#include <medusa/MedusaClient.h>
#endif

namespace OpenWifi {

	static std::string MakeServiceListString(const Types::MicroServiceMetaMap &Services) {
        std::string SvcList;
        for (const auto &Svc : Services) {
            if (SvcList.empty())
                SvcList = Svc.second.Type;
            else
                SvcList += ", " + Svc.second.Type;
        }
        return SvcList;
    }

	void MicroService::BusMessageReceived([[maybe_unused]] const std::string &Key,
										  const std::string &Payload) {
		std::lock_guard G(InfraMutex_);

		Poco::Logger &BusLogger = EventBusManager()->Logger();

		try {
			Poco::JSON::Parser P;
			auto Object = P.parse(Payload).extract<Poco::JSON::Object::Ptr>();

			if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) &&
				Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) {
				uint64_t ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID);
				auto Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString();
				if (ID != ID_) {
					if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN ||
						Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE ||
						Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
						if (Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) &&
							Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) &&
							Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) &&
							Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) &&
							Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) {
							auto PrivateEndPoint =
								Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString();
							if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
								Services_.erase(PrivateEndPoint);
								poco_information(
									BusLogger,
									fmt::format(
										"Service {} ID={} leaving system.",
										Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE)
											.toString(),
										ID));
							} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN ||
									   Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) {
								auto ServiceInfo = Types::MicroServiceMeta{
									.Id = ID,
									.Type = Poco::toLower(
										Object->get(KafkaTopics::ServiceEvents::Fields::TYPE)
											.toString()),
									.PrivateEndPoint =
										Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE)
											.toString(),
									.PublicEndPoint =
										Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC)
											.toString(),
									.AccessKey =
										Object->get(KafkaTopics::ServiceEvents::Fields::KEY)
											.toString(),
									.Version = Object->get(KafkaTopics::ServiceEvents::Fields::VRSN)
												   .toString(),
									.LastUpdate = Utils::Now()};

                                auto s1 = MakeServiceListString(Services_);
								auto PreviousSize = Services_.size();
								Services_[PrivateEndPoint] = ServiceInfo;
								auto CurrentSize = Services_.size();
								if(Event == KafkaTopics::ServiceEvents::EVENT_JOIN) {
									if(!s1.empty()) {
										poco_information(
											BusLogger,
											fmt::format(
												"Service {} ID={} is joining the system.",
												Object
													->get(
														KafkaTopics::ServiceEvents::Fields::PRIVATE)
													.toString(),
												ID));
									}
									std::string SvcList;
									for (const auto &Svc : Services_) {
										if (SvcList.empty())
											SvcList = Svc.second.Type;
										else
											SvcList += ", " + Svc.second.Type;
									}
									poco_information(
										BusLogger,
										fmt::format("Current list of microservices: {}", SvcList));
								} else if(CurrentSize!=PreviousSize) {
									poco_information(
										BusLogger,
										fmt::format(
											"Service {} ID={} is being added back in.",
											Object
												->get(KafkaTopics::ServiceEvents::Fields::PRIVATE)
												.toString(),
											ID));
								}
							}
						} else {
							poco_information(
								BusLogger,
								fmt::format("KAFKA-MSG: invalid event '{}', missing a field.",
											Event));
						}
					} else if (Event == KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) {
						if (Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) {
#ifndef TIP_SECURITY_SERVICE
							AuthClient()->RemovedCachedToken(
								Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString());
#endif
						} else {
							poco_information(
								BusLogger,
								fmt::format("KAFKA-MSG: invalid event '{}', missing token", Event));
						}
					} else {
						poco_information(BusLogger,
								   fmt::format("Unknown Event: {} Source: {}", Event, ID));
					}
				}
			} else {
				std::ostringstream os;
				Object->stringify(std::cout);
				poco_error(BusLogger, fmt::format("Bad bus message: {}", os.str()));
			}

			auto ServiceHint = Services_.begin();
			auto now = Utils::Now();
            auto si1 = Services_.size();
            auto ss1 = MakeServiceListString(Services_);
			while(ServiceHint!=Services_.end()) {
				if ((now - ServiceHint->second.LastUpdate) > 120) {
					poco_information(BusLogger, fmt::format("ZombieService: Removing service {}, ", ServiceHint->second.PublicEndPoint));
					ServiceHint = Services_.erase(ServiceHint);
				} else
					++ServiceHint;
			}
            if(Services_.size() != si1) {
                auto ss2 = MakeServiceListString(Services_);
                poco_information(BusLogger, fmt::format("Current list of microservices: {} -> {}", ss1, ss2));
            }

		} catch (const Poco::Exception &E) {
			BusLogger.log(E);
		}
	}

	Types::MicroServiceMetaVec MicroService::GetServices(const std::string &Type) {
		std::lock_guard G(InfraMutex_);

		auto T = Poco::toLower(Type);
		Types::MicroServiceMetaVec Res;
		for (const auto &[_, ServiceRec] : Services_) {
			if (ServiceRec.Type == T)
				Res.push_back(ServiceRec);
		}
		return Res;
	}

	Types::MicroServiceMetaVec MicroService::GetServices() {
		std::lock_guard G(InfraMutex_);

		Types::MicroServiceMetaVec Res;
		for (const auto &[_, ServiceRec] : Services_) {
			Res.push_back(ServiceRec);
		}
		return Res;

	}

	void MicroService::LoadConfigurationFile() {
        if(ConfigContent_.empty()) {
            std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR, ".");
            ConfigFileName_ =
                ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
            Poco::Path ConfigFile(ConfigFileName_);

            if (!ConfigFile.isFile()) {
                std::cerr << DAEMON_APP_NAME << ": Configuration " << ConfigFile.toString()
                          << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR +
                                 " env variable the path of the " + DAEMON_PROPERTIES_FILENAME +
                                 " file."
                          << std::endl;
                std::exit(Poco::Util::Application::EXIT_CONFIG);
            }
            PropConfigurationFile_ = new Poco::Util::PropertyFileConfiguration(ConfigFile.toString());
        } else {
            std::istringstream is(ConfigContent_);
            PropConfigurationFile_ = new Poco::Util::PropertyFileConfiguration(is);
        }
		configPtr()->addWriteable(PropConfigurationFile_, PRIO_DEFAULT);
	}

	void MicroService::Reload() {
		LoadConfigurationFile();
		LoadMyConfig();
	}

	void MicroService::LoadMyConfig() {
		NoAPISecurity_ = ConfigGetBool("openwifi.security.restapi.disable", false);
		std::string KeyFile = ConfigPath("openwifi.service.key", "");
		if (!KeyFile.empty()) {
			std::string KeyFilePassword = ConfigPath("openwifi.service.key.password", "");
			AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(
				new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword));
			Cipher_ = CipherFactory_.createCipher(*AppKey_);
			Signer_.setRSAKey(AppKey_);
			Signer_.addAllAlgorithms();
			NoBuiltInCrypto_ = false;
		} else {
			NoBuiltInCrypto_ = true;
		}

		ID_ = Utils::GetSystemId();
		if (!DebugMode_)
			DebugMode_ = ConfigGetBool("openwifi.system.debug", false);
		MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private");
		MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public");
		UIURI_ = ConfigGetString("openwifi.system.uri.ui");
		MyHash_ = Utils::ComputeHash(MyPublicEndPoint_);
	}

	void MicroService::InitializeLoggingSystem() {
		static auto initialized = false;

		if (!initialized) {
			initialized = true;
			LoadConfigurationFile();

			auto LoggingDestination =
				MicroService::instance().ConfigGetString("logging.type", "file");
			auto LoggingFormat = MicroService::instance().ConfigGetString(
				"logging.format", "%Y-%m-%d %H:%M:%S.%i %s: [%p][thr:%I] %t");
			auto UseAsyncLogs_ = MicroService::instance().ConfigGetBool("logging.asynch", true);
			auto DisableWebSocketLogging =
				MicroService::instance().ConfigGetBool("logging.websocket", false);

			if (LoggingDestination == "null") {
				Poco::AutoPtr<Poco::NullChannel> DevNull(new Poco::NullChannel);
				Poco::Logger::root().setChannel(DevNull);
			} else if (LoggingDestination == "console") {
				SetConsoleLogs(UseAsyncLogs_, DisableWebSocketLogging, LoggingFormat);
			} else if (LoggingDestination == "colorconsole") {
				SetColorConsoleLogs(UseAsyncLogs_, DisableWebSocketLogging, LoggingFormat);
			} else if (LoggingDestination == "sql") {
				SetSQLLogs(UseAsyncLogs_, DisableWebSocketLogging, LoggingFormat);
			} else if (LoggingDestination == "syslog") {
				SetSyslogLogs(UseAsyncLogs_, DisableWebSocketLogging, LoggingFormat);
			} else {
				SetFileLogs(UseAsyncLogs_, DisableWebSocketLogging, LoggingFormat,
							DAEMON_ROOT_ENV_VAR);
			}

			auto Level = Poco::Logger::parseLevel(
				MicroService::instance().ConfigGetString("logging.level", "debug"));
			Poco::Logger::root().setLevel(Level);
			if (!DisableWebSocketLogging) {
				static const UI_WebSocketClientServer::NotificationTypeIdVec Notifications = {
					{1, "log"}};
				UI_WebSocketClientServer()->RegisterNotifications(Notifications);
			}
		}
	}

	void MicroService::SetConsoleLogs(bool UseAsync, bool DisableWebSocketLogging,
									  const std::string &FormatterPattern) {

		Poco::AutoPtr<Poco::ConsoleChannel> Console(new Poco::ConsoleChannel);
		Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
		Formatter->setProperty("pattern", FormatterPattern);
		Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
			new Poco::FormattingChannel(Formatter, Console));

		if (DisableWebSocketLogging) {
			if (UseAsync) {
				Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(FormattingChannel));
				Poco::Logger::root().setChannel(Async);
			} else {
				Poco::Logger::root().setChannel(FormattingChannel);
			}
		} else {
			Poco::AutoPtr<WebSocketLogger> WSLogger(new WebSocketLogger);
			Poco::AutoPtr<Poco::SplitterChannel> Splitter(new Poco::SplitterChannel);
			Splitter->addChannel(WSLogger);
			Splitter->addChannel(FormattingChannel);
			if (UseAsync) {
				Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(Splitter));
				Poco::Logger::root().setChannel(Async);
			} else {
				Poco::Logger::root().setChannel(Splitter);
			}
		}
		Poco::Logger::root().information(fmt::format("Enabled console logs: asynch={} websocket={}",
													 UseAsync, DisableWebSocketLogging));
	}

	void MicroService::SetColorConsoleLogs(bool UseAsync, bool DisableWebSocketLogging,
										   const std::string &FormatterPattern) {

		Poco::AutoPtr<Poco::ColorConsoleChannel> Console(new Poco::ColorConsoleChannel);
		Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
		Formatter->setProperty("pattern", FormatterPattern);
		Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
			new Poco::FormattingChannel(Formatter, Console));

		if (DisableWebSocketLogging) {
			if (UseAsync) {
				Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(FormattingChannel));
				Poco::Logger::root().setChannel(Async);
			} else {
				Poco::Logger::root().setChannel(FormattingChannel);
			}
		} else {
			Poco::AutoPtr<WebSocketLogger> WSLogger(new WebSocketLogger);
			Poco::AutoPtr<Poco::SplitterChannel> Splitter(new Poco::SplitterChannel);
			Splitter->addChannel(WSLogger);
			Splitter->addChannel(FormattingChannel);
			if (UseAsync) {
				Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(Splitter));
				Poco::Logger::root().setChannel(Async);
			} else {
				Poco::Logger::root().setChannel(Splitter);
			}
		}
		Poco::Logger::root().information(
			fmt::format("Enabled color console logs: asynch={} websocket={}", UseAsync,
						DisableWebSocketLogging));
	}

	void MicroService::SetSQLLogs([[maybe_unused]] bool UseAsync,
								  [[maybe_unused]] bool DisableWebSocketLogging,
								  [[maybe_unused]] const std::string &FormatterPattern) {
		//"CREATE TABLE T_POCO_LOG (Source VARCHAR, Name VARCHAR, ProcessId INTEGER, Thread VARCHAR,
		//ThreadId INTEGER, Priority INTEGER, Text VARCHAR, DateTime DATE)"
	}

	void MicroService::SetSyslogLogs([[maybe_unused]] bool UseAsync,
									 [[maybe_unused]] bool DisableWebSocketLogging,
									 [[maybe_unused]] const std::string &FormatterPattern) {}

	void MicroService::SetFileLogs(bool UseAsync, bool DisableWebSocketLogging,
								   const std::string &FormatterPattern,
								   const std::string &root_env_var) {
		std::string DefaultLogPath = fmt::format("${}/logs", root_env_var);
		auto LoggingLocationDir =
			MicroService::instance().ConfigPath("logging.path", DefaultLogPath);
		Poco::File LD(LoggingLocationDir);
		try {
			if (!LD.exists()) {
				LD.createDirectory();
			}
		} catch (const Poco::Exception &E) {
			std::cout << "Cannot create " << LD.path() << "  Error: " << E.message() << std::endl;
		}
		auto LoggingLocationDirFilePattern = LoggingLocationDir + "/log";

		Poco::AutoPtr<Poco::FileChannel> FileChannel(new Poco::FileChannel);
		FileChannel->setProperty("rotation", "10 M");
		FileChannel->setProperty("archive", "timestamp");
		FileChannel->setProperty("purgeCount", "10");
		FileChannel->setProperty("path", LoggingLocationDirFilePattern);

		Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
		Formatter->setProperty("pattern", FormatterPattern);
		Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
			new Poco::FormattingChannel(Formatter, FileChannel));

		if (DisableWebSocketLogging) {
			if (UseAsync) {
				Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(FormattingChannel));
				Poco::Logger::root().setChannel(Async);
			} else {
				Poco::Logger::root().setChannel(FormattingChannel);
			}
		} else {
			Poco::AutoPtr<WebSocketLogger> WSLogger(new WebSocketLogger);
			Poco::AutoPtr<Poco::SplitterChannel> Splitter(new Poco::SplitterChannel);
			Splitter->addChannel(WSLogger);
			Splitter->addChannel(FormattingChannel);
			if (UseAsync) {
				Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(Splitter));
				Poco::Logger::root().setChannel(Async);
			} else {
				Poco::Logger::root().setChannel(Splitter);
			}
		}
		Poco::Logger::root().information(fmt::format("Enabled file logs: asynch={} websocket={}",
													 UseAsync, DisableWebSocketLogging));
	}

	void DaemonPostInitialization(Poco::Util::Application &self);

    void MicroService::StartEverything(Poco::Util::Application &self) {
        LoadConfigurationFile();
        InitializeLoggingSystem();

        static bool InitializedBaseService=false;
        if(!InitializedBaseService) {
            InitializedBaseService = true;
            SubSystems_.push_back(KafkaManager());
            SubSystems_.push_back(ALBHealthCheckServer());
            SubSystems_.push_back(RESTAPI_ExtServer());
            SubSystems_.push_back(RESTAPI_IntServer());
#ifndef TIP_SECURITY_SERVICE
            SubSystems_.push_back(AuthClient());
#endif

            Poco::Net::initializeSSL();
            Poco::Net::HTTPStreamFactory::registerFactory();
            Poco::Net::HTTPSStreamFactory::registerFactory();
            Poco::Net::FTPStreamFactory::registerFactory();
            Poco::Net::FTPSStreamFactory::registerFactory();
        }

        Poco::File DataDir(ConfigPath("openwifi.system.data"));
        DataDir_ = DataDir.path();
        if (!DataDir.exists()) {
            try {
                DataDir.createDirectory();
            } catch (const Poco::Exception &E) {
                Logger_.log(E);
            }
        }
        WWWAssetsDir_ = ConfigPath("openwifi.restapi.wwwassets", "");
        if (WWWAssetsDir_.empty())
            WWWAssetsDir_ = DataDir_;

        LoadMyConfig();

        AllowExternalMicroServices_ = ConfigGetBool("allowexternalmicroservices", true);

        InitializeSubSystemServers();
        ServerApplication::initialize(self);
        DaemonPostInitialization(self);

        Types::TopicNotifyFunction F = [this](const std::string &Key, const std::string &Payload) {
            this->BusMessageReceived(Key, Payload);
        };
        KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F);
    }

    void MicroService::StopEverything([[maybe_unused]] Poco::Util::Application &self) {
        LoadConfigurationFile();
        InitializeLoggingSystem();

        Types::TopicNotifyFunction F = [this](const std::string &Key, const std::string &Payload) {
            this->BusMessageReceived(Key, Payload);
        };
        KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F);
    }

    void MicroService::initialize([[maybe_unused]] Poco::Util::Application &self) {
#ifndef USE_MEDUSA_CLIENT
        StartEverything(self);
#endif
	}

	void MicroService::uninitialize() {
		// add your own uninitialization code here
		ServerApplication::uninitialize();
	}

	void MicroService::reinitialize(Poco::Util::Application &self) {
		ServerApplication::reinitialize(self);
		// add your own reinitialization code here
	}

	void MicroService::defineOptions(Poco::Util::OptionSet &options) {
		ServerApplication::defineOptions(options);

		options.addOption(
			Poco::Util::Option("help", "", "display help information on command line arguments")
				.required(false)
				.repeatable(false)
				.callback(
					Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleHelp)));

		options.addOption(Poco::Util::Option("file", "", "specify the configuration file")
							  .required(false)
							  .repeatable(false)
							  .argument("file")
							  .callback(Poco::Util::OptionCallback<MicroService>(
								  this, &MicroService::handleConfig)));

		options.addOption(Poco::Util::Option("debug", "", "to run in debug, set to true")
							  .required(false)
							  .repeatable(false)
							  .callback(Poco::Util::OptionCallback<MicroService>(
								  this, &MicroService::handleDebug)));

		options.addOption(
			Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
				.required(false)
				.repeatable(false)
				.argument("dir")
				.callback(
					Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleLogs)));

		options.addOption(Poco::Util::Option("version", "", "get the version and quit.")
							  .required(false)
							  .repeatable(false)
							  .callback(Poco::Util::OptionCallback<MicroService>(
								  this, &MicroService::handleVersion)));
	}

	void MicroService::handleHelp([[maybe_unused]] const std::string &name,
								  [[maybe_unused]] const std::string &value) {
		HelpRequested_ = true;
		displayHelp();
		stopOptionsProcessing();
	}

	void MicroService::handleVersion([[maybe_unused]] const std::string &name,
									 [[maybe_unused]] const std::string &value) {
		HelpRequested_ = true;
		std::cout << Version() << std::endl;
		stopOptionsProcessing();
	}

	void MicroService::handleDebug([[maybe_unused]] const std::string &name,
								   const std::string &value) {
		if (value == "true")
			DebugMode_ = true;
	}

	void MicroService::handleLogs([[maybe_unused]] const std::string &name,
								  const std::string &value) {
		LogDir_ = value;
	}

	void MicroService::handleConfig([[maybe_unused]] const std::string &name,
									const std::string &value) {
		ConfigFileName_ = value;
	}

	void MicroService::displayHelp() {
		Poco::Util::HelpFormatter helpFormatter(options());
		helpFormatter.setCommand(commandName());
		helpFormatter.setUsage("OPTIONS");
		helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP.");
		helpFormatter.format(std::cout);
	}

	void MicroService::InitializeSubSystemServers() {
		for (auto i : SubSystems_) {
			addSubsystem(i);
		}
	}

	void MicroService::StartSubSystemServers() {
		AddActivity("Starting");
		for (auto i : SubSystems_) {
			i->Start();
		}
		EventBusManager()->Start();
	}

	void MicroService::StopSubSystemServers() {
		AddActivity("Stopping");
		EventBusManager()->Stop();
		for (auto i = SubSystems_.rbegin(); i != SubSystems_.rend(); ++i) {
			(*i)->Stop();
		}
	}

	[[nodiscard]] std::string MicroService::CreateUUID() {
		static std::random_device rd;
		static std::mt19937_64 gen(rd());
		static std::uniform_int_distribution<> dis(0, 15);
		static std::uniform_int_distribution<> dis2(8, 11);

		std::stringstream ss;
		int i;
		ss << std::hex;
		for (i = 0; i < 8; i++) {
			ss << dis(gen);
		}
		ss << "-";
		for (i = 0; i < 4; i++) {
			ss << dis(gen);
		}
		ss << "-4";
		for (i = 0; i < 3; i++) {
			ss << dis(gen);
		}
		ss << "-";
		ss << dis2(gen);
		for (i = 0; i < 3; i++) {
			ss << dis(gen);
		}
		ss << "-";
		for (i = 0; i < 12; i++) {
			ss << dis(gen);
		};
		return ss.str();
	}

	bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem,
											const std::string &Level) {
		try {
			auto P = Poco::Logger::parseLevel(Level);
			auto Sub = Poco::toLower(SubSystem);

			if (Sub == "all") {
				for (auto i : SubSystems_) {
					i->Logger().setLevel(P);
				}
				return true;
			} else {
				for (auto i : SubSystems_) {
					if (Sub == Poco::toLower(i->Name())) {
						i->Logger().setLevel(P);
						return true;
					}
				}
			}
		} catch (const Poco::Exception &E) {
			std::cerr << "Exception" << std::endl;
		}
		return false;
	}

	void MicroService::Reload(const std::string &Sub) {
		for (auto i : SubSystems_) {
			if (Poco::toLower(Sub) == Poco::toLower(i->Name())) {
				i->reinitialize(Poco::Util::Application::instance());
				return;
			}
		}
	}

	Types::StringVec MicroService::GetSubSystems() const {
		Types::StringVec Result;
		for (auto i : SubSystems_)
			Result.push_back(Poco::toLower(i->Name()));
		return Result;
	}

	Types::StringPairVec MicroService::GetLogLevels() {
		Types::StringPairVec Result;

		for (auto &i : SubSystems_) {
			auto P = std::make_pair(i->Name(), Utils::LogLevelToString(i->GetLoggingLevel()));
			Result.push_back(P);
		}
		return Result;
	}

	const Types::StringVec &MicroService::GetLogLevelNames() {
		static Types::StringVec LevelNames{"none",	 "fatal",		"critical", "error", "warning",
										   "notice", "information", "debug",	"trace"};
		return LevelNames;
	}

	uint64_t MicroService::ConfigGetInt(const std::string &Key, uint64_t Default) {
		return (uint64_t)config().getInt64(Key, Default);
	}

	uint64_t MicroService::ConfigGetInt(const std::string &Key) { return config().getInt(Key); }

	uint64_t MicroService::ConfigGetBool(const std::string &Key, bool Default) {
		return config().getBool(Key, Default);
	}

	uint64_t MicroService::ConfigGetBool(const std::string &Key) { return config().getBool(Key); }

	std::string MicroService::ConfigGetString(const std::string &Key, const std::string &Default) {
		return config().getString(Key, Default);
	}

	std::string MicroService::ConfigGetString(const std::string &Key) {
		return config().getString(Key);
	}

	std::string MicroService::ConfigPath(const std::string &Key, const std::string &Default) {
		std::string R = config().getString(Key, Default);
		return Poco::Path::expand(R);
	}

	std::string MicroService::ConfigPath(const std::string &Key) {
		std::string R = config().getString(Key);
		return Poco::Path::expand(R);
	}

	std::string MicroService::Encrypt(const std::string &S) {
		if (NoBuiltInCrypto_) {
			return S;
		}
		return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);
		;
	}

	std::string MicroService::Decrypt(const std::string &S) {
		if (NoBuiltInCrypto_) {
			return S;
		}
		return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);
		;
	}

	std::string MicroService::MakeSystemEventMessage(const std::string &Type) const {
		Poco::JSON::Object Obj;
		Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT, Type);
		Obj.set(KafkaTopics::ServiceEvents::Fields::ID, ID_);
		Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE, Poco::toLower(DAEMON_APP_NAME));
		Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC, MyPublicEndPoint_);
		Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE, MyPrivateEndPoint_);
		Obj.set(KafkaTopics::ServiceEvents::Fields::KEY, MyHash_);
		Obj.set(KafkaTopics::ServiceEvents::Fields::VRSN, Version_);
		std::stringstream ResultText;
		Poco::JSON::Stringifier::stringify(Obj, ResultText);
		return ResultText.str();
	}

	[[nodiscard]] bool MicroService::IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) {
		try {
			auto APIKEY = Request.get("X-API-KEY");
			return APIKEY == MyHash_;
		} catch (const Poco::Exception &E) {
			Logger_.log(E);
		}
		return false;
	}

	void MicroService::SavePID() {
		try {
			std::ofstream O;
			O.open(MicroService::instance().DataDir() + "/pidfile",
				   std::ios::binary | std::ios::trunc);
			O << Poco::Process::id();
			O.close();
		} catch (...) {
			std::cout << "Could not save system ID" << std::endl;
		}
	}

	int MicroService::main([[maybe_unused]] const ArgVec &args) {
		MicroServiceErrorHandler ErrorHandler(*this);
		Poco::ErrorHandler::set(&ErrorHandler);

        Args_ = args;

		if (!HelpRequested_) {
			SavePID();

			Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME);
			logger.notice(fmt::format("Starting {} version {}.", DAEMON_APP_NAME, Version()));

			if (Poco::Net::Socket::supportsIPv6())
				poco_information(logger, "System supports IPv6.");
			else
				poco_information(logger, "System does NOT support IPv6.");

			if (config().getBool("application.runAsDaemon", false)) {
				poco_information(logger, "Starting as a daemon.");
			}

#ifdef USE_MEDUSA_CLIENT
            MedusaClient::instance()->SetSubSystems(SubSystems_);
            MedusaClient::instance()->Start();
			waitForTerminationRequest();
            MedusaClient::instance()->Stop();
#else
			poco_information(logger, fmt::format("System ID set to {}", ID_));
			StartSubSystemServers();
			waitForTerminationRequest();
			StopSubSystemServers();
			logger.notice(fmt::format("Stopped {}...", DAEMON_APP_NAME));
#endif
		}

		return Application::EXIT_OK;
	}

	void MicroService::AddActivity(const std::string &Activity) {
		if (!DataDir_.empty()) {
			std::string ActivityFile{DataDir_ + "/activity.log"};
			try {
				std::ofstream of(ActivityFile, std::ios_base::app | std::ios_base::out);
				auto t = std::chrono::system_clock::now();
				std::time_t now = std::chrono::system_clock::to_time_t(t);
				of << Activity << " at " << std::ctime(&now);
			} catch (...) {
			}
		}
	}

	[[nodiscard]] std::string MicroService::Sign(Poco::JWT::Token &T, const std::string &Algo) {
		if (NoBuiltInCrypto_) {
			return T.toString();
		} else {
			return Signer_.sign(T, Algo);
		}
	}

	void MicroService::DeleteOverrideConfiguration() {
		Poco::File F(DataDir_ + ExtraConfigurationFilename);

		try {
			if (F.exists())
				F.remove();
		} catch (...) {
		}
	}

} // namespace OpenWifi