//
//	License type: BSD 3-Clause License
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
//	Created by Stephane Bourque on 2021-03-04.
//	Arilia Wireless Inc.
//

#pragma once

#include <array>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include "Poco/Data/RecordSet.h"
#include "Poco/Data/SQLite/Connector.h"
#include "Poco/Data/SessionPool.h"
#include "Poco/Data/Statement.h"
#include "Poco/Logger.h"
#include "Poco/StringTokenizer.h"
#include "Poco/Tuple.h"
#include "StorageClass.h"

#include "fmt/format.h"

namespace ORM {

	enum FieldType { FT_INT, FT_BIGINT, FT_TEXT, FT_VARCHAR, FT_BLOB, FT_BOOLEAN, FT_REAL };

	enum Indextype { ASC, DESC };

	struct Field {
		std::string Name;
		FieldType Type;
		int Size = 0;
		bool Index = false;

		Field(std::string N, FieldType T, int S = 0, bool Index = false)
			: Name(std::move(N)), Type(T), Size(S), Index(Index) {}

		explicit Field(std::string N) : Name(std::move(N)) { Type = FT_TEXT; }

		Field(std::string N, int S) : Name(std::move(N)), Size(S) {
			if (Size > 0 && Size < 255)
				Type = FT_VARCHAR;
			else
				Type = FT_TEXT;
		}

		Field(std::string N, int S, bool I) : Name(std::move(N)), Size(S), Index(I) {
			if (Size > 0 && Size < 255)
				Type = FT_VARCHAR;
			else
				Type = FT_TEXT;
		}
	};
	typedef std::vector<Field> FieldVec;

	struct IndexEntry {
		std::string FieldName;
		Indextype Type;
	};
	typedef std::vector<IndexEntry> IndexEntryVec;

	struct Index {
		std::string Name;
		IndexEntryVec Entries;
	};
	typedef std::vector<Index> IndexVec;

	inline std::string FieldTypeToChar(OpenWifi::DBType Type, FieldType T, int Size = 0) {
		switch (T) {
		case FT_INT:
			return "INT";
		case FT_BIGINT:
			return "BIGINT";
		case FT_TEXT:
			return "TEXT";
		case FT_BOOLEAN:
			return "BOOLEAN";
		case FT_VARCHAR:
			if (Size)
				return std::string("VARCHAR(") + std::to_string(Size) + std::string(")");
			else
				return "TEXT";
		case FT_BLOB:
			if (Type == OpenWifi::DBType::mysql)
				return "LONGBLOB";
			else if (Type == OpenWifi::DBType::pgsql)
				return "BYTEA";
			else
				return "BLOB";
		case FT_REAL:
			return "REAL";
		default:
			assert(false);
		}
		assert(false);
		return "";
	}

	inline std::string Escape(const std::string &S) {
		std::string R;

		for (const auto &i : S) {
			if (i == '\'')
				R += "''";
			else
				R += i;
		}
		return R;
	}

	inline std::string WHERE_AND_(std::string Result) { return Result; }

	template <typename T, typename... Args>
	std::string WHERE_AND_(std::string Result, const char *fieldName, const T &Value,
						   Args... args) {
		if constexpr (std::is_same_v<T, std::string>) {
			if (!Value.empty()) {
				if (!Result.empty())
					Result += " and ";
				Result += fieldName;
				Result += '=';
				Result += "'";
				Result += Escape(Value);
				Result += "'";
			}
			return WHERE_AND_(Result, args...);
		} else if constexpr (std::is_same_v<T, const char *>) {
			if (*Value != 0) {
				if (!Result.empty())
					Result += " and ";
				Result += fieldName;
				Result += '=';
				Result += "'";
				Result += Escape(Value);
				Result += "'";
			}
			return WHERE_AND_(Result, args...);
		} else if constexpr (std::is_same_v<T, bool>) {
			if (!Result.empty())
				Result += " and ";
			Result += fieldName;
			Result += '=';
			Result += Value ? "true" : "false";
			return WHERE_AND_(Result, args...);
		} else if constexpr (std::is_arithmetic_v<T>) {
			if (!Result.empty())
				Result += " and ";
			Result += fieldName;
			Result += '=';
			Result += std::to_string(Value);
			return WHERE_AND_(Result, args...);
		} else {
			assert(false);
		}
		return WHERE_AND_(Result, args...);
	}

