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

#include "Poco/Path.h"
#include "Poco/TemporaryFile.h"
#include "Poco/Crypto/ECKey.h"
#include "framework/AppServiceRegistry.h"
#include "framework/utils.h"

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <string>
#include <algorithm>

#include <resolv.h>

namespace OpenWifi::Utils {

	bool NormalizeMac(std::string &Mac) {
		Poco::replaceInPlace(Mac, ":", "");
		Poco::replaceInPlace(Mac, "-", "");
		if (Mac.size() != 12)
			return false;
		for (const auto &i : Mac) {
			if (!std::isxdigit(i))
				return false;
		}
		Poco::toLowerInPlace(Mac);
		return true;
	}

	[[nodiscard]] bool ValidSerialNumber(const std::string &Serial) {
		return ((Serial.size() < uCentralProtocol::SERIAL_NUMBER_LENGTH) &&
				std::all_of(Serial.begin(), Serial.end(), [](auto i) { return std::isxdigit(i); }));
	}

	[[nodiscard]] bool ValidSerialNumbers(const std::vector<std::string> &numbers) {
		return std::all_of(numbers.begin(),numbers.end(),[](auto &number) {return ValidSerialNumber(number);});
	}

	[[nodiscard]] bool ValidUUID(const std::string &UUID) {
		if (UUID.size() > 36)
			return false;
		uint dashes = 0;
		return (std::all_of(UUID.begin(), UUID.end(),
							[&](auto i) {
								if (i == '-')
									dashes++;
								return i == '-' || std::isxdigit(i);
							})) &&
			   (dashes > 0);
	}

	[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter) {
		std::vector<std::string> ReturnList;

		unsigned long P = 0;

		while (P < List.size()) {
			unsigned long P2 = List.find_first_of(Delimiter, P);
			if (P2 == std::string::npos) {
				ReturnList.push_back(List.substr(P));
				break;
			} else
				ReturnList.push_back(List.substr(P, P2 - P));
			P = P2 + 1;
		}
		return ReturnList;
	}

	[[nodiscard]] std::string FormatIPv6(const std::string &I) {
		if (I.substr(0, 8) == "[::ffff:") {
			unsigned long PClosingBracket = I.find_first_of(']');

			std::string ip = I.substr(8, PClosingBracket - 8);
			std::string port = I.substr(PClosingBracket + 1);
			return ip + port;
		}

		return I;
	}

	void padTo(std::string &str, size_t num, char paddingChar) {
		str.append(num - str.length() % num, paddingChar);
	}

	[[nodiscard]] std::string SerialToMAC(const std::string &Serial) {
		std::string R = Serial;

		if (R.size() < 12)
			padTo(R, 12, '0');
		else if (R.size() > 12)
			R = R.substr(0, 12);

		char buf[18];

		buf[0] = R[0];
		buf[1] = R[1];
		buf[2] = ':';
		buf[3] = R[2];
		buf[4] = R[3];
		buf[5] = ':';
		buf[6] = R[4];
		buf[7] = R[5];
		buf[8] = ':';
		buf[9] = R[6];
		buf[10] = R[7];
		buf[11] = ':';
		buf[12] = R[8];
		buf[13] = R[9];
		buf[14] = ':';
		buf[15] = R[10];
		buf[16] = R[11];
		buf[17] = 0;

		return buf;
	}

	uint64_t MACToInt(const std::string &MAC) {
		uint64_t Result = 0;
		for (const auto &c : MAC) {
			if (c == ':')
				continue;
			Result <<= 4;
			if (c >= '0' && c <= '9') {
				Result += (c - '0');
			} else if (c >= 'a' && c <= 'f') {
				Result += (c - 'a' + 10);
			} else if (c >= 'A' && c <= 'F') {
				Result += (c - 'A' + 10);
			}
		}
		return Result;
	}

	[[nodiscard]] bool ValidHostname(const std::string &Hostname) {
		static std::regex HostNameRegex(
			"^(?!-)[A-Za-z0-9-]+([\\-\\.]{1}[a-z0-9]+)*\\.[A-Za-z]{2,6}$");
		return std::regex_match(Hostname, HostNameRegex);
	}

	[[nodiscard]] bool ValidNumber(const std::string &number, bool isSigned)
	{
		static std::regex IntRegex("^-?[0-9]\\d*(\\.\\d+)?$");
		if(!isSigned) {
			IntRegex = "^[0-9]\\d*(\\.\\d+)?$";
		}
		return std::regex_match(number, IntRegex);
	}

