using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.IO;
using System.Xml;
using System.Security.Principal;
using System.Net;
using System.Globalization;

using C4IT.Logging;
using C4IT.Security;
using C4IT.XML;
using C4IT.FASD.Base;

using static C4IT.Logging.cLogManager;

#if !NETSTANDARD && !NETCORE
using System.Data.SqlClient;
#else
using Microsoft.Data.SqlClient;
#endif

namespace C4IT.DataHistoryProvider
{
    public static class DataHistorySqlHelper
    {
        public const string constWellDefinedSqlFile = "DataHistorySql";

        public const int constDefaultSqlTimeout = 20;

        public const string constBinValueNameAdd = "_bin";

        private static string ApplicationName = null;

        private static string WorkstationID = null;

        public readonly static Dictionary<string, cDbQuery> wellDefinedSqlStatements = new Dictionary<string, cDbQuery>();

        public static int lastSqlConnectionError { get; private set; } = 0;

        public static bool LogSql = false;
        public static string SqlLogFileName = null;
        public static object SqlLogSync = new object();

        public static void Initialize(Assembly Ass = null)
        {
            if (WorkstationID != null)
                return;

            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (Ass == null)
                    Ass = Assembly.GetExecutingAssembly();

                ApplicationName = cLogManagerFile.GetAssemblyTitle(Ass);
                WorkstationID = Environment.MachineName;

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

        }

        static DataHistorySqlHelper()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                Initialize();