	template <typename... Args> std::string WHERE_AND(Args... args) {
		std::string Result;
		return WHERE_AND_(Result, args...);
	}

	enum SqlComparison { EQ = 0, NEQ, LT, LTE, GT, GTE };
	enum SqlBinaryOp { AND = 0, OR };

	static const std::vector<std::string> BOPS{" and ", " or "};
	static const std::vector<std::string> SQLCOMPS{"=", "!=", "<", "<=", ">", ">="};

	inline std::string to_string(uint64_t V) { return std::to_string(V); }

	inline std::string to_string(int V) { return std::to_string(V); }

	inline std::string to_string(bool V) { return std::to_string(V); }

	inline std::string to_string(const std::string &S) { return S; }

	inline std::string to_string(const Poco::Data::BLOB &blob) {
		std::string result;
		result.assign(blob.begin(), blob.end());
		return result;
	}

	inline std::string to_string(const char *S) { return S; }

	template <typename RecordType> class DBCache {
	  public:
		DBCache(unsigned Size, unsigned Timeout) : Size_(Size), Timeout_(Timeout) {}
		virtual void Create(const RecordType &R) = 0;
		virtual bool GetFromCache(const std::string &FieldName, const std::string &Value,
								  RecordType &R) = 0;
		virtual void UpdateCache(const RecordType &R) = 0;
		virtual void Delete(const std::string &FieldName, const std::string &Value) = 0;

	  private:
		size_t Size_ = 0;
		uint64_t Timeout_ = 0;
	};