	[[nodiscard]] std::string ToHex(const std::vector<unsigned char> &B) {
		std::string R;
		R.reserve(B.size() * 2);

		static const char hex[] = "0123456789abcdef";

		for (const auto &i : B) {
			R += (hex[(i & 0xf0) >> 4]);
			R += (hex[(i & 0x0f)]);
		}

		return R;
	}

	inline static const char kEncodeLookup[] =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	inline static const char kPadCharacter = '=';

	using byte = std::uint8_t;

	[[nodiscard]] std::string base64encode(const byte *input, uint32_t size) {
		std::string encoded;
		encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);

		std::uint32_t temp, i, ee;
		ee = (size / 3);

		for (i = 0; i < 3 * ee; ++i) {
			temp = input[i++] << 16;
			temp += input[i++] << 8;
			temp += input[i];
			encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
			encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
			encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
			encoded.append(1, kEncodeLookup[(temp & 0x0000003F)]);
		}

		switch (size % 3) {
		case 1:
			temp = input[i] << 16;
			encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
			encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
			encoded.append(2, kPadCharacter);
			break;
		case 2:
			temp = input[i++] << 16;
			temp += input[i] << 8;
			encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
			encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
			encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
			encoded.append(1, kPadCharacter);
			break;
		}