                // initialize encryption enclave providers at statup, because this will not work if the first sql operation is impersonated 
                // see https://support.microsoft.com/en-us/topic/sqlconnection-instantiation-exception-on-net-4-6-and-later-after-august-september-2018-net-framework-updates-8486716b-6f39-8ed5-37f6-25487ee8b9ab
                // see https://stackoverflow.com/questions/12791207/catastrophic-failure-when-impersonate-other-user/16777117#16777117
                System.Configuration.ConfigurationManager.GetSection("SqlColumnEncryptionEnclaveProviders");
                System.Configuration.ConfigurationManager.GetSection("SectionNameThatDoesNotExistInAnyConfigurationFile");
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public static void SaveSqlTimingEntry(string SqlQueryName, DateTime start, double duration, string SqlQueryParams = null, int error = 0, string OriginName = null)
        {
            try
            {
                if (!LogSql || SqlLogFileName == null)
                    return;

                var _strStart = start.ToString("yyyy-MM-dd-HH-mm-ss-FFFFF");
                var _strEnd = DateTime.UtcNow.ToString("yyyy-MM-dd-HH-mm-ss-FFFFF");

                var _line = string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\r\n", _strStart, _strEnd, SqlQueryName, duration, error, SqlQueryParams ?? "", OriginName ?? "");
                lock (SqlLogSync)
                    System.IO.File.AppendAllText(SqlLogFileName, _line, Encoding.UTF8);
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        public static void SaveSqlTimingEntry(string SqlQueryName, DateTime timeStart, string SqlQueryParams = null, int error = 0, string OriginName = null)
        {
            try
            {
                if (!LogSql || SqlLogFileName == null)
                    return;

                var end = DateTime.UtcNow;
                var duration = (end - timeStart).TotalMilliseconds;

                SaveSqlTimingEntry(SqlQueryName, timeStart, duration, SqlQueryParams, error, OriginName);
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        public static void SaveSqlTimingEntry(string SqlQueryName, DateTime start, double duration, Dictionary<string, Object> SqlQueryParams, int error = 0, string OriginName = null)
        {
            try
            {
                if (!LogSql || SqlLogFileName == null)
                    return;

                var strSqlParams = "";
                if (SqlQueryParams != null)
                {
                    foreach (var _entry in SqlQueryParams)
                    {
                        if (!string.IsNullOrEmpty(strSqlParams))
                            strSqlParams += "|";
                        strSqlParams += _entry.Key + "=" + _entry.Value?.ToString() ?? "";
                    }
                }

                SaveSqlTimingEntry(SqlQueryName, start, duration, strSqlParams, error, OriginName);
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        public static void SaveSqlTimingEntry(string SqlQueryName, DateTime timeStart, Dictionary<string, Object> SqlQueryParams, int error = 0, string OriginName = null)
        {
            try
            {
                if (!LogSql || SqlLogFileName == null)
                    return;

                var end = DateTime.UtcNow;
                var duration = (end - timeStart).TotalMilliseconds;

                SaveSqlTimingEntry(SqlQueryName, timeStart, duration, SqlQueryParams, error, OriginName);
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        public static bool LoadWellKnownSqlStatements(out List<cXmlParserNodeMessage> ParsingMessages, Assembly ExecutingAssembly = null)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            ParsingMessages = new List<cXmlParserNodeMessage>();

            cXmlParser Parser = null;

            try
            {
                wellDefinedSqlStatements.Clear();

                var Locations = new List<cXmlFileLocation>() {
                    new cXmlFileLocation() { LocationType = cXmlFileLocation.enumXmlLocationType.ProgramFolder, SubFolder = "", Location = constWellDefinedSqlFile + ".xml", ExecutingAssembly = ExecutingAssembly },
                    new cXmlFileLocation() { LocationType = cXmlFileLocation.enumXmlLocationType.EmbeddedResource, SubFolder = "", Location = constWellDefinedSqlFile + ".xml", ExecutingAssembly = ExecutingAssembly }
                };


                var XmlRoot = cXmlParser.OpenXmlDocument(Locations, out Parser, constWellDefinedSqlFile);

                if (XmlRoot == null || Parser == null || !Parser.IsValid)
                    return false;

                var XQueries = XmlRoot.SelectSingleNode("Queries");
                if (XQueries != null)
                {
                    Parser.EnterElement("Queries");

                    var XList = XQueries.SelectNodes("Query");
                    if (XList != null && XList.Count > 0)
                    {
                        Parser.EnterElement("Query");
                        foreach (var Entry in XList)
                        {
                            if (!(Entry is XmlElement XQuery))
                                continue;

                            var Q = new cDbQuery
                            {
                                Name = cXmlParser.GetStringFromXmlAttribute(XQuery, "Name"),
                                Query = cXmlParser.GetInnerTextFromXmlElement(XQuery),
                                TimeoutFactor = cXmlParser.GetIntegerFromXmlAttribute(XQuery, "TimeoutFactor", 1),
                                Subsequent = cXmlParser.GetIntegerFromXmlAttribute(XQuery, "Subsequent", 0),
                                Description = cXmlParser.GetStringFromXmlAttribute(XQuery, "Description", "")
                            };

                            if (string.IsNullOrWhiteSpace(Q.Name))
                            {
                                Parser.AddInvalidName(XQuery);
                                continue;
                            }
                            else if (string.IsNullOrWhiteSpace(Q.Query))
                            {
                                Parser.AddMessage(XQuery, $"No SQL statement in element <query Name=\"{Q.Name}\">");
                                continue;
                            }
                            else if (wellDefinedSqlStatements.ContainsKey(Q.Name))
                            {
                                Parser.AddDuplicateName(XQuery, Q.Name);
                                continue;
                            }

                            wellDefinedSqlStatements.Add(Q.Name, Q);

                            Parser.SelectElementNext();
                        }
                        Parser.LeaveElement("Query");
                    }

                    Parser.LeaveElement("Queries");
                }

                if (Parser.LogMessages.Count == 0)
                    return true;

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (Parser != null)
                    ParsingMessages = Parser.LogMessages;
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public static bool GetWellKnownSqlStatement(string Name, out cDbQuery Query)
        {
            if (wellDefinedSqlStatements.TryGetValue(Name, out var RetVal))
            {
                Query = RetVal;
                return true;
            }

            Query = null;
            return false;
        }

        private static void GetFormatters(bool formatted, out string strCF, out string strTab)
        {
            strCF = " ";
            strTab = "";
            if (formatted)
            {
                strCF = "\n";
                strTab = "\t";
            }
        }

        public static string GetSqlCreateStatement(cDataHistoryConfigTable Table, bool UseBinValuesAsPrimaryKey, bool formatted, bool Full = false)
        {
            GetFormatters(formatted, out var strCF, out var strTab);

            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var strColumns = "[ScanId] [uniqueidentifier] NOT NULL";
                if (Table.Type == eDataHistoryTableType.Events)
                    strColumns += ", [EventId] [uniqueidentifier] NOT NULL";

                foreach (var Entry in Table.Columns.Values)
                {
                    if (Entry is cDataHistoryConfigColumnComputation)
                        continue;
                    if (strColumns != "")
                        strColumns += "," + strCF;
                    strColumns += strTab;

                    var strNull = "NULL";
                    if (!Table.IsNullable(Entry))
                        strNull = "NOT " + strNull;

                    strColumns += string.Format("[{0}] {1} {2}", Entry.Name, Entry.SqlType, strNull);

                    if (Entry.SqlTypeBin != null)
                    {
                        strColumns += "," + strCF + strTab + string.Format("[{0}{1}] {2} {3}", Entry.Name, constBinValueNameAdd, Entry.SqlTypeBin, strNull);
                    }
                }

                var strSql = string.Format("CREATE TABLE [{0}] (", Table.SourceName) + strCF + strColumns + strCF + GetSqlPrimaryKeyStatement(Table, UseBinValuesAsPrimaryKey, formatted) + ");";


                strSql += strCF + strCF + $"ALTER TABLE [{Table.SourceName}] ADD  CONSTRAINT [DF_{Table.SourceName}_ScanId]  DEFAULT ('00000000-0000-0000-0000-000000000000') FOR [ScanId];";
                if (Table.Type == eDataHistoryTableType.Events)
                    strSql += strCF + strCF + $"ALTER TABLE [{Table.SourceName}] ADD  CONSTRAINT [DF_{Table.SourceName}_eventId]  DEFAULT (newid()) FOR [EventId];";

                foreach (var Index in Table.Indexes.Values)
                    strSql += strCF + strCF + GetSqlIndexStatement(Index, Table, formatted);

                if (Full)
                {
                    if (Table.Type == eDataHistoryTableType.Events || Table.Type == eDataHistoryTableType.History || Table.Type == eDataHistoryTableType.HistoryEvents)
                        strSql += strCF + $"ALTER TABLE [{Table.SourceName}]  WITH CHECK ADD  CONSTRAINT [FK_{Table.SourceName}_scan-history] FOREIGN KEY([ScanId]) REFERENCES [main-scan-history] ([ScanId]) ON DELETE CASCADE;";

                    foreach (var Ref in Table.References.Values)
                        strSql += strCF + GetReferencingStatements(Table, Ref, formatted);
                }

                return strSql;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        private static string GetSqlPrimaryKeyStatement(cDataHistoryConfigTable Table, bool UseBinValuesAsPrimaryKey, bool formatted)
        {
            try
            {
                GetFormatters(formatted, out var strCF, out var strTab);

                var strSql = "";

                foreach (var Col in Table.KeyColumns)
                {
                    if (strSql != "")
                        strSql += "," + strCF;
                    strSql += strTab;

                    var strColName = Col.Name;
                    if (UseBinValuesAsPrimaryKey && Col.SqlTypeBin != null)
                        strColName += constBinValueNameAdd;

                    strSql += string.Format("[{0}] ASC", strColName);
                }
                if (Table.Type == eDataHistoryTableType.History)
                    strSql += "," + strCF + "[ScanId]";
                else if (Table.Type == eDataHistoryTableType.Events)
                {
                    strSql += "," + strCF + $"[{Table.EventTimeColumn.Name}]";
                    strSql += "," + strCF + "[EventId]";
                }
                else if (Table.Type == eDataHistoryTableType.HistoryEvents)
                {
                    strSql += "," + strCF + "[ScanId]";
                    strSql += "," + strCF + $"[{Table.EventTimeColumn.Name}]";
                }

                strSql = $"CONSTRAINT [PK_{Table.SourceName}]" + strCF + "PRIMARY KEY CLUSTERED (" + strCF + strSql + ")";

                return strSql;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return "";
        }

        private static string GetSqlIndexStatement(cDataHistoryConfigTableIndex Index, cDataHistoryConfigTable Table, bool formatted = false)
        {
            try
            {
                GetFormatters(formatted, out var strCF, out var strTab);

                var strCols = "";
                foreach (var Entry in Index.Columns)
                {
                    if (strCols != "")
                        strCols += "," + strCF;
                    strCols += strTab + "[" + Entry.Name + "] ASC";
                }

                var strUnique = "";
                if (Index.Unique)
                    strUnique = " UNIQUE";

                var strSql = $"CREATE{strUnique} INDEX [{Index.Name}]{strCF}ON [{Table.SourceName}] ({strCF}{strCols})";

                return strSql;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return "";
        }

        public static string GetReferencingName(cDataHistoryConfigTable Table, cDataHistoryConfigTableReferenceInfo RefInfo)
        {
            return string.Format("FK_{0}_{1}", Table.SourceName, RefInfo.ForeignTable.SourceName);
        }

        public static string GetReferencingStatements(cDataHistoryConfigTable Table, cDataHistoryConfigTableReferenceInfo Ref, bool formatted = false)
        {
            try
            {
                GetFormatters(formatted, out var strCF, out var strTab);

                var Result = "";
                Result += strCF;
                var strTable = Table.SourceName;
                var strTableRef = Ref.ForeignTableName;
                var strName = GetReferencingName(Table, Ref);

                var strCols = "";
                var strColsRef = "";
                var ColsForeign = Ref.ReferencedColumns.GetEnumerator();
                foreach (var Col in Ref.Columns)
                {
                    if (!ColsForeign.MoveNext())
                        return "";

                    var ColRef = ColsForeign.Current;
                    var strBin = "";
                    if (Col.SqlTypeBin != null && ColRef.SqlTypeBin != null)
                        strBin = "_bin";

                    if (strCols != "")
                        strCols += ", ";
                    strCols += strCF + strTab + "[" + Col.Name + strBin + "]";

                    if (strColsRef != "")
                        strColsRef += ", ";
                    strColsRef += strCF + strTab + "[" + ColRef.Name + strBin + "]";
                }

                var Line = string.Format("ALTER TABLE [{0}]{5}WITH CHECK ADD CONSTRAINT [{1}]{5}FOREIGN KEY ({2}){5}REFERENCES [{3}] ({4})", strTable, strName, strColsRef, strTableRef, strCols, strCF);
                Result += Line;

                return Result;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return "";
        }

        internal static object[] _GetSqlValuesVersion(object val)
        {
            try
            {
                if (val == null)
                    return new object[2] { null, null };

                Version v = null;
                if (val is Version _ver)
                    v = _ver;
                else if (val is byte[] _arr)
                {
                    if (_arr.Length <= 8 && ((_arr.Length & 1) == 0))
                    {
                        var strVersion = "";
                        for (int i = 0; i < _arr.Length; i += 2)
                        {
                            if (strVersion != "")
                                strVersion += '.';
                            strVersion += ((int)_arr[i] + ((int)_arr[i + 1]) >> 8).ToString();
                        }
                        return new object[2] { strVersion, _arr };
                    }
                }
                else if (Version.TryParse(val.ToString(), out var _ver2))
                    v = _ver2;

                if (v != null)
                {
                    byte[] _arr = null;
                    if (v.Major < 0)
                        return null;
                    else if (v.Minor < 0)
                        _arr = new byte[2] { (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) };
                    else if (v.Build < 0)
                        _arr = new byte[4] { (byte)(v.Minor & 0xFF), (byte)((v.Minor & 0xFF00) << 8), (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) };
                    else if (v.Revision < 0)
                        _arr = new byte[6] { (byte)(v.Build & 0xFF), (byte)((v.Build & 0xFF00) << 8), (byte)(v.Minor & 0xFF), (byte)((v.Minor & 0xFF00) << 8), (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) };
                    else
                        _arr = new byte[8] { (byte)(v.Revision & 0xFF), (byte)((v.Revision & 0xFF00) << 8), (byte)(v.Build & 0xFF), (byte)((v.Build & 0xFF00) << 8), (byte)(v.Minor & 0xFF), (byte)((v.Minor & 0xFF00) << 8), (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) };

                    return new object[2] { v.ToString(), _arr };
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[2] { null, null }; ;
        }


        internal static object[] _GetSqlValuesMD5(object val)
        {
            try
            {
                if (val == null)
                    return new object[2] { null, null };

                if (val is byte[] _arr)
                {
                    if (_arr.Length == 16)
                    {
                        StringBuilder result = new StringBuilder(_arr.Length * 2);

                        for (int i = 0; i < _arr.Length; i++)
                            result.Append(_arr[i].ToString("X2"));

                        return new object[2] { result, _arr };
                    }

                    return null;
                }

                var strMD5 = val.ToString();
                var _arr2 = new byte[16];
                if (strMD5.Length == 32)
                {
                    for (int i = 0; i < 16; i++)
                    {
                        var strHex = strMD5.Substring(i * 2, 2);
                        _arr2[i] = Convert.ToByte(strHex, 16);
                    }

                    return new object[2] { strMD5, _arr2 };

                }



            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[2] { null, null }; ;
        }


        internal static object[] _GetSqlValuesSID(object val)
        {
            try
            {
                if (val == null)
                    return new object[2] { null, null };

                if (val is byte[] arrByte)
                {
                    var sid = new SecurityIdentifier(arrByte, 0);
                    var strSid = sid.ToString();
                    return new object[2] { strSid, arrByte };
                }

                var sid2 = new SecurityIdentifier(val.ToString());
                var _arr = new byte[sid2.BinaryLength];
                sid2.GetBinaryForm(_arr, 0);
                return new object[2] { sid2.ToString(), _arr };
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[2] { null, null }; ;
        }

        internal static object[] _GetSqlValuesIP4(object val)
        {
            try
            {
                if (val == null)
                    return new object[2] { null, null };

                if (val is byte[] arrByte && arrByte.Length == 4)
                {
                    var _ip = new IPAddress(arrByte);
                    var strIP = _ip.ToString();
                    return new object[2] { strIP, arrByte };
                }

                var strIp = val.ToString();
                if (IPAddress.TryParse(strIp, out var _ip2))
                {
                    if (_ip2.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        var _arr = _ip2.GetAddressBytes();
                        return new object[2] { _ip2.ToString(), _arr };
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[2] { null, null }; ;
        }

        internal static object[] _GetSqlValuesIP6(object val)
        {
            try
            {
                if (val == null)
                    return new object[2] { null, null };

                if (val is byte[] arrByte)
                {
                    var _ip = new IPAddress(arrByte);
                    if (_ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
                    {
                        var strIP = _ip.ToString();
                        return new object[2] { strIP, arrByte };
                    }
                }

                var strIp = val.ToString();
                if (IPAddress.TryParse(strIp, out var _ip2))
                {
                    if (_ip2.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        var _arr = _ip2.GetAddressBytes();
                        return new object[2] { _ip2.ToString(), _arr };
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[2] { null, null };
        }

        internal static object[] _GetSqlValuesString(object val, int Cardinal)
        {
            try
            {
                var s1 = val.ToString();
                if (Cardinal >= 0 && s1.Length > Cardinal)
                    s1 = s1.Substring(0, Cardinal);
                return new object[1] { s1 };
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[1] { null };
        }

        internal static object[] _GetSqlValuesGuid(object val)
        {
            try
            {
                if (val is Guid _guid)
                    return new object[1] { _guid };

                if (Guid.TryParse(val.ToString(), out var _guid2))
                    return new object[1] { _guid2 };
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[1] { null };
        }

        internal static object[] _GetSqlValuesInt(object val)
        {
            try
            {
                if (val is Int32 _int)
                    return new object[1] { _int };

                var _int2 = Convert.ToInt32(val);
                return new object[1] { _int2 };
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[1] { null };
        }

        internal static object[] _GetSqlValuesBigInt(object val)
        {
            try
            {
                if (val is Int64 _int)
                    return new object[1] { _int };

                var _int2 = Convert.ToInt64(val);
                return new object[1] { _int2 };
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[1] { null };
        }

        internal static object[] _GetSqlValuesFloat(object val)
        {
            try
            {
                if (val is float _f)
                    return new object[1] { _f };

                var _f2 = Convert.ToSingle(val);
                return new object[1] { _f2 };
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[1] { null };
        }

        internal static object[] _GetSqlValuesDateTime(object val)
        {
            try
            {
                if (val is DateTime _dt)
                {
                    if (_dt.Kind != DateTimeKind.Utc)
                        _dt = DateTime.SpecifyKind(_dt, DateTimeKind.Utc);
                    return new object[1] { _dt };
                }

                if (DateTime.TryParse(val.ToString(), CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal, out var _dt2))
                {
                    if (_dt2.Kind != DateTimeKind.Utc)
                        _dt2 = DateTime.SpecifyKind(_dt2, DateTimeKind.Utc);
                    return new object[1] { _dt2 };
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return new object[1] { null };
        }

        public static object[] GetSqlDataValueFromHistoryType(object val, enumFasdValueType type, int Cardinal)
        {
            try
            {
                object[] retVal = null;
                switch (type)
                {
                    case enumFasdValueType.VERSION:
                        retVal = _GetSqlValuesVersion(val);
                        break;
                    case enumFasdValueType.MD5:
                        retVal = _GetSqlValuesMD5(val);
                        break;
                    case enumFasdValueType.SID:
                        retVal = _GetSqlValuesSID(val);
                        break;
                    case enumFasdValueType.IPV4:
                        retVal = _GetSqlValuesIP4(val);
                        break;
                    case enumFasdValueType.IPV6:
                        retVal = _GetSqlValuesIP6(val);
                        break;
                }
                if (retVal != null)
                {
                    if (retVal.Length > 1 && retVal[1] == null)
                        retVal[1] = System.Data.SqlTypes.SqlBinary.Null;
                    return retVal;
                }

                if (val == null)
                    return new object[1] { null };
                if (type != enumFasdValueType.STRING && val is string str)
                    if (str == "")
                        return null;

                switch (type)
                {
                    case enumFasdValueType.STRING:
                        return _GetSqlValuesString(val, Cardinal);
                    case enumFasdValueType.GUID:
                        return _GetSqlValuesGuid(val);
                    case enumFasdValueType.INT:
                        return _GetSqlValuesInt(val);
                    case enumFasdValueType.BIGINT:
                        return _GetSqlValuesBigInt(val);
                    case enumFasdValueType.FLOAT:
                        return _GetSqlValuesFloat(val);
                    case enumFasdValueType.DATETIME:
                        return _GetSqlValuesDateTime(val);

                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        public static string CreateConnectionString(cDataHistoryConfigSqlConnection Connection, int Timeout = -1, bool MARS = false, bool WithoutDatabase = false)
        {
            try
            {
                var _connBuilder = new SqlConnectionStringBuilder()
                {
                    ApplicationName = ApplicationName,
                    WorkstationID = WorkstationID,
                    MultipleActiveResultSets = MARS,
                    IntegratedSecurity = !Connection.NativeAccount
                };

                if (Timeout >= 0)
                    _connBuilder.ConnectTimeout = Timeout;
                else if (Connection.Timeout >= 0)
                    _connBuilder.ConnectTimeout = Connection.Timeout;
                else
                    _connBuilder.ConnectTimeout = constDefaultSqlTimeout;

                var _DataSource = Connection.Server;
                if (!string.IsNullOrWhiteSpace(Connection.Instance))
                    _DataSource += @"\" + Connection.Instance;
                _connBuilder.DataSource = _DataSource;

                if (!WithoutDatabase)
                    _connBuilder.InitialCatalog = Connection.Database;

                var RetVal = _connBuilder.ConnectionString;
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        public static SqlConnection OpenConnection(cDataHistoryConfigSqlConnection Connection, int Timeout = -1, bool MARS = false, bool WithoutDatabase = false, bool LogError = true)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                lastSqlConnectionError = 0;

                SqlConnection _conn = null;
                var _cs = CreateConnectionString(Connection, Timeout, MARS, WithoutDatabase);
                try
                {
                    if (Connection.NativeAccount)
                    {
                        Connection.Credential.Password.MakeReadOnly();
                        _conn = new SqlConnection(_cs, new SqlCredential(Connection.Credential.User, Connection.Credential.Password));
                        _conn.Open();
                    }
                    else
                    {
                        WindowsIdentity.RunImpersonated(Connection.Credential.SafeAccessToken, () =>
                        {
                            try
                            {
                                if (System.Diagnostics.Debugger.IsAttached) // when running locally, the sql server does not have a certificate.
                                    _cs += ";TrustServerCertificate=True";

                                _conn = new SqlConnection(_cs);
                                _conn.Open();
                            }
                            catch (Exception ex)
                            {
                                LogException(ex);
                            }
                        });
                    }
                }
#if NETFRAMEWORK
                catch (SqlException E)
                {
                    lastSqlConnectionError = E.Number;
                    if (LogError)
                        LogEntry(string.Format("error opening sql connection '{0}': {1}, {2}", _cs, E.Number, E.Message), LogLevels.Error);
                    throw;
                }
#endif
                catch
                {
                    if (LogError)
                        LogEntry(string.Format("error opening sql connection: {0}", _cs), LogLevels.Error);
                    throw;
                }

                if (_conn == null || _conn.State != System.Data.ConnectionState.Open)
                    LogEntry(string.Format("error opening sql connection: {0}", _cs), LogLevels.Warning);

                return _conn;
            }
            catch (Exception E)
            {
                if (LogError)
                    LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public static async Task<int?> ExecuteAsync(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> Parameters, cF4sdWebRequestInfo requestInfo, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false, int Timeout = 0)
        {
            var _startTime = DateTime.UtcNow;

            if (Timeout == 0)
                Timeout = Connection.Connector.Timeout * Query.TimeoutFactor;

            var res = await ExecuteAsync(Connection.Connection, Query.Query, Parameters, Token, Timeout, Transaction, LogNoResult, LogSilent);

            if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Parameters, 0, requestInfo?.requestName);

            return res;
        }

        public static async Task<int?> ExecuteAsync(DbConnection Connection, string Query, Dictionary<string, Object> Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false)
        {
            MethodBase CM = null;
            if (!LogSilent)
            {
                CM = MethodBase.GetCurrentMethod();
                LogMethodBegin(CM);
            }

            int? RetVal = null;

            try
            {
                using (DbCommand DbCmd = Connection.CreateCommand())
                {
                    if (Timeout > 0)
                        DbCmd.CommandTimeout = Timeout;

                    DbCmd.CommandText = Query;
                    DbCmd.Transaction = Transaction;
                    if (Parameters != null)
                    {
                        foreach (var Entry in Parameters)
                        {
                            DbParameter Param = DbCmd.CreateParameter();
                            Param.ParameterName = Entry.Key;
                            if (Entry.Value == null)
                                Param.Value = DBNull.Value;
                            else
                                Param.Value = Entry.Value;
                            DbCmd.Parameters.Add(Param);
                        }
                    }
                    RetVal = await DbCmd.ExecuteNonQueryAsync(Token);
                }
            }
            catch (Exception E)
            {
                LogException(E);
                LogSqlInfo("ExecuteAsync", Query, Parameters);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }

        public static async Task<DbDataReader> GetDataReaderAsync(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false)
        {
            return await GetDataReaderAsync(Connection.Connection, Query.Query, Parameters, Token, Connection.Connector.Timeout * Query.TimeoutFactor, Transaction, LogNoResult);
        }

        public static async Task<DbDataReader> GetDataReaderAsync(DbConnection Connection, string Query, Dictionary<string, Object> Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false)
        {

            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                using (DbCommand DbCmd = Connection.CreateCommand())
                {
                    if (Timeout < 0)
                        DbCmd.CommandTimeout = Timeout;

                    DbCmd.CommandText = Query;
                    DbCmd.Transaction = Transaction;
                    if (Parameters != null)
                    {
                        foreach (var Entry in Parameters)
                        {
                            DbParameter Param = DbCmd.CreateParameter();
                            Param.ParameterName = Entry.Key;
                            Param.Value = Entry.Value;
                            DbCmd.Parameters.Add(Param);
                        }
                    }

                    var RetVal = await DbCmd.ExecuteReaderAsync(Token);
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                LogException(E);
                LogSqlInfo("ExecuteAsync", Query, Parameters);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public static async Task<T> GetScalarResultAsync<T>(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> Parameters, cF4sdWebRequestInfo requestInfo, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false)
        {
            var _startTime = DateTime.UtcNow;

            var res = await GetScalarResultAsync<T>(Connection.Connection, Query.Query, Parameters, Token, Connection.Connector.Timeout * Query.TimeoutFactor, Transaction, LogNoResult);

            if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Parameters, 0, requestInfo?.requestName);

            return res;
        }

        public static async Task<T> GetScalarResultAsync<T>(DbConnection Connection, string Query, Dictionary<string, Object> Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false)
        {

            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                using (DbCommand DbCmd = Connection.CreateCommand())
                {
                    if (Timeout < 0)
                        DbCmd.CommandTimeout = Timeout;

                    DbCmd.CommandText = Query;
                    DbCmd.Transaction = Transaction;
                    if (Parameters != null)
                    {
                        foreach (var Entry in Parameters)
                        {
                            DbParameter Param = DbCmd.CreateParameter();
                            Param.ParameterName = Entry.Key;
                            Param.Value = Entry.Value;
                            DbCmd.Parameters.Add(Param);
                        }
                    }
                    var oRetVal = await DbCmd.ExecuteScalarAsync(Token);
                    if (LogNoResult && (oRetVal == null || oRetVal is DBNull))
                    {
                        LogEntry("No result (NULL) on SQL execute", LogLevels.Error);
                        LogSqlInfo("GetScalarResultAsync", Query, Parameters);
                    }
                    if (oRetVal is T RetVal)
                        return RetVal;
                }
            }
            catch (Exception E)
            {
                LogException(E);
                LogSqlInfo("GetScalarResultAsync", Query, Parameters);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return default;
        }

        public static async Task<DbDataReader> GetTableResultAsync(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null)
        {
            return await GetTableResultAsync(Connection.Connection, Query.Query, Parameters, Token, Connection.Connector.Timeout * Query.TimeoutFactor, Transaction);
        }

        public static async Task<DbDataReader> GetTableResultAsync(DbConnection Connection, string Query, Dictionary<string, Object> Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool SupportCancellation = false)
        {
            DbDataReader RetVal = null;

            try
            {
                using (DbCommand DbCmd = Connection.CreateCommand())
                {
                    if (Timeout != 0)
                        DbCmd.CommandTimeout = Timeout;

                    DbCmd.CommandText = Query;
                    DbCmd.Transaction = Transaction;
                    if (Parameters != null)
                    {
                        foreach (var Entry in Parameters)
                        {
                            DbParameter Param = DbCmd.CreateParameter();
                            Param.ParameterName = Entry.Key;
                            Param.Value = Entry.Value;
                            DbCmd.Parameters.Add(Param);
                        }
                    }

                    try
                    {
                        RetVal = await DbCmd.ExecuteReaderAsync(Token);
                    }
                    catch
                    {
                        if (!Token.IsCancellationRequested || !SupportCancellation)
                            throw;
                        else
                        {
                            DbCmd.Cancel();
                            return null;
                        }
                    }

                    if (RetVal == null)
                    {
                        LogEntry("No result (NULL) on SQL execute", LogLevels.Error);
                        LogSqlInfo("GetTableResult", Query, Parameters);
                    }
                }
            }
            catch (Exception E)
            {
                if (E is TaskCanceledException)
                {

                }
                cLogManager.DefaultLogger.LogException(E);
                LogSqlInfo("GetTableResult", Query, Parameters);
                RetVal = null;
            }

            return RetVal;
        }

        public static async Task<bool> CheckIfTableExistAsync(DbConnection Connection, string TableName, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
            var _startTime = DateTime.UtcNow;

            try
            {
                var Query = "SELECT COUNT(OBJECT_ID(@TableName, N'U'))";
                var Params = new Dictionary<string, Object>() {
                    { "TableName", TableName }
                };

                var Res = await GetScalarResultAsync<int>(Connection, Query, Params, CancellationToken.None, LogNoResult: true);
                if (Res >= 1)
                    return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool CheckDbConnection(DbConnection Connection)
        {
            return (Connection != null && Connection.State == System.Data.ConnectionState.Open);
        }

        public static void LogSqlInfo(string Func, string Query, Dictionary<string, Object> Parameters)
        {
            var Msg = string.Format("DataHistorySqlHelper.{0}: Query={1}", Func, Query);
            if (Parameters != null)
            {
                var lstMsg = new List<string>
                {
                    Msg,
                    "Parameters:"
                };
                foreach (var Entry in Parameters)
                    lstMsg.Add(Entry.Key + "=" + Entry.Value?.ToString());
                cLogManager.DefaultLogger.LogList(LogLevels.Warning, lstMsg);
            }
            else
                cLogManager.DefaultLogger.LogEntry(LogLevels.Warning, Msg);
        }

        public static async Task<Dictionary<string, cSqlReferenceInfo>> GetTableReferences(DbConnection Connection, string TableName, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var Query = "SELECT fk.name name, tb.name tableName, cp.name tableColumn, tr.name referencedTable, cr.name referencedColumn  FROM sys.foreign_keys fk";
                Query += " INNER JOIN sys.tables tb ON tb.object_id = fk.parent_object_id";
                Query += " INNER JOIN sys.tables tr ON tr.object_id = fk.referenced_object_id";
                Query += " INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id";
                Query += " INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id";
                Query += " INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id";
                Query += " WHERE tb.name = @TableName";

                var Params = new Dictionary<string, object>()
                {
                    { "TableName", TableName}
                };

                var _res = new Dictionary<string, cSqlReferenceInfo>();
                using (var _reader = await GetDataReaderAsync(Connection, Query, Params, Token, LogNoResult: true))
                {
                    if (_reader.HasRows)
                    {
                        while (await _reader.ReadAsync())
                        {
                            try
                            {
                                var _RefName = _reader.GetString(0);
                                if (!_res.TryGetValue(_RefName, out var _if))
                                {
                                    _if = new cSqlReferenceInfo()
                                    {
                                        Table = _reader.GetString(1),
                                        ReferencedTable = _reader.GetString(3),
                                        Name = _RefName
                                    };
                                    _res.Add(_RefName, _if);
                                    _if.Columns.Add(_reader.GetString(2));
                                    _if.ReferencedColumns.Add(_reader.GetString(4));
                                }
                            }
                            catch (Exception E)
                            {
                                LogException(E);
                            }
                            finally
                            {
                            }
                        }
                    }
                }

                return _res;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public static async Task<DbDataReader> getDbReaderHistoricTable(cDbConnection Conn, string Table, Dictionary<string, object> Filter, DateTime LastEventTime, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var strFilter = "";
                int i = 0;
                var Params = new Dictionary<string, object>();
                foreach (var Entry in Filter)
                {
                    i++;
                    if (strFilter != "")
                        strFilter += " AND ";
                    strFilter += "t.[" + Entry.Key + "] = @P" + i.ToString();
                    Params.Add("P" + i.ToString(), Entry.Value);
                }
                Params.Add("LASTTIME", LastEventTime);

                var Cmd = $"SELECT h.[TimeFrameFrom], h.[TimeFrameTo], t.* FROM [{Table}] t JOIN [main-scan-history] h ON h.[ScanId] = t.[ScanId] WHERE {strFilter} AND h.[State] = 1 AND h.[TimeFrameFrom] >= @LASTTIME ORDER BY h.[TimeFrameTo] DESC";

                var _reader = await GetDataReaderAsync(Conn.Connection, Cmd, Params, Token);
                return _reader;

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public static async Task<DbDataReader> getDbReaderSelectAsync(cDbConnection Conn, string Table, IEnumerable<string> Columns, Dictionary<string, object> Filter, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var strFields = "";
                foreach (var strField in Columns)
                {
                    if (strFields != "")
                        strFields += ",";
                    strFields += "[" + strField + "]";
                }

                var strFilter = "";
                int i = 0;
                var Params = new Dictionary<string, object>();
                foreach (var Entry in Filter)
                {
                    i++;
                    if (strFilter != "")
                        strFilter += " AND ";
                    strFilter += "[" + Entry.Key + "] = @P" + i.ToString();
                    Params.Add("P" + i.ToString(), Entry.Value);
                }
                if (strFilter != "")
                    strFilter = " WHERE " + strFilter;

                var Cmd = $"SELECT {strFields} FROM [{Table}]{strFilter}";

                var Reader = await GetDataReaderAsync(Conn.Connection, Cmd, Params, Token, Timeout, Transaction);
                return Reader;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public static async Task<DbDataReader> getDbReaderDetailTable(cDbConnection Conn, string Table, Dictionary<string, object> Filter, string rowTime, int Age, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var strFilter = "";
                int i = 0;
                var Params = new Dictionary<string, object>();
                foreach (var Entry in Filter)
                {
                    i++;
                    if (strFilter != "")
                        strFilter += " AND ";
                    strFilter += "[" + Entry.Key + "] = @P" + i.ToString();
                    Params.Add("P" + i.ToString(), Entry.Value);
                }
                Params.Add("AGE", Age);

                var Cmd = $"SELECT * FROM [{Table}] WHERE {strFilter} AND [{rowTime}] >= DATEADD(DAY,-@Age, GETUTCDATE()) ORDER BY [{rowTime}] DESC";

                var _reader = await GetDataReaderAsync(Conn.Connection, Cmd, Params, Token);
                return _reader;

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public static async Task<bool> DoUpdateOrInsertAsync(cDbConnection Connection, string Table, Dictionary<string, object> Columns, List<string> IDs, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
            var _startTime = DateTime.UtcNow;

            try
            {
                string strSet = "";
                string strWhere = "";
                string strInsertCol = "";
                string strInstertVal = "";
                var SqlParams = new Dictionary<string, object>();

                int PNr = 0;
                foreach (var Entry in Columns)
                {
                    PNr++;
                    var PName = "P" + PNr.ToString();
                    var V = Entry.Value;
                    if (V == null)
                        V = DBNull.Value;
                    SqlParams.Add(PName, V);

                    if (strInsertCol != "")
                        strInsertCol += ", ";
                    strInsertCol += '[' + Entry.Key + ']';

                    if (strInstertVal != "")
                        strInstertVal += ", ";
                    strInstertVal += "@" + PName;

                    if (IDs.Contains(Entry.Key))
                    {
                        if (strWhere != "")
                            strWhere += " AND ";

                        strWhere += '[' + Entry.Key + "]=@" + PName;
                    }
                    else
                    {
                        if (strSet != "")
                            strSet += ", ";
                        strSet += '[' + Entry.Key + "]=@" + PName;
                    }
                }

                var Query = $"UPDATE [{Table}] SET {strSet} WHERE {strWhere}\n";
                Query += $"IF @@ROWCOUNT = 0 INSERT INTO [{Table}] ({strInsertCol}) VALUES ({strInstertVal})";
                var n = await ExecuteAsync(Connection.Connection, Query, SqlParams, Token, Connection.Connector.Timeout * 10, LogSilent: LogSilent);
                if (n > 0)
                    return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("DoUpdateOrInsertAsync", _startTime, Table, 0, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public static async Task<bool> DoInsertAsync(cDbConnection Connection, string Table, Dictionary<string, object> Columns, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
            var _startTime = DateTime.UtcNow;

            try
            {
                string strInsertCol = "";
                string strInstertVal = "";
                var SqlParams = new Dictionary<string, object>();

                int PNr = 0;
                foreach (var Entry in Columns)
                {
                    PNr++;
                    var PName = "P" + PNr.ToString();
                    var V = Entry.Value;
                    if (V == null)
                        V = DBNull.Value;
                    SqlParams.Add(PName, V);

                    if (strInsertCol != "")
                        strInsertCol += ", ";
                    strInsertCol += '[' + Entry.Key + ']';

                    if (strInstertVal != "")
                        strInstertVal += ", ";
                    strInstertVal += "@" + PName;

                }

                var Query = $"INSERT INTO [{Table}] ({strInsertCol}) VALUES ({strInstertVal})";
                var n = await ExecuteAsync(Connection.Connection, Query, SqlParams, Token, Connection.Connector.Timeout * 10, LogSilent: LogSilent);
                if (n > 0)
                    return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("DoInsertAsync", _startTime, Table, 0, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public static DateTime ConvertDBDateTimeToUTC(DateTime d)
        {
            return DateTime.SpecifyKind(d, DateTimeKind.Utc);
        }

        public static string GetSqlLogFileName()
        {
            try
            {
                if (!(cLogManager.Instance is cLogManagerFile lf))
                    return null;

                if (string.IsNullOrEmpty(lf.LogFolder))
                    return null;

                var _fn = Path.GetFileNameWithoutExtension(lf.GetLogFileName());
                return Path.Combine(lf.LogFolder, _fn + "-SqlTiming.log");
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

    }

    public class cSqlReferenceInfo
    {
        public string Table;
        public string ReferencedTable;
        public string Name;
        public List<string> Columns = new List<string>();
        public List<string> ReferencedColumns = new List<string>();
    }

    public class cDbQuery
    {
        public string Query = "";
        public string Name = "";
        public int TimeoutFactor = 1;
        public int Subsequent = 0;
        public string Description = "";

        public cDbQuery Clone(string _query = null)
        {
            var me = new cDbQuery()
            {
                Query = Query,
                Name = Name,
                TimeoutFactor = TimeoutFactor,
                Subsequent = Subsequent,
                Description = Description
            };
            if (_query != null)
                me.Query = _query;
            return me;
        }
    }

    public class cDbConnection : IDisposable
    {
        public DbConnection Connection { get; private set; } = null;
        public cDataHistoryConfigSqlConnection Connector { get; private set; } = null;
        public bool IsOpen { get; private set; } = false;

        public cDbConnection(cDataHistoryConfigSqlConnection Connector, int? Timeout = null, bool MARS = false, bool WithoutDatabase = false, bool LogError = true)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                this.Connector = Connector;
                int TO = Connector.Timeout;
                if (Timeout != null)
                    TO = (int)Timeout;

                Connection = DataHistorySqlHelper.OpenConnection(Connector, TO, MARS, WithoutDatabase, LogError);
                IsOpen = DataHistorySqlHelper.CheckDbConnection(Connection);
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

        }

        public void Dispose()
        {
            if (Connection != null)
            {
                try
                {
                    if (Connection.State == ConnectionState.Open)
                        Connection.Close();
                }
                catch { }
                ;
                try
                {
                    Connection.Dispose();
                }
                catch { }
                ;
            }
        }

    }

}