	template <typename RecordTuple, typename RecordType> class DB {
	  public:
		typedef const char *field_name_t;

		DB(OpenWifi::DBType dbtype, const char *TableName, const FieldVec &Fields,
		   const IndexVec &Indexes, Poco::Data::SessionPool &Pool, Poco::Logger &L,
		   const char *Prefix, DBCache<RecordType> *Cache = nullptr)
			: TableName_(TableName), Type_(dbtype), Pool_(Pool), Logger_(L), Prefix_(Prefix),
			  Cache_(Cache) {
			assert(RecordTuple::length == Fields.size());

			bool first = true;
			int Place = 0;

			for (const auto &i : Fields) {
				std::string FieldName = Poco::toLower(i.Name);
				FieldNames_[FieldName] = Place;
				if (!first) {
					CreateFields_ += ", ";
					SelectFields_ += ", ";
					UpdateFields_ += ", ";
					SelectList_ += ", ";
				} else {
					SelectList_ += "(";
				}

				CreateFields_ += FieldName + " " + FieldTypeToChar(Type_, i.Type, i.Size) +
								 (i.Index ? " unique primary key" : "");
				SelectFields_ += FieldName;
				UpdateFields_ += FieldName + "=?";
				SelectList_ += "?";
				first = false;
				Place++;
			}
			SelectList_ += ")";

			if (!Indexes.empty()) {
				if (Type_ == OpenWifi::DBType::sqlite || Type_ == OpenWifi::DBType::pgsql) {
					for (const auto &j : Indexes) {
						std::string IndexLine;

						IndexLine = std::string("CREATE INDEX IF NOT EXISTS ") + j.Name +
									std::string(" ON ") + TableName_ + " (";
						bool first_entry = true;
						for (const auto &k : j.Entries) {
							auto IndexFieldName = Poco::toLower(k.FieldName);
							assert(ValidFieldName(IndexFieldName));
							if (!first_entry) {
								IndexLine += " , ";
							}
							first_entry = false;
							IndexLine += IndexFieldName + std::string(" ") +
										 std::string(k.Type == Indextype::ASC ? "ASC" : "DESC");
						}
						IndexLine += " )";
						IndexCreation_.template emplace_back(IndexLine);
					}
				} else if (Type_ == OpenWifi::DBType::mysql) {
					bool firstIndex = true;
					std::string IndexLine;
					for (const auto &j : Indexes) {
						if (!firstIndex)
							IndexLine += ", ";
						firstIndex = false;
						IndexLine += " INDEX " + j.Name + " ( ";
						bool first_entry = true;
						for (const auto &k : j.Entries) {
							auto IndexFieldName = Poco::toLower(k.FieldName);
							assert(FieldNames_.find(IndexFieldName) != FieldNames_.end());
							if (!first_entry) {
								IndexLine += " ,";
							}
							first_entry = false;
							IndexLine += IndexFieldName +
										 std::string(k.Type == Indextype::ASC ? " ASC" : " DESC");
						}
						IndexLine += " ) ";
					}
					IndexCreation_.template emplace_back(IndexLine);
				}
			}
		}

		[[nodiscard]] const std::string &CreateFields() const { return CreateFields_; };
		[[nodiscard]] const std::string &SelectFields() const { return SelectFields_; };
		[[nodiscard]] const std::string &SelectList() const { return SelectList_; };
		[[nodiscard]] const std::string &UpdateFields() const { return UpdateFields_; };

		inline std::string OP(field_name_t F, SqlComparison O, bool V) {
			assert(ValidFieldName(F));
			return std::string{"("} + F + SQLCOMPS[O] + (V ? "true" : "false") + ")";
		}

		inline std::string OP(field_name_t F, SqlComparison O, int V) {
			assert(ValidFieldName(F));
			return std::string{"("} + F + SQLCOMPS[O] + std::to_string(V) + ")";
		}

		inline std::string OP(field_name_t F, SqlComparison O, uint64_t V) {
			assert(ValidFieldName(F));
			return std::string{"("} + F + SQLCOMPS[O] + std::to_string(V) + ")";
		}

		std::string OP(field_name_t F, SqlComparison O, const std::string &V) {
			assert(ValidFieldName(F));
			return std::string{"("} + F + SQLCOMPS[O] + "'" + Escape(V) + "')";
		}

		std::string OP(field_name_t F, SqlComparison O, const char *V) {
			assert(ValidFieldName(F));
			return std::string{"("} + F + SQLCOMPS[O] + "'" + Escape(V) + "')";
		}

		static std::string OP(const std::string &P1, SqlBinaryOp BOP, const std::string &P2) {
			return std::string("(") + P1 + BOPS[BOP] + P2 + ")";
		}

		std::string OP([[maybe_unused]] bool Paran, const std::string &P1, SqlBinaryOp BOP,
					   const std::string &P2) {
			return P1 + BOPS[BOP] + P2 + ")";
		}

		template <typename... Others>
		std::string OP(bool ParanOpen, const std::string &P1, SqlBinaryOp BOP,
					   const std::string &P2, Others... More) {
			return P1 + BOPS[BOP] + OP(ParanOpen, P2, More...) + ")";
		}

		template <typename... Others>
		std::string OP(const std::string &P1, SqlBinaryOp BOP, const std::string &P2,
					   Others... More) {
			return std::string{"("} + P1 + BOPS[BOP] + OP(true, P2, More...);
		}

		bool Upgrade() {
			uint32_t To;
			return Upgrade(0, To);
		}

		inline bool Create() {
			switch (Type_) {
			case OpenWifi::DBType::mysql: {
				try {
					Poco::Data::Session Session = Pool_.get();
					std::string Statement = IndexCreation_.empty()
												? "create table if not exists " + TableName_ +
													  " ( " + CreateFields_ + " )"
												: "create table if not exists " + TableName_ +
													  " ( " + CreateFields_ + " ), " +
													  IndexCreation_[0] + " )";
					Session << Statement, Poco::Data::Keywords::now;
				} catch (const Poco::Exception &E) {
					Logger_.error("Failure to create MySQL DB resources.");
					Logger_.log(E);
				}
			} break;

			case OpenWifi::DBType::sqlite: {
				try {
					Poco::Data::Session Session = Pool_.get();
					std::string Statement =
						"create table if not exists " + TableName_ + " ( " + CreateFields_ + " )";
					Session << Statement, Poco::Data::Keywords::now;
					for (const auto &i : IndexCreation_) {
						Session << i, Poco::Data::Keywords::now;
					}
				} catch (const Poco::Exception &E) {
					Logger_.error("Failure to create SQLITE DB resources.");
					Logger_.log(E);
				}
			} break;

			case OpenWifi::DBType::pgsql: {
				try {
					Poco::Data::Session Session = Pool_.get();
					std::string Statement =
						"create table if not exists " + TableName_ + " ( " + CreateFields_ + " )";
					Session << Statement, Poco::Data::Keywords::now;
					for (const auto &i : IndexCreation_) {
						Session << i, Poco::Data::Keywords::now;
					}
				} catch (const Poco::Exception &E) {
					Logger_.error("Failure to create POSTGRESQL DB resources.");
					Logger_.log(E);
				}
			} break;
			}
			return Upgrade();
		}

		[[nodiscard]] std::string ConvertParams(const std::string &S) const {
			if (Type_ != OpenWifi::DBType::pgsql)
				return S;

			std::string R;
			R.reserve(S.size() * 2 + 1);
			auto Idx = 1;
			for (auto const &i : S) {
				if (i == '?') {
					R += '$';
					R.append(std::to_string(Idx++));
				} else {
					R += i;
				}
			}

			return R;
		}

		void Convert(const RecordTuple &in, RecordType &out);
		void Convert(const RecordType &in, RecordTuple &out);

		inline const std::string &Prefix() { return Prefix_; };

		bool CreateRecord(const RecordType &R) {
			try {
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Insert(Session);

				RecordTuple RT;
				Convert(R, RT);
				std::string St = "insert into  " + TableName_ + " ( " + SelectFields_ +
								 " ) values " + SelectList_;
				Insert << ConvertParams(St), Poco::Data::Keywords::use(RT);
				Insert.execute();

				if (Cache_)
					Cache_->Create(R);
				return true;

			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		template <typename T>
		bool GetRecord(field_name_t FieldName, const T &Value, RecordType &R) {
			try {
				assert(ValidFieldName(FieldName));

				if (Cache_) {
					if (Cache_->GetFromCache(FieldName, Value, R))
						return true;
				}

				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);
				RecordTuple RT;

				std::string St = "select " + SelectFields_ + " from " + TableName_ + " where " +
								 FieldName + "=?" + " limit 1";

				auto tValue{Value};

				Select << ConvertParams(St), Poco::Data::Keywords::into(RT),
					Poco::Data::Keywords::use(tValue);
				Select.execute();

				if (Select.execute() == 1) {
					Convert(RT, R);
					if (Cache_)
						Cache_->UpdateCache(R);
					return true;
				}
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool GetRecord(RecordType &T, const std::string &WhereClause) {
			try {
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);
				RecordTuple RT;

				std::string St = "select " + SelectFields_ + " from " + TableName_ + " where " +
								 WhereClause + " limit 1";

				Select << ConvertParams(St), Poco::Data::Keywords::into(RT);
				Select.execute();

				if (Select.execute() == 1) {
					Convert(RT, T);
					if (Cache_)
						Cache_->UpdateCache(T);
					return true;
				}
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		typedef std::vector<std::string> StringVec;

		template <typename T, typename T0, typename T1>
		bool GR(field_name_t FieldName, T &R, T0 &V0, T1 &V1) {
			try {

				assert(ValidFieldName(FieldName));

				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);
				RecordTuple RT;

				std::string St = "select " + SelectFields_ + " from " + TableName_ + " where " +
								 FieldName[0] + "=? and " + FieldName[1] + "=?";
				Select << ConvertParams(St), Poco::Data::Keywords::into(RT),
					Poco::Data::Keywords::use(V0), Poco::Data::Keywords::use(V1);

				if (Select.execute() == 1) {
					Convert(RT, R);
					return true;
				}
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		template <typename T> bool Join(const std::string &statement, std::vector<T> &records) {
			try {
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);

				Select << statement, Poco::Data::Keywords::into(records);
				Select.execute();
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		typedef std::vector<RecordTuple> RecordList;
		typedef std::vector<RecordType> RecordVec;
		typedef RecordType RecordName;

		bool GetRecords(uint64_t Offset, uint64_t HowMany, RecordVec &Records,
						const std::string &Where = "", const std::string &OrderBy = "") {
			try {
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);
				RecordList RL;
				std::string St = "select " + SelectFields_ + " from " + TableName_ +
								 (Where.empty() ? "" : " where " + Where) + OrderBy +
								 ComputeRange(Offset, HowMany);

				Select << St, Poco::Data::Keywords::into(RL);
				Select.execute();

				if (Select.rowsExtracted() > 0) {
					for (auto &i : RL) {
						RecordType R;
						Convert(i, R);
						Records.template emplace_back(R);
					}
					return true;
				}
				return false;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		template <typename T>
		bool UpdateRecord(field_name_t FieldName, const T &Value, const RecordType &R) {
			try {
				assert(ValidFieldName(FieldName));
				Poco::Data::Session Session = Pool_.get();
                Session.begin();
				Poco::Data::Statement Update(Session);

				RecordTuple RT;

				Convert(R, RT);

				auto tValue(Value);

				std::string St =
					"update " + TableName_ + " set " + UpdateFields_ + " where " + FieldName + "=?";
				Update << ConvertParams(St), Poco::Data::Keywords::use(RT),
					Poco::Data::Keywords::use(tValue);
				Update.execute();
				if (Cache_)
					Cache_->UpdateCache(R);
                Session.commit();
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool RunStatement(const std::string &St) {
			try {
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Command(Session);

				Command << St;
				Command.execute();

				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		template <typename T>
		bool ReplaceRecord(field_name_t FieldName, const T &Value, RecordType &R) {
			try {
				if (Exists(FieldName, Value)) {
					return UpdateRecord(FieldName, Value, R);
				}
				return CreateRecord(R);
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		template <typename T>
		bool GetNameAndDescription(field_name_t FieldName, const T &Value, std::string &Name,
								   std::string &Description) {
			try {
				assert(ValidFieldName(FieldName));
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);
				RecordTuple RT;

				std::string St = "select " + SelectFields_ + " from " + TableName_ + " where " +
								 FieldName + "=?";
				RecordType R;
				auto tValue{Value};
				Select << ConvertParams(St), Poco::Data::Keywords::into(RT),
					Poco::Data::Keywords::use(tValue);

				if (Select.execute() == 1) {
					Convert(RT, R);
					Name = R.info.name;
					Description = R.info.description;
					return true;
				}
				return false;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		template <typename T> bool DeleteRecord(field_name_t FieldName, const T &Value) {
			try {
				assert(ValidFieldName(FieldName));

				Poco::Data::Session Session = Pool_.get();
                Session.begin();
				Poco::Data::Statement Delete(Session);

				std::string St = "delete from " + TableName_ + " where " + FieldName + "=?";
				auto tValue{Value};

				Delete << ConvertParams(St), Poco::Data::Keywords::use(tValue);
				Delete.execute();
				if (Cache_)
					Cache_->Delete(FieldName, Value);
                Session.commit();
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool DeleteRecords(const std::string &WhereClause) {
			try {
				assert(!WhereClause.empty());
				Poco::Data::Session Session = Pool_.get();
                Session.begin();
				Poco::Data::Statement Delete(Session);

				std::string St = "delete from " + TableName_ + " where " + WhereClause;
				Delete << St;
				Delete.execute();
                Session.commit();
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool Exists(field_name_t FieldName, const std::string &Value) {
			try {
				assert(ValidFieldName(FieldName));

				RecordType R;
				if (GetRecord(FieldName, Value, R))
					return true;
				return false;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool Iterate(std::function<bool(const RecordType &R)> F,
					 const std::string &WhereClause = "") {
			try {

				uint64_t Offset = 0;
				uint64_t Batch = 50;
				bool Done = false;
				while (!Done) {
					std::vector<RecordType> Records;
					if (GetRecords(Offset, Batch, Records, WhereClause)) {
						for (const auto &i : Records) {
							if (!F(i))
								return true;
						}
						if (Records.size() < Batch)
							return true;
						Offset += Batch;
					} else {
						Done = true;
					}
				}
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool PrepareOrderBy(const std::string &OrderByList, std::string &OrderByString) {
			auto items = Poco::StringTokenizer(OrderByList, ",");
			std::string ItemList;

			for (const auto &i : items) {
				auto T = Poco::StringTokenizer(i, ":");
				if (T.count() != 2) {
					return false;
				}
				if (T[1] != "a" && T[1] != "d") {
					return false;
				}
				if (!ItemList.empty())
					ItemList += " , ";
				auto hint = FieldNames_.find(Poco::toLower(T[0]));
				if (hint == FieldNames_.end()) {
					return false;
				}
				ItemList += T[0] + (T[1] == "a" ? " ASC" : " DESC");
			}

			if (!ItemList.empty()) {
				OrderByString = " ORDER BY " + ItemList;
			}
			return true;
		}

		uint64_t Count(const std::string &Where = "") {
			try {
				uint64_t Cnt = 0;

				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Select(Session);

				std::string st{"SELECT COUNT(*) FROM " + TableName_ + " " +
							   (Where.empty() ? "" : (" where " + Where))};

				Select << st, Poco::Data::Keywords::into(Cnt);
				Select.execute();

				return Cnt;

			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return 0;
		}

		template <typename X>
		bool ManipulateVectorMember(X T, field_name_t FieldName, const std::string &ParentUUID,
									const std::string &ChildUUID, bool Add) {
			try {
				assert(ValidFieldName(FieldName));

				RecordType R;
				if (GetRecord(FieldName, ParentUUID, R)) {
					auto it = std::find((R.*T).begin(), (R.*T).end(), ChildUUID);
					if (Add) {
						if (it != (R.*T).end() && *it == ChildUUID)
							return false;
						(R.*T).push_back(ChildUUID);
						std::sort((R.*T).begin(), (R.*T).end());
					} else {
						if (it != (R.*T).end() && *it == ChildUUID)
							(R.*T).erase(it);
						else
							return false;
					}
					UpdateRecord(FieldName, ParentUUID, R);
					return true;
				}
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		bool RunScript(const std::vector<std::string> &Statements, bool IgnoreExceptions = true) {
			try {
				Poco::Data::Session Session = Pool_.get();
				Poco::Data::Statement Command(Session);

				for (const auto &i : Statements) {
					try {
						Command << i, Poco::Data::Keywords::now;
					} catch (const Poco::Exception &E) {
						// Logger_.log(E);
						// Logger_.error(Poco::format("The following statement '%s' generated an
						// exception during a table upgrade. This may or may not be a problem.",
						// i));
						if (!IgnoreExceptions) {
							return false;
						}
					}
					Command.reset(Session);
				}
				return true;
			} catch (const Poco::Exception &E) {
				Logger_.log(E);
			}
			return false;
		}

		virtual uint32_t Version() { return 0; }

		virtual bool Upgrade(uint32_t from, uint32_t &to) {
			to = from;
			return true;
		}

		inline bool AddChild(field_name_t FieldName, const std::string &ParentUUID,
							 const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::children, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DeleteChild(field_name_t FieldName, const std::string &ParentUUID,
								const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::children, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddLocation(field_name_t FieldName, const std::string &ParentUUID,
								const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::locations, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DeleteLocation(field_name_t FieldName, const std::string &ParentUUID,
								   const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::locations, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddContact(field_name_t FieldName, const std::string &ParentUUID,
							   const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::contacts, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DeleteContact(field_name_t FieldName, const std::string &ParentUUID,
								  const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::contacts, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddVenue(field_name_t FieldName, const std::string &ParentUUID,
							 const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::venues, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DeleteVenue(field_name_t FieldName, const std::string &ParentUUID,
								const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::venues, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddDevice(field_name_t FieldName, const std::string &ParentUUID,
							  const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::devices, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DeleteDevice(field_name_t FieldName, const std::string &ParentUUID,
								 const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::devices, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddEntity(field_name_t FieldName, const std::string &ParentUUID,
							  const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::entities, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DeleteEntity(field_name_t FieldName, const std::string &ParentUUID,
								 const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::entities, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddUser(field_name_t FieldName, const std::string &ParentUUID,
							const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::users, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DelUser(field_name_t FieldName, const std::string &ParentUUID,
							const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::users, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddConfiguration(field_name_t FieldName, const std::string &ParentUUID,
									 const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::deviceConfiguration, FieldName, ParentUUID,
										  ChildUUID, true);
		}

		inline bool DelConfiguration(field_name_t FieldName, const std::string &ParentUUID,
									 const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::deviceConfiguration, FieldName, ParentUUID,
										  ChildUUID, false);
		}

		inline bool AddVariable(field_name_t FieldName, const std::string &ParentUUID,
								const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::variables, FieldName, ParentUUID, ChildUUID,
										  true);
		}

		inline bool DelVariable(field_name_t FieldName, const std::string &ParentUUID,
								const std::string &ChildUUID) {
			return ManipulateVectorMember(&RecordType::variables, FieldName, ParentUUID, ChildUUID,
										  false);
		}

		inline bool AddInUse(field_name_t FieldName, const std::string &ParentUUID,
							 const std::string &Prefix, const std::string &ChildUUID) {
			std::string FakeUUID{Prefix + ":" + ChildUUID};
			return ManipulateVectorMember(&RecordType::inUse, FieldName, ParentUUID, FakeUUID,
										  true);
		}

		inline bool DeleteInUse(field_name_t FieldName, const std::string &ParentUUID,
								const std::string &Prefix, const std::string &ChildUUID) {
			std::string FakeUUID{Prefix + ":" + ChildUUID};
			return ManipulateVectorMember(&RecordType::inUse, FieldName, ParentUUID, FakeUUID,
										  false);
		}

		inline bool GetInUse(field_name_t FieldName, const std::string &UUID,
							 std::vector<std::string> &UUIDs) {
			RecordType R;
			if (GetRecord(FieldName, UUID, R)) {
				UUIDs = R.inUse;
				return true;
			}
			return false;
		}

		inline bool ValidFieldName(const std::string &FieldName) {
			return FieldNames_.find(Poco::toLower(FieldName)) != FieldNames_.end();
		}

		inline bool ValidFieldName(const char *FieldName) {
			std::string Field{FieldName};
			return ValidFieldName(Field);
		}

		[[nodiscard]] inline std::string ComputeRange(uint64_t From, uint64_t HowMany) {
			if (From < 1)
				From = 0;
			switch (Type_) {
			case OpenWifi::DBType::sqlite:
				return " LIMIT " + std::to_string(From) + ", " + std::to_string(HowMany) + " ";
			case OpenWifi::DBType::pgsql:
				return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) +
					   " ";
			case OpenWifi::DBType::mysql:
				return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) +
					   " ";
			default:
				return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) +
					   " ";
			}
		}

		Poco::Logger &Logger() { return Logger_; }

		inline bool DeleteRecordsFromCache(const char *FieldName, const std::string &Value) {
			if (Cache_)
				Cache_->Delete(FieldName, Value);
			return true;
		}

		inline void GetFieldNames(OpenWifi::Types::StringVec &F) {
			for (const auto &[field, _] : FieldNames_)
				F.push_back(field);
		}

	  protected:
		std::string TableName_;
		OpenWifi::DBType Type_;
		Poco::Data::SessionPool &Pool_;
		Poco::Logger &Logger_;
		std::string Prefix_;
		DBCache<RecordType> *Cache_ = nullptr;

	  private:
		std::string CreateFields_;
		std::string SelectFields_;
		std::string SelectList_;
		std::string UpdateFields_;
		std::vector<std::string> IndexCreation_;
		std::map<std::string, int> FieldNames_;
	};
} // namespace ORM