		return encoded;
	}

	[[nodiscard]] std::vector<byte> base64decode(const std::string &input) {
		if (input.length() % 4)
			throw std::runtime_error("Invalid base64 length!");

		std::size_t padding = 0;

		if (input.length()) {
			if (input[input.length() - 1] == kPadCharacter)
				padding++;
			if (input[input.length() - 2] == kPadCharacter)
				padding++;
		}

		std::vector<byte> decoded;
		decoded.reserve(((input.length() / 4) * 3) - padding);

		std::uint32_t temp = 0;
		auto it = input.begin();

		while (it < input.end()) {
			for (std::size_t i = 0; i < 4; ++i) {
				temp <<= 6;
				if (*it >= 0x41 && *it <= 0x5A)
					temp |= *it - 0x41;
				else if (*it >= 0x61 && *it <= 0x7A)
					temp |= *it - 0x47;
				else if (*it >= 0x30 && *it <= 0x39)
					temp |= *it + 0x04;
				else if (*it == 0x2B)
					temp |= 0x3E;
				else if (*it == 0x2F)
					temp |= 0x3F;
				else if (*it == kPadCharacter) {
					switch (input.end() - it) {
					case 1:
						decoded.push_back((temp >> 16) & 0x000000FF);
						decoded.push_back((temp >> 8) & 0x000000FF);
						return decoded;
					case 2:
						decoded.push_back((temp >> 10) & 0x000000FF);
						return decoded;
					default:
						throw std::runtime_error("Invalid padding in base64!");
					}
				} else
					throw std::runtime_error("Invalid character in base64!");

				++it;
			}

			decoded.push_back((temp >> 16) & 0x000000FF);
			decoded.push_back((temp >> 8) & 0x000000FF);
			decoded.push_back((temp)&0x000000FF);
		}

		return decoded;
	}

	bool ParseTime(const std::string &Time, int &Hours, int &Minutes, int &Seconds) {
		Poco::StringTokenizer TimeTokens(Time, ":", Poco::StringTokenizer::TOK_TRIM);

		Hours = Minutes = Seconds = 0;
		if (TimeTokens.count() == 1) {
			Hours = std::atoi(TimeTokens[0].c_str());
		} else if (TimeTokens.count() == 2) {
			Hours = std::atoi(TimeTokens[0].c_str());
			Minutes = std::atoi(TimeTokens[1].c_str());
		} else if (TimeTokens.count() == 3) {
			Hours = std::atoi(TimeTokens[0].c_str());
			Minutes = std::atoi(TimeTokens[1].c_str());
			Seconds = std::atoi(TimeTokens[2].c_str());
		} else
			return false;
		return true;
	}

	bool ParseDate(const std::string &Time, int &Year, int &Month, int &Day) {
		Poco::StringTokenizer DateTokens(Time, "-", Poco::StringTokenizer::TOK_TRIM);

		Year = Month = Day = 0;
		if (DateTokens.count() == 3) {
			Year = std::atoi(DateTokens[0].c_str());
			Month = std::atoi(DateTokens[1].c_str());
			Day = std::atoi(DateTokens[2].c_str());
		} else
			return false;
		return true;
	}

	bool CompareTime(int H1, int H2, int M1, int M2, int S1, int S2) {
		if (H1 < H2)
			return true;
		if (H1 > H2)
			return false;
		if (M1 < M2)
			return true;
		if (M2 > M1)
			return false;
		if (S1 <= S2)
			return true;
		return false;
	}

	[[nodiscard]] std::string LogLevelToString(int Level) {
		switch (Level) {
		case Poco::Message::PRIO_DEBUG:
			return "debug";
		case Poco::Message::PRIO_INFORMATION:
			return "information";
		case Poco::Message::PRIO_FATAL:
			return "fatal";
		case Poco::Message::PRIO_WARNING:
			return "warning";
		case Poco::Message::PRIO_NOTICE:
			return "notice";
		case Poco::Message::PRIO_CRITICAL:
			return "critical";
		case Poco::Message::PRIO_ERROR:
			return "error";
		case Poco::Message::PRIO_TRACE:
			return "trace";
		default:
			return "none";
		}
	}

	[[nodiscard]] uint64_t SerialNumberToInt(const std::string &S) {
		return std::stoull(S, nullptr, 16);
	}

	[[nodiscard]] std::string IntToSerialNumber(uint64_t S) {
		char b[16];
		for (int i = 0; i < 12; ++i) {
			int B = (S & 0x0f);
			if (B < 10)
				b[11 - i] = B + '0';
			else
				b[11 - i] = B - 10 + 'a';
			S >>= 4;
		}
		b[12] = 0;
		return b;
	}

	[[nodiscard]] bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits) {
		auto S1_i = SerialNumberToInt(S1);
		auto S2_i = SerialNumberToInt(S2);
		return ((S1_i >> Bits) == (S2_i >> Bits));
	}

	[[nodiscard]] uint64_t SerialNumberToOUI(const std::string &S) {
		uint64_t Result = 0;
		int Digits = 0;

		for (const auto &i : S) {
			if (std::isxdigit(i)) {
				if (i >= '0' && i <= '9') {
					Result <<= 4;
					Result += i - '0';
				} else if (i >= 'A' && i <= 'F') {
					Result <<= 4;
					Result += i - 'A' + 10;
				} else if (i >= 'a' && i <= 'f') {
					Result <<= 4;
					Result += i - 'a' + 10;
				}
				Digits++;
				if (Digits == 6)
					break;
			}
		}
		return Result;
	}

	[[nodiscard]] uint64_t GetDefaultMacAsInt64() {
		uint64_t Result = 0;
		auto IFaceList = Poco::Net::NetworkInterface::list();

		for (const auto &iface : IFaceList) {
			if (iface.isRunning() && !iface.isLoopback()) {
				auto MAC = iface.macAddress();
				for (auto const &i : MAC) {
					Result <<= 8;
					Result += (uint8_t)i;
				}
				if (Result != 0)
					break;
			}
		}
		return Result;
	}

	[[nodiscard]] uint64_t InitializeSystemId() {
		std::random_device RDev;
		std::srand(RDev());
		std::chrono::high_resolution_clock Clock;
		auto Now = Clock.now().time_since_epoch().count();
		auto S = (GetDefaultMacAsInt64() + std::rand() + Now);
		OpenWifi::AppServiceRegistry().Set("systemid", S);
		return S;
	}

	[[nodiscard]] uint64_t GetSystemId() {
		uint64_t ID = 0;
		if (!AppServiceRegistry().Get("systemid", ID)) {
			return InitializeSystemId();
		}
		return ID;
	}

	[[nodiscard]] bool ValidEMailAddress(const std::string &email) {
		// define a regular expression
		static const std::regex pattern(
			"[_a-z0-9-]+(\\.[_a-z0-9-]+)*(\\+[a-z0-9-]+)?@[a-z0-9-]+(\\.[a-z0-9-]+)*");
		// try to match the string with the regular expression
		return std::regex_match(email, pattern);
	}

	[[nodiscard]] std::string LoadFile(const Poco::File &F) {
		std::string Result;
		try {
			std::ostringstream OS;
			std::ifstream IF(F.path());
			Poco::StreamCopier::copyStream(IF, OS);
			Result = OS.str();
		} catch (...) {
		}
		return Result;
	}

	void ReplaceVariables(std::string &Content, const Types::StringPairVec &P) {
		for (const auto &[Variable, Value] : P) {
			Poco::replaceInPlace(Content, "${" + Variable + "}", Value);
		}
	}

	[[nodiscard]] MediaTypeEncoding FindMediaType(const Poco::File &F) {
		const auto E = Poco::Path(F.path()).getExtension();
		if (E == "png")
			return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "image/png"};
		if (E == "gif")
			return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "image/gif"};
		if (E == "jpeg" || E == "jpg")
			return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "image/jpeg"};
		if (E == "svg" || E == "svgz")
			return MediaTypeEncoding{.Encoding = PLAIN, .ContentType = "image/svg+xml"};
		if (E == "html")
			return MediaTypeEncoding{.Encoding = PLAIN, .ContentType = "text/html"};
		if (E == "css")
			return MediaTypeEncoding{.Encoding = PLAIN, .ContentType = "text/css"};
		if (E == "js")
			return MediaTypeEncoding{.Encoding = PLAIN, .ContentType = "application/javascript"};
		if (E == "pcap")
			return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "application/vnd.tcpdump.pcap"};
		if (E == "txt")
			return MediaTypeEncoding{.Encoding = PLAIN, .ContentType = "text/plain"};
		if (E == "tgz")
			return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "application/tar+gzip"};
		if (E == "gz" || E=="gzip")
			return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "application/gzip"};

		return MediaTypeEncoding{.Encoding = BINARY, .ContentType = "application/octet-stream"};
	}

	[[nodiscard]] std::string BinaryFileToHexString(const Poco::File &F) {
		static const char hex[] = "0123456789abcdef";
		std::string Result;
		try {
			std::ifstream IF(F.path());

			int Count = 0;
			while (IF.good()) {
				if (Count)
					Result += ", ";
				if ((Count % 32) == 0)
					Result += "\r\n";
				Count++;
				unsigned char C = IF.get();
				Result += "0x";
				Result += (char)(hex[(C & 0xf0) >> 4]);
				Result += (char)(hex[(C & 0x0f)]);
			}
		} catch (...) {
		}
		return Result;
	}

	[[nodiscard]] std::string SecondsToNiceText(uint64_t Seconds) {
		std::string Result;
		int Days = Seconds / (24 * 60 * 60);
		Seconds -= Days * (24 * 60 * 60);
		int Hours = Seconds / (60 * 60);
		Seconds -= Hours * (60 * 60);
		int Minutes = Seconds / 60;
		Seconds -= Minutes * 60;
		Result = std::to_string(Days) + " days, " + std::to_string(Hours) + ":" +
				 std::to_string(Minutes) + ":" + std::to_string(Seconds);
		return Result;
	}

	[[nodiscard]] bool wgets(const std::string &URL, std::string &Response) {
		try {
			Poco::URI uri(URL);
			Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort());

			// prepare path
			std::string path(uri.getPathAndQuery());
			if (path.empty()) {
				path = "/";
			}

			// send request
			Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_GET, path,
									   Poco::Net::HTTPMessage::HTTP_1_1);
			session.sendRequest(req);

			Poco::Net::HTTPResponse res;
			std::istream &is = session.receiveResponse(res);
			std::ostringstream os;

			Poco::StreamCopier::copyStream(is, os);
			Response = os.str();

			return true;
		} catch (...) {
		}
		return false;
	}

	[[nodiscard]] bool wgetfile(const Poco::URI &uri, const std::string &FileName) {
		try {
			Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort());

			// send request
			Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_GET, uri.getPath(),
									   Poco::Net::HTTPMessage::HTTP_1_1);
			session.sendRequest(req);

			Poco::Net::HTTPResponse res;
			std::istream &is = session.receiveResponse(res);
			std::fstream os(FileName,
							std::ios_base::trunc | std::ios_base::binary | std::ios_base::out);
			Poco::StreamCopier::copyStream(is, os);
			return true;
		} catch (...) {
		}
		return false;
	}

	bool ExtractBase64CompressedData(const std::string &CompressedData,
									 std::string &UnCompressedData, uint64_t compress_sz) {
		std::istringstream ifs(CompressedData);
		Poco::Base64Decoder b64in(ifs);
		std::ostringstream ofs;
		Poco::StreamCopier::copyStream(b64in, ofs);

		int factor = 20;
		unsigned long MaxSize = compress_sz ? (unsigned long)(compress_sz + 5000)
											: (unsigned long)(ofs.str().size() * factor);
		while (true) {
			std::vector<uint8_t> UncompressedBuffer(MaxSize);
			unsigned long FinalSize = MaxSize;
			auto status = uncompress((uint8_t *)&UncompressedBuffer[0], &FinalSize,
									 (uint8_t *)ofs.str().c_str(), ofs.str().size());
			if (status == Z_OK) {
				UncompressedBuffer[FinalSize] = 0;
				UnCompressedData = (char *)&UncompressedBuffer[0];
				return true;
			}
			if (status == Z_BUF_ERROR) {
				if (factor < 300) {
					factor += 10;
					MaxSize = ofs.str().size() * factor;
					continue;
				} else {
					return false;
				}
			}
			return false;
		}
		return false;
	}

	bool IsAlphaNumeric(const std::string &s) {
		return std::all_of(s.begin(), s.end(), [](char c) -> bool { return isalnum(c); });
	}

	std::string SanitizeToken(const std::string &Token) {
		if (Token.size() > 8) {
			return Token.substr(0, 4) + "****" + Token.substr(Token.size() - 4, 4);
		}
		return "*******";
	}

	[[nodiscard]] bool ValidateURI(const std::string &uri) {
		try {
			Poco::URI u(uri);
			return true;
		} catch (...) {
		}
		return false;
	}

	[[nodiscard]] std::uint64_t ConvertDate(const std::string &Date) {
		Poco::DateTime DT;
		int TZ;
		Poco::DateTimeParser::parse(Poco::DateTimeFormat::ISO8601_FORMAT, Date, DT, TZ);
		return DT.timestamp().epochTime();
	}

    static std::string FileToString(const std::string &Filename) {
        std::ifstream   ifs(Filename.c_str(),std::ios_base::in|std::ios_base::binary);
        std::ostringstream os;
        Poco::StreamCopier::copyStream(ifs,os);
        return os.str();
    }

    bool CreateX509CSR(const CSRCreationParameters & Parameters, CSRCreationResults & Results) {
        int             ret = 0;
        RSA             *r = nullptr;
        BIGNUM          *bne = nullptr;

        int             nVersion = 0;
        unsigned long   e = RSA_F4;

        X509_REQ        *x509_req = nullptr;
        X509_NAME       *x509_name = nullptr;
        EVP_PKEY        *pKey = nullptr;
//        RSA             *tem = nullptr;
//        BIO             *bio_err = nullptr;

        const char      *szCountry = Parameters.Country.c_str();
        const char      *szProvince = Parameters.Province.c_str();
        const char      *szCity = Parameters.City.c_str();
        const char      *szOrganization = Parameters.Organization.c_str();
        const char      *szCommon = Parameters.CommonName.c_str();

        Poco::TemporaryFile     CsrPath, PubKey, PrivateKey;
        std::string             Result;
        std::ifstream           ifs;
        std::ostringstream      ss;
        BIO                     *bp_public = nullptr,
                *bp_private = nullptr,
                *bp_csr = nullptr;

        // 1. generate rsa key
        bne = BN_new();
        ret = BN_set_word(bne,e);
        if(ret != 1){
            goto free_all;
        }

        r = RSA_new();
        ret = RSA_generate_key_ex(r, Parameters.bits, bne, nullptr);
        if(ret != 1){
            goto free_all;
        }

        bp_public = BIO_new_file(PubKey.path().c_str(), "w+");
        ret = PEM_write_bio_RSAPublicKey(bp_public, r);
        if(ret != 1) {
            goto free_all;
        }

        bp_private = BIO_new_file(PrivateKey.path().c_str(), "w+");
        ret = PEM_write_bio_RSAPrivateKey(bp_private, r, NULL, NULL, 0, NULL, NULL);
        if(ret != 1) {
            goto free_all;
        }

// 2. set version of x509 req
        x509_req = X509_REQ_new();
        ret = X509_REQ_set_version(x509_req, nVersion);
        if (ret != 1){
            goto free_all;
        }

// 3. set subject of x509 req
        x509_name = X509_REQ_get_subject_name(x509_req);

        ret = X509_NAME_add_entry_by_txt(x509_name,"C", MBSTRING_ASC, (const unsigned char*)szCountry, -1, -1, 0);
        if (ret != 1){
            goto free_all;
        }

        ret = X509_NAME_add_entry_by_txt(x509_name,"ST", MBSTRING_ASC, (const unsigned char*)szProvince, -1, -1, 0);
        if (ret != 1){
            goto free_all;
        }

        ret = X509_NAME_add_entry_by_txt(x509_name,"L", MBSTRING_ASC, (const unsigned char*)szCity, -1, -1, 0);
        if (ret != 1){
            goto free_all;
        }

        ret = X509_NAME_add_entry_by_txt(x509_name,"O", MBSTRING_ASC, (const unsigned char*)szOrganization, -1, -1, 0);
        if (ret != 1){
            goto free_all;
        }

        ret = X509_NAME_add_entry_by_txt(x509_name,"CN", MBSTRING_ASC, (const unsigned char*)szCommon, -1, -1, 0);
        if (ret != 1){
            goto free_all;
        }

// 4. set public key of x509 req
        pKey = EVP_PKEY_new();
        EVP_PKEY_assign_RSA(pKey, r);
        r = nullptr;   // will be free rsa when EVP_PKEY_free(pKey)

        ret = X509_REQ_set_pubkey(x509_req, pKey);
        if (ret != 1){
            goto free_all;
        }

// 5. set sign key of x509 req
        ret = X509_REQ_sign(x509_req, pKey, EVP_sha1());    // return x509_req->signature->length
        if (ret <= 0){
            goto free_all;
        }

        bp_csr = BIO_new_file(CsrPath.path().c_str(),"w");
        ret = PEM_write_bio_X509_REQ(bp_csr, x509_req);

// 6. free
        free_all:
        X509_REQ_free(x509_req);
        BIO_free_all(bp_csr);
        BIO_free_all(bp_public);
        BIO_free_all(bp_private);

        EVP_PKEY_free(pKey);
        BN_free(bne);
        if(ret==1) {
            Results.CSR = FileToString(CsrPath.path());
            Results.PrivateKey = FileToString(PrivateKey.path());
            Results.PublicKey = FileToString(PubKey.path());
        }

        return ret;
    }

    bool VerifyECKey(const std::string &key) {
        try {
            Poco::TemporaryFile F;

            std::ofstream of(F.path().c_str(), std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);
            of << key;
            of.close();

            auto Key = Poco::SharedPtr<Poco::Crypto::ECKey>(
                    new Poco::Crypto::ECKey("", F.path(),""));

            return true;
        } catch (const Poco::Exception &E) {

        }
        return false;
    }

    bool VerifyRSAKey([[
    maybe_unused]] const std::string &key) {
        try {
            Poco::TemporaryFile F;

            std::ofstream of(F.path().c_str(), std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);
            of << key;
            of.close();

            auto Key = Poco::SharedPtr<Poco::Crypto::RSAKey>(
                    new Poco::Crypto::RSAKey("", F.path(),""));
            return true;
        } catch (const Poco::Exception &E) {

        }
        return false;
    }

    bool VerifyPrivateKey(const std::string &key) {
        return VerifyECKey(key) || VerifyRSAKey(key);
    }

    bool ValidX509Certificate([[
                              maybe_unused]] const std::string &Cert) {
        try {
            Poco::TemporaryFile F;
            std::ofstream of(F.path().c_str(), std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);
            of << Cert;
            of.close();

            auto Key = Poco::SharedPtr<Poco::Crypto::X509Certificate>(
                    new Poco::Crypto::X509Certificate(F.path()));
            return true;
        } catch (const Poco::Exception &E) {

        }
        return false;
    }

    bool ValidX509Certificate([[
                              maybe_unused]] const std::vector<std::string> &Certs) {
        auto F = [](const std::string &C) -> bool { return ValidX509Certificate(C); };
        return std::all_of(Certs.begin(),Certs.end(), F);
    }

    std::string generateStrongPassword(int minLength, int maxLength, int numDigits, int minLowercase, int minSpecial, int minUppercase) {
        // Define character sets for each category
        const std::string lowercaseChars = "abcdefghijklmnopqrstuvwxyz";
        const std::string uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const std::string digitChars = "0123456789";
        const std::string specialChars = "!@#$%^&*()_+[]{}|;:,.<>?";

        // Check if parameters are valid
        if (minLength < 1 || minLength > maxLength || minLowercase + minUppercase + numDigits + minSpecial > maxLength) {
            return "Invalid parameters";
        }

        // Initialize random seed
        std::random_device rd;
        std::mt19937 g(rd());

        // Initialize the password string
        std::string password;

        // Generate the required number of each character type
        for (int i = 0; i < minLowercase; ++i) {
            password += lowercaseChars[g() % lowercaseChars.length()];
        }
        for (int i = 0; i < minUppercase; ++i) {
            password += uppercaseChars[g() % uppercaseChars.length()];
        }
        for (int i = 0; i < numDigits; ++i) {
            password += digitChars[g() % digitChars.length()];
        }
        for (int i = 0; i < minSpecial; ++i) {
            password += specialChars[g() % specialChars.length()];
        }

        // Calculate how many more characters are needed
        int remainingLength = maxLength - (int)password.length();

        // Generate random characters to fill the remaining length
        for (int i = 0; i < remainingLength; ++i) {
            int category = g() % 4; // Randomly select a category
            if (category == 0) {
                password += lowercaseChars[g() % lowercaseChars.length()];
            } else if (category == 1) {
                password += uppercaseChars[g() % uppercaseChars.length()];
            } else if (category == 2) {
                password += digitChars[g() % digitChars.length()];
            } else {
                password += specialChars[g() % specialChars.length()];
            }
        }

        // Shuffle the password to randomize the character order
        std::shuffle(password.begin(), password.end(),g);

        return password;
    }

// Function to query NAPTR records for a domain and return them in a vector
    std::vector<NAPTRRecord> getNAPTRRecords(const std::string& domain) {
        std::vector<NAPTRRecord> naptrRecords;

        unsigned char buf[4096];
        ns_msg handle;
        ns_initparse(buf, NS_PACKETSZ, &handle);

        // Query NAPTR records for the given domain
        int response = res_query(domain.c_str(), ns_c_in, ns_t_naptr, buf, sizeof(buf));
        if (response < 0) {
            return naptrRecords;
        }

        if(ns_initparse(buf, response, &handle) < 0) {
            return naptrRecords;
        }

        // Iterate through the DNS response and extract NAPTR records
        int count = ns_msg_count(handle, ns_s_an);
        for (int i = 0; i < count; ++i) {
            ns_rr rr;
            if (ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
                char rdata[256];
                ns_sprintrr(&handle, &rr, nullptr, nullptr, rdata, sizeof(rdata));
                NAPTRRecord record;
                std::istringstream os(rdata);
                os  >> record.name >> record.ttl >> record.rclass >> record.rtype >> record.order >> record.preference >> record.flags
                    >> record.service >> record.regexp >>  record.replacement;
                naptrRecords.push_back(record);
            }
        }

        return naptrRecords;
    }

    std::vector<SrvRecord> getSRVRecords(const std::string& domain) {
        std::vector<SrvRecord> srvRecords;

        // Buffer to hold the DNS response
        unsigned char buf[4096];
        ns_msg handle;
        ns_initparse(buf, NS_PACKETSZ, &handle);

        // Query NAPTR records for the given domain
        int response = res_query(domain.c_str(), ns_c_in, ns_t_srv, buf, sizeof(buf));
        if (response < 0) {
            std::cerr << "DNS query failed for " << domain << ": " << hstrerror(h_errno) << std::endl;
            return srvRecords;
        }

        if(ns_initparse(buf, response, &handle) < 0) {
            return srvRecords;
        }

        // Iterate through the DNS response and extract NAPTR records
        int count = ns_msg_count(handle, ns_s_an);
        for (int i = 0; i < count; ++i) {
            ns_rr rr;
            if (ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
                char rdata[256];
                ns_sprintrr(&handle, &rr, nullptr, nullptr, rdata, sizeof(rdata));
                SrvRecord record;
                std::istringstream os(rdata);
                os  >>  record.name >> record.ttl >> record.rclass >> record.rtype >> record.pref >> record.weight >>
                    record.port >> record.srvname ;
                srvRecords.push_back(record);
            }
        }

        return srvRecords;
    }


} // namespace OpenWifi::Utils
