using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices.ActiveDirectory;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;

using C4IT.DataHistoryProvider.Base.Modules.Citrix;
using C4IT.FASD.Base;
using C4IT.FASD.Licensing;
using C4IT.Logging;
using C4IT.Matrix42.WebClient;
using C4IT.Security;
using C4IT.XML;

using C4IT_DataHistoryProvider_Base;
using C4IT_DataHistoryProvider_Base.DataSources;
using C4IT_DataHistoryProvider_Base.Services;

using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using static C4IT.FASD.Base.cF4sdQuickActionRevision;
using static C4IT.Logging.cLogManager;



namespace C4IT.DataHistoryProvider
{
    public delegate void DataHistoryUiMessageFunc(cDataHistoryUiStatusMessage Message);

    public abstract class cDataHistoryCollectorModule
    {
        public cDataHistoryCollector Collector { get; protected set; }
        public readonly List<enumDataHistoryOrigin> Origins;

        public readonly string Name = "";

        public readonly Guid LicenseId = Guid.Empty;

        public cDataHistoryCollectorModule(cDataHistoryCollector Collector, enumDataHistoryOrigin Origin, string Name, string LicenseId)
        {
            this.Name = Name;
            Guid.TryParse(LicenseId, out this.LicenseId);
            this.Collector = Collector;
            this.Origins = new List<enumDataHistoryOrigin>() { Origin };
        }

        public cDataHistoryCollectorModule(cDataHistoryCollector Collector, List<enumDataHistoryOrigin> Origin, string Name, string LicenseId)
        {
            this.Name = Name;
            Guid.TryParse(LicenseId, out this.LicenseId);
            this.Collector = Collector;
            this.Origins = Origin;
        }

        public override string ToString()
        {
            return Name;
        }

        public abstract Task<cScanTimeInfo> GetScanTimeInfoAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token);

        public async Task<cScanTimeInfo> GetScanTimeInfoAsync(cDataHistoryScanTiming ScanTiming, string ScanName, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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;

            var LastScan = await Collector.GetLastScanTime(ScanName, requestInfo, LogDeep + 1, Token);
            if (LastScan == null || LastScan == new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc))
                return null;

            var retVal = new cScanTimeInfo((DateTime)LastScan);
            try
            {
                retVal.NextScan = retVal.GetScanTime(DateTime.UtcNow, ScanTiming);
            }
            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 retVal;
        }

        public abstract Task<bool> DoScanAsync(bool Always, bool Rescan, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool EnhancedDebug = false);

        public virtual async Task<List<object>> GetIds(cF4sdConnectorIds IdEntry, enumFasdInformationClass InfoClass, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            await Task.CompletedTask;
            if (IdEntry.sid == null)
                return null;
            return new List<object>() { IdEntry.Id };
        }

        public virtual bool UseForeignUserIds()
        {
            return false;
        }

        public virtual List<object> GetFilterValues(List<object> IDs, enumFasdInformationClass InfoClass)
        {
            return IDs;
        }

        public virtual async Task<List<string>> GetQuickActionList()
        {
            await Task.CompletedTask;
            var result = new List<string>();
            return result;
        }

        public virtual bool CheckIfLateDelivery(cDataHistoryConfigTable Table)
        {
            return false;
        }

        public void AddTableResultVirtualSuccessorTasks(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, List<Int64> UsageInfo, Guid CacheId, 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 lstTables = new List<string>();
                var myTables = new List<cDataHistoryConfigTable>();
                foreach (var Table in Tables)
                {
                    if (!this.Origins.Contains(Table.ParentCluster.Origin))
                        continue;

                    if (!CheckIfLateDelivery(Table))
                        continue;

                    myTables.Add(Table);
                    lstTables.Add(Table.Name);
                }

                if (lstTables.Count > 0)
                {
                    var ct = new CancellationTokenSource().Token;
                    var tsk = GetTableResultsVirtualAsync(myTables, Identities, RefTime, MaxAge, false, CacheId, ct, requestInfo, LogDeep + 1);
                    SuccessorCache.AddTask(CacheId, lstTables, tsk, UsageInfo, ct);
                }
            }
            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);
            }
        }

        public virtual async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetTableResultsVirtualAsync(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, bool instantly, Guid? CacheId, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            await Task.CompletedTask;
            return null;
        }

        public virtual async Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>> GetDetailsTableResultsVirtualAsync(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            await Task.CompletedTask;
            return null;
        }

        public virtual async Task<Int64> GetSelectionTableResultCountAsync(cDataHistoryConfigTable Table, List<cF4sdIdentityEntry> Identities, string search, cF4sdWebRequestInfo requestInfo, CancellationToken Token, bool resetFilter = false, List<string> filterParams = null)
        {
            await Task.CompletedTask;
            return -1;
        }

        public virtual async Task<cF4SDHealthCardRawData.cHealthCardTable> GetSelectionTableResultAsync(cDataHistoryConfigTable Table, List<cF4sdIdentityEntry> Identities, string search, int PageSize, int Page, cF4sdWebRequestInfo requestInfo, CancellationToken Token, bool resetFilter = false, List<string> filterParams = null)
        {
            await Task.CompletedTask;
            return null;
        }

        public virtual async Task<bool> ValidateMainTablesAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            await Task.CompletedTask;
            return true;
        }

        public virtual async Task<bool> ColumnUpatePostProcessingAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            await Task.CompletedTask;
            return false;
        }

        public virtual void TablePostConfig(cDataHistoryConfigTable Table)
        {

        }

        public virtual async Task<bool> WritePropertyAsync(cF4SDWriteParameters PropertiesToWrite, cF4sdWebRequestInfo requestInfo, CancellationToken Token)
        {
            await Task.CompletedTask;
            return false;
        }

        public virtual async Task<cF4SdUserInfoChange> ValidateTokenAsync(cF4SDTokenRegistration TokenRegistration, cF4sdWebRequestInfo RequestInfo, int LogDeep, CancellationToken Token)
        {
            await Task.CompletedTask;
            return null;
        }

        public virtual async Task<cF4SDAdditionalUserInfo> GetAdditionalUserInfo(enumAdditionalAuthentication Type, cF4sdWebRequestInfo RequestInfo, int LogDeep, CancellationToken Token)
        {
            await Task.CompletedTask;
            return null;
        }

        public virtual async Task<int> ValidateTokenInfo(cDataHistoryCollectorTokenCache.cTokenInfo tokenInfo)
        // 0: Token Info is correct
        // -1: Token Info is incorrect
        // 1: Token Info as to be updated due to newer infos
        {
            await Task.CompletedTask;
            return 0;
        }
    }

    public class cDataHistoryCollector : cDataHistoryCollectorModule, IConfigNodeValidation, ISearchResultRelationProvider
    {
        public const string constConnectorName = "F4SD collector";
        public const string constLicenseId = "00000000-0000-0000-0000-000000000000";

        public const int constMinutesUntilTokenIsInvalid = 5;

        public static readonly Guid ConfigId = new Guid("0FCE8005-81BD-4F06-82FF-55DD9364FBE5");
        public static readonly string JwtTokenIssuer = "https://login.f4sdcockpit.consulting4it.de";
        public static readonly string JwtTokenAudience = "urn:Cockpit.F4SD";

        public Guid getId() { return ConfigId; }


        public const string constFileNameClusterConfig = "F4SD-DataClusters-Configuration.xml";
        public const string constFileNameInfrastructureConfig = "F4SD-Infrastructure-Configuration.xml";

        public bool IsValid { get; private set; } = false;

        public cDataHistoryConfigClusters ClusterConfig { get; private set; } = null;
        public cDataHistoryConfigInfrastructure InfrastructureConfig { get; private set; } = null;

        private cDataHistoryConfigGolabalParameters GlobalParametersConfigFile = null;
        private cF4sdGlobalConfig GlobalConfig { get; set; } = null;
        private DateTime GlobalParametersConfigTimestamp = DateTime.MinValue;
        private object GlobalParametersConfigLock = new object();

        public readonly Dictionary<enumDataHistoryOrigin, cDataHistoryCollectorModule> Connectors = new Dictionary<enumDataHistoryOrigin, cDataHistoryCollectorModule>();
        public readonly cDataHistoryCollectorActiveDirectory ActiveDirectory = null;
        public readonly cDataHistoryCollectorClientAgent F4sdAgent = null;
        public cDataHistoryCollectorM42Wpm M42WpmCollector { get; private set; } = null;
        public cDataHistoryCollectorNxql NxqlCollector { get; private set; } = null;
        public cDataHistoryCollectorCitrix CitrixCollector { get; private set; } = null;
        //public cDataHistoryCollectorEmpirum EmpirumCollector { get; private set; } = null;
        public bool F4SDAnalyticsValid = false;
        public cF4SDAnalyticsConfigDatabase F4SDAnalyticsDb { get; private set; } = null;
        public event DataHistoryUiMessageFunc ProcessUiMessage;

        public readonly bool autoConnectionCheck = false;
        public Dictionary<enumFasdConfigurationType, cFasdBaseConfig> ClientConfigs = null;

        public Dictionary<string, string> GlobalConfigValues = new Dictionary<string, string>();

        public readonly JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler();
        public SymmetricSecurityKey BearerSecurityKey { get; private set; } = null;
        public TokenValidationParameters JwtTokenValidationParameters { get; private set; } = null;

        public Dictionary<Guid, DateTime> SessionValues = new Dictionary<Guid, DateTime>();
        public Dictionary<Guid, DateTime> CaseValues = new Dictionary<Guid, DateTime>();

        public cDataHistoryCollectorTokenCache TokenCache;

        public enumWebServerStatus ServerStatus = enumWebServerStatus.starting;

        private readonly List<ISearchResultRelationProvider> _searchResultRelationProviders = new List<ISearchResultRelationProvider>();

        private Dictionary<cF4SDTokenRegistration.enumTokenType, cDataHistoryCollectorModule> _tokenValidators = new Dictionary<cF4SDTokenRegistration.enumTokenType, cDataHistoryCollectorModule>();

        public bool RunningInIISExpress = false;

        #region Services

        private IStagedRelationService _stagedRelationService;

        public IStagedRelationService GetStagedRelationService()
        {
            if (_stagedRelationService is null)
                _stagedRelationService = new StagedRelationService(this, _searchResultRelationProviders);

            return _stagedRelationService;
        }

        #endregion

        internal class cDetailsComputationInfo
        {
            internal cDataHistoryConfigComputationBase Computation;
            internal int[] ValueIndexes;
        }

        public cDataHistoryConfigSqlConnection mainDbConnection { get; private set; } = null;

        public cDataHistoryCollector(bool autoConnectionCheck, cF4sdWebRequestInfo requestInfo, int LogDeep) : base(null, enumDataHistoryOrigin.Main, constConnectorName, constLicenseId)
        {
            this.Collector = this;
            this.autoConnectionCheck = autoConnectionCheck;

            TokenCache = new cDataHistoryCollectorTokenCache(this, requestInfo, LogDeep + 1);

            Connectors.Add(enumDataHistoryOrigin.Main, this);

            try
            {
                var _process = Process.GetCurrentProcess();
                if (_process?.ProcessName != null && _process.ProcessName.ToLower().StartsWith("iisexpress"))
                    RunningInIISExpress = true;
            }
            catch (Exception e1)
            {
                LogException(e1);
            }

            var _ActiveDirectory = new cDataHistoryCollectorActiveDirectory(this);
            if (cF4SDLicense.Instance?.Modules != null)
            {
                if (cF4SDLicense.Instance.Modules.ContainsKey(_ActiveDirectory.LicenseId))
                {
                    ActiveDirectory = _ActiveDirectory;
                    Connectors.Add(enumDataHistoryOrigin.ActiveDirectory, ActiveDirectory);
                }

                var _F4sdAgent = new cDataHistoryCollectorClientAgent(this);
                if (cF4SDLicense.Instance.Modules.ContainsKey(_F4sdAgent.LicenseId))
                {
                    F4sdAgent = _F4sdAgent;
                    Connectors.Add(enumDataHistoryOrigin.F4sdAgent, F4sdAgent);
                }

            }
        }

        public List<cDataHistoryCollectorModule> GetConnectors()
        {
            var _connectors = Connectors.Values.Distinct().ToList();
            return _connectors;
        }

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

            try
            {
                lock (GlobalParametersConfigLock)
                {
                    var _currentTimeStamp = DateTime.MaxValue;
                    if (GlobalParametersConfigFile != null)
                        _currentTimeStamp = File.GetLastWriteTimeUtc(GlobalParametersConfigFile.FilePath);

                    if (_currentTimeStamp <= GlobalParametersConfigTimestamp)
                    {
                        DoProcessUiMessage(0, "");
                        DoProcessUiMessage(1, "Global parameters are already loaded and are up to date...");
                        return true;
                    }

                    DoProcessUiMessage(0, "");
                    DoProcessUiMessage(1, "loading the global parameters...");

                    var _cfgGlobalParameters = new cDataHistoryConfigGolabalParameters(this.InfrastructureConfig);
                    _cfgGlobalParameters.LoadFromFile(out var PMs);

                    if (PMs != null && PMs.Count > 0)
                        DoProcessUiMessage(2, PMs);

                    if (_cfgGlobalParameters == null || !_cfgGlobalParameters.IsValid)
                    {
                        DoProcessUiMessage(1, "error on loading the global parameters.");
                        return false;
                    }

                    GlobalParametersConfigFile = _cfgGlobalParameters;

                    GlobalConfig = new cF4sdGlobalConfig(regPath: null);
                    if (GlobalParametersConfigFile.Parameters != null)
                        GlobalConfig.Load(CentralParameters: GlobalParametersConfigFile.Parameters);
                }

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

            return false;
        }

        public async Task<bool> LoadConfigurationAsync(bool checkServerEnvironment, bool checkCurrentUser, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, Assembly ExecutingAssembly = null)
        {
            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
            {
                IsValid = false;

                DoProcessUiMessage(0, "start loading the collector configuration...");

                // load the SqlHelper
                if (!DataHistorySqlHelper.LoadWellKnownSqlStatements(out var PMs, ExecutingAssembly))
                {
                    if (PMs != null && PMs.Count > 0)
                    {
                        DoProcessUiMessage(1, "loading the well defined SQL statements...");
                        DoProcessUiMessage(2, PMs);
                        DoProcessUiMessage(1, "error on loading the well defined SQL statement.");
                    }
                    return false;
                }


                // load the infrastructure
                DoProcessUiMessage(0, "");
                DoProcessUiMessage(1, "loading the the infrastructure definitions...");
                var _cfgInfrastructure = new cDataHistoryConfigInfrastructure();
                _cfgInfrastructure.LoadFromFile(out PMs);

                if (PMs != null && PMs.Count > 0)
                    DoProcessUiMessage(2, PMs);

                if (_cfgInfrastructure == null || !_cfgInfrastructure.IsValid)
                {
                    DoProcessUiMessage(1, "error on loading the infrastructure definitions.");
                    return false;
                }
                InfrastructureConfig = _cfgInfrastructure;
                DoProcessUiMessage(1, "the infrastructure configuration loaded successful.");
                DoProcessUiMessage(0, "");

                var HasM42Config = _cfgInfrastructure.M42Wpm != null;
                var HasIntuneConfig = (_cfgInfrastructure.AzureTenants?.Values.Any(t => t.ScanIntuneDevices) == true);
                var HasMobileDeviceConfig = (_cfgInfrastructure.AzureTenants?.Values.Any(t => t.WithMobileDevices) == true);
                var HasCitrixConfig = (InfrastructureConfig.Citrix != null);
                var HasEmpirumConfig = true;

                DoProcessUiMessage(1, "resolving the ad membership groups");
                ActiveDirectory.ResolveWinAuthenticationGroups();

                // check the server environment
                if (checkServerEnvironment)
                    await CheckServerEnvironmentAsync(checkCurrentUser, Token, requestInfo, LogDeep + 1);

                // load the data clusters
                DoProcessUiMessage(0, "");
                DoProcessUiMessage(1, "loading the data cluster definitions...");
                var _cfgDataClusters = new cDataHistoryConfigClusters();
                _cfgDataClusters.LoadFromFile(out PMs, withM42Config: HasM42Config, withIntuneConfig: HasIntuneConfig, withMobileDeviceConfig: HasMobileDeviceConfig, withCitrixConfig: HasCitrixConfig);

                if (PMs != null && PMs.Count > 0)
                    DoProcessUiMessage(2, PMs);

                if (_cfgDataClusters == null || !_cfgDataClusters.IsValid)
                {
                    DoProcessUiMessage(1, "error on loading the data cluster definitions.");
                    return false;
                }
                ClusterConfig = _cfgDataClusters;
                DoProcessUiMessage(1, "the data cluster configuration loaded successful.");
                DoProcessUiMessage(0, "");

                // load the global parameters
                if (!RefreshGlobalConfigurationParameters())
                    return false;

                mainDbConnection = InfrastructureConfig.HistoryDB.Connection;

                if (InfrastructureConfig.M42Wpm != null)
                {
                    var _M42WpmCollector = new cDataHistoryCollectorM42Wpm(this);
                    if (cF4SDLicense.Instance?.Modules != null)
                        if (cF4SDLicense.Instance.Modules.ContainsKey(_M42WpmCollector.LicenseId))
                        {
                            M42WpmCollector = _M42WpmCollector;
                            Connectors.Add(enumDataHistoryOrigin.M42Wpm, M42WpmCollector);
                            RegisterSearchRelationProvider(_M42WpmCollector);
                        }
                }

                if (InfrastructureConfig.Nexthink != null)
                {
                    var _NxqlCollector = new cDataHistoryCollectorNxql(this);
                    if (cF4SDLicense.Instance?.Modules != null)
                        if (cF4SDLicense.Instance.Modules.ContainsKey(_NxqlCollector.LicenseId))
                        {
                            NxqlCollector = _NxqlCollector;
                            Connectors.Add(enumDataHistoryOrigin.NexthinkNxql, NxqlCollector);
                        }
                }

                if (HasCitrixConfig)
                {
                    var _CitrixCollector = new cDataHistoryCollectorCitrix(this);
                    if (cF4SDLicense.Instance?.Modules != null)
                        if (cF4SDLicense.Instance.Modules.ContainsKey(_CitrixCollector.LicenseId))
                        {
                            CitrixCollector = _CitrixCollector;
                            Connectors.Add(enumDataHistoryOrigin.Citrix, _CitrixCollector);
                            RegisterSearchRelationProvider(_CitrixCollector);
                        }
                }

                if (HasIntuneConfig)
                {
                    Connectors.Add(enumDataHistoryOrigin.Intune, ActiveDirectory);

                }
                //if (HasIntuneConfig && HasMobileDeviceConfig)
                //{
                //    RegisterSearchRelationProvider(ActiveDirectory);
                //}

                //if (HasEmpirumConfig)
                //{
                //    var _EmpirumCollector = new cDataHistoryCollectorEmpirum(this);
                //    EmpirumCollector = _EmpirumCollector;
                //    Connectors.Add(enumDataHistoryOrigin.Empirum, EmpirumCollector);

                //}

                // do the post configuration tasks afer the infrastructure was loaded
                DoProcessUiMessage(1, "processing post configuration tasks");
                foreach (var Table in _cfgDataClusters.Tables.Values)
                {
                    if (Connectors.TryGetValue(Table.ParentCluster.Origin, out var _module))
                        _module?.TablePostConfig(Table);
                }

                RegisterSearchRelationProvider(this);

                DoProcessUiMessage(0, "");
                DoProcessUiMessage(0, "the collector configuration loaded successful.");

                IsValid = true;
                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;
        }

        private void RegisterSearchRelationProvider(ISearchResultRelationProvider provider)
        {
            _searchResultRelationProviders.Add(provider);
        }

        public cF4sdGlobalConfig GetGlobalConfig()
        {
            RefreshGlobalConfigurationParameters();
            cF4sdGlobalConfig _result = null;
            lock (GlobalParametersConfigLock)
            {
                _result = GlobalConfig;
            }
            return GlobalConfig;
        }

        public Task<cTicketExternalLinkResult> GetTicketExternalLinkAsync(Guid ticketId, enumTicketExternalOpenMode mode, cF4sdWebRequestInfo requestInfo, int logDeep, CancellationToken token)
        {
            if (M42WpmCollector == null)
            {
                return Task.FromResult(new cTicketExternalLinkResult
                {
                    Success = false,
                    Mode = enumTicketExternalOpenMode.None
                });
            }

            return M42WpmCollector.GetTicketExternalLinkAsync(ticketId, mode, requestInfo, logDeep, token);
        }

        private void RegisterSearchRelationProviders(IEnumerable<ISearchResultRelationProvider> providers)
        {
            _searchResultRelationProviders.AddRange(providers);
        }

        public async Task CheckServerEnvironmentAsync(bool CheckCurrentUser, CancellationToken Token, 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
            {
                DoProcessUiMessage(1, "");
                DoProcessUiMessage(1, "checking the server environment...");

                var _issConfig = IisConfigHelper.CheckIISConfiguration();

                if (!_issConfig.IsWebSocketInstalled)
                    DoProcessUiMessage(2, "  Warning: The IIS feature 'WebSocket' is not installed. This is needed for use of direct connections.", LogLevels.Warning);
                if (!_issConfig.IsIssWindowsAuthenticationInstalled)
                    DoProcessUiMessage(2, "  Warning: The IIS feature 'WindowsAuthentication' is not installed. This is mandatory for the use of authorization.", LogLevels.Warning);
                if (!_issConfig.IsIssWindowsAuthenticationEnabled)
                    DoProcessUiMessage(2, "  Warning: The 'WindowsAuthentication' is not enabled for the WEB application F4SDCockpit. This is mandatory for the use of authorization.", LogLevels.Warning);

                await CheckAuthorisationConfigurationAsync(CheckCurrentUser, Token, requestInfo, LogDeep + 1);



                DoProcessUiMessage(1, "server environment check finished.");
            }
            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);
            }
        }

        public async Task CheckAuthorisationConfigurationAsync(bool CheckCurrentUser, CancellationToken Token, 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
            {
                if (InfrastructureConfig?.Authorisation == null || !InfrastructureConfig.Authorisation.IsValid)
                {
                    DoProcessUiMessage(2, "  Warning: Authentication is not configured properly in the file 'F4SD-Infrastructure-Configuration.xml'. It is strongly recommended not to use F4SD Cockpit without configured authentication.", LogLevels.Warning);
                    return;
                }

                if (CheckCurrentUser)
                {
                    var _userInfo = await GetCurrentUserInfoAsync(Token, requestInfo, LogDeep + 1);
                    if (_userInfo?.Roles == null || _userInfo.Roles.Count == 0)
                    {
                        DoProcessUiMessage(2, $"  Warning: The current user {_userInfo?.AdAccount} is not member of any F4SD role. Please check the authentication configuration and the ad group memberships.", LogLevels.Warning);
                    }
                    else
                    {
                        if (!_userInfo.Roles.Contains("Cockpit.User"))
                            DoProcessUiMessage(2, $"  Warning: The current user {_userInfo.AdAccount} is not member of the F4SD role 'Cockpit.User'. Please check the authentication configuration and the ad group memberships.", LogLevels.Warning);
                    }
                }
            }
            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);
            }
        }

        public void ValidateClientConfiguration()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                DoProcessUiMessage(0, "start validating the client configuration files...");
                DoProcessUiMessage(0, "");

                var _Configs = new Dictionary<enumFasdConfigurationType, cFasdBaseConfig>();
                var _lst = Enum.GetValues(typeof(enumFasdConfigurationType)).Cast<enumFasdConfigurationType>();
                foreach (var _item in _lst)
                {
                    if (_item != enumFasdConfigurationType.unknown)
                    {
                        var _msg = new List<cXmlParserNodeMessage>();
                        var HasM42Config = InfrastructureConfig.M42Wpm != null;
                        var HasIntuneConfig = (InfrastructureConfig.AzureTenants?.Values.Any(t => t.ScanIntuneDevices) == true);
                        var HasMobileDeviceConfig = (InfrastructureConfig.AzureTenants?.Values.Any(t => t.WithMobileDevices) == true);
                        var HasCitrixConfig = (InfrastructureConfig.Citrix != null);

                        var _cfg = cFasdBaseConfig.GetConfigByType(_item, null, _msg, withM42Config: HasM42Config, withIntuneConfig: HasIntuneConfig, withMobileDeviceConfig: HasMobileDeviceConfig, withCitrixConfig: HasCitrixConfig);
                        if (_cfg.IsValid)
                        {
                            _Configs[_item] = _cfg;
                            if (_msg.Count == 0)
                                DoProcessUiMessage(1, $"the {_item} configuration is valid.");
                            else
                                DoProcessUiMessage(1, $"the {_item} configuration is valid but has some annotations:");
                        }
                        else
                            DoProcessUiMessage(1, $"the the {_item} configuration is invalid!");
                        if (_msg.Count > 0)
                            DoProcessUiMessage(2, _msg);

                        DoProcessUiMessage(0, "");
                    }
                }

                ClientConfigs = _Configs;

                DoProcessUiMessage(0, "the validation of the client configuration files finished.");
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

        }

        public async Task UpdateF4SDAnalyticsConfigInfosAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken _token)
        {
            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
            {
                if (this.F4SDAnalyticsValid && this.F4SDAnalyticsDb?.Connection != null)
                {
                    if (ClientConfigs != null && ClientConfigs.TryGetValue(enumFasdConfigurationType.quickActions, out var _quickActionsBase))
                    {
                        if (_quickActionsBase is cFasdQuickActionConfig _quickActions)
                        {
                            if (DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateQActionInfo", out var Query))
                                if (DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateQActionInfoLang", out var Query2))
                                {
                                    using (var Conn = new cDbConnection(this.F4SDAnalyticsDb.Connection))
                                    {
                                        foreach (var _quickAction in _quickActions.F4SDQuickActions.QuickActions.Values)
                                        {
                                            if (_quickAction.Id == Guid.Empty)
                                                continue;

                                            var _params = new Dictionary<string, object>()
                                            {
                                                { "Id", _quickAction.Id},
                                                { "Name", _quickAction.Name},
                                                { "Type", _quickAction.GetTypeName()}
                                            };
                                            await DataHistorySqlHelper.ExecuteAsync(Conn, Query, _params, requestInfo, _token);

                                            foreach (var _langEntry in _quickAction.Names)
                                            {
                                                _params = new Dictionary<string, object>()
                                                {
                                                    { "Id", _quickAction.Id},
                                                    { "Name", _langEntry.Value},
                                                    { "Lang", _langEntry.Key.ToUpperInvariant()}
                                                };
                                                await DataHistorySqlHelper.ExecuteAsync(Conn, Query2, _params, requestInfo, _token);
                                            }
                                        }
                                    }
                                }
                        }
                    }
                }
            }
            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);
            }
        }

        private async Task ValidateDatabaseAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 DbName = InfrastructureConfig.HistoryDB.Connection.Database;
                DoProcessUiMessage(0, "");
                DoProcessUiMessage(1, $"start validating the data history database '{DbName}' itself...");

                using (var DbConn = new cDbConnection(mainDbConnection, Timeout: 10, WithoutDatabase: true))
                    if (DbConn.IsOpen)
                    {
                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_Database", out var Query))
                            return;
                        var Params = new Dictionary<string, object>() { { "DbName", DbName } };
                        var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, Params, requestInfo, Token);
                        if (n >= 1)
                        {
                            DoProcessUiMessage(1, $"the validation of the data history database '{DbName}' itself finished successful.");
                        }
                        else
                        {
                            DoProcessUiMessage(2, $"the database {DbName} coud not be found.");

                            if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Create_Database", out Query))
                                return;
                            var Query2 = Query.Clone(string.Format(Query.Query, mainDbConnection.Database));
                            var n2 = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query2, null, requestInfo, Token);
                            if (n2 == null)
                            {
                                DoProcessUiMessage(2, $"the data history database '{DbName}' could not be created.");
                                return;
                            }

                            DoProcessUiMessage(2, $"the database {DbName} is created successful.");
                        }

                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_DbCollation", out Query))
                            return;

                        var strQuery = string.Format(Query.Query, DbName);
                        var _startTime2 = DateTime.UtcNow;
                        n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn.Connection, strQuery, null, Token);
                        if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime2, DbName, 0, requestInfo?.requestName);
                        if (n != 1)
                            DoProcessUiMessage(2, $"Warning: the collation of database {DbName} is not 'Latin1_General_CI_AI'");
                    }


            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the data history database itself aborted with an exceptional error.");
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public override async Task<bool> ValidateMainTablesAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                // validate if fulltext search is enabled for the server instance
                DoProcessUiMessage(1, $"start validating the full text feature...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_Fulltext_Installed", out var Query))
                    return false;
                if (await DataHistorySqlHelper.GetScalarResultAsync<int?>(DbConn, Query, null, requestInfo, Token) != 1)
                {
                    DoProcessUiMessage(1, $"the sql server instance {DbConn.Connection.DataSource} is not enabled for full text.");
                    return false;
                }

                // validate if fulltext search is enabled for the database
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_Fulltext", out Query))
                    return false;
                if (!(await DataHistorySqlHelper.GetScalarResultAsync<bool?>(DbConn, Query, null, requestInfo, Token) == true))
                {
                    DoProcessUiMessage(1, $"the database '{DbConn.Connection.Database}' is not enabled for full text.");
                    return false;
                }
                DoProcessUiMessage(1, $"the full text feature is activated for the database.");
                DoProcessUiMessage(1, "");

                // validate the main tables
                DoProcessUiMessage(1, $"start validating the main tables...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_MainTables", out Query))
                    return false;
                var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                if (n > 0)
                {
                    DoProcessUiMessage(1, $"the validation of the main tables finished successful.");
                    return true;
                }

                // create the main tables
                DoProcessUiMessage(2, $"the main tables are not complete, creating them...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Create_MainTables", out Query))
                    return false;
                var n2 = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, null, requestInfo, Token);

                // validate the creates main tables
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_MainTables", out Query))
                    return false;
                n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                if (n > 0)
                {
                    DoProcessUiMessage(2, $"the main tables were created successfull.");
                    return true;
                }

                DoProcessUiMessage(2, "An error occurred when creating the main tables.");

            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the main tables aborted with an exceptional error.");
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        private async Task<bool> ValidateF4SDAnalyticsDatabaseAsync(CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, bool ForceCreate = 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
            {
                var DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
                if (DbConnection == null)
                    return false;

                DoProcessUiMessage(0, "");

                DoProcessUiMessage(1, $"start validating the data database '{DbConnection.Database}' itself...");


                using (var DbConn = new cDbConnection(DbConnection, WithoutDatabase: true))
                    if (DbConn.IsOpen)
                    {
                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_Database", out var Query))
                            return false;
                        var Params = new Dictionary<string, object>() { { "DbName", DbConnection.Database } };
                        var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, Params, requestInfo, Token);
                        if (n >= 1)
                        {
                            DoProcessUiMessage(1, $"the validation of the data F4SDAnalytics database '{DbConnection.Database}' itself finished successful.");
                            return true;
                        }

                        DoProcessUiMessage(2, $"the database {DbConnection.Database} coud not be found.");
                        if (ForceCreate)
                        {
                            if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Create_Database", out Query))
                                return false;
                            var Query2 = Query.Clone(string.Format(Query.Query, DbConnection.Database));
                            var n2 = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query2, null, requestInfo, Token);
                            if (n2 != null)
                            {
                                DoProcessUiMessage(2, $"the database {DbConnection.Database} is created successful.");
                                return true;
                            }

                            DoProcessUiMessage(2, $"Error: the database '{DbConnection.Database}'  could not be created.");
                            return false;
                        }
                    }
                DoProcessUiMessage(1, $"Error: the connection on the database '{DbConnection.Database}' is not available.");

                return false;
            }
            catch (Exception E)
            {
                DoProcessUiMessage(0, "");
                DoProcessUiMessage(0, $"the validation of the data F4SDAnalytics database itself aborted with an exceptional error.");
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        private async Task<bool> ValidateF4SDAnalyticsMainTablesAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool ForceCreate)
        {
            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
            {
                // validate the main tables
                DoProcessUiMessage(1, $"start validating the main tables...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_F4SDAnalyticsMainTables", out var Query))
                    return false;
                var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                if (n > 0)
                {
                    DoProcessUiMessage(1, $"the validation of the main tables finished successful.");
                    DoProcessUiMessage(0, "");
                    return true;
                }
                if (!ForceCreate && n < 1)
                {
                    DoProcessUiMessage(2, $"the main tables are not complete...");
                    return false;
                }
                if (ForceCreate)
                {
                    // create the main tables
                    DoProcessUiMessage(2, $"the main tables are not complete, creating them...");
                    if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Create_F4SDAnalyticsMainTables", out Query))
                        return false;
                    await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, null, requestInfo, Token);

                    // validate the creates main tables
                    if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_F4SDAnalyticsMainTables", out Query))
                        return false;
                    n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                    if (n > 0)
                    {
                        DoProcessUiMessage(2, $"the main tables were created successfull.");
                        DoProcessUiMessage(0, "");
                        return true;
                    }

                    DoProcessUiMessage(2, "An error occurred when creating the main tables.");
                }
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the main tables aborted with an exceptional error.");
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        private async Task<bool> ValidateF4SDAnalyticsFunctionsAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool ForceCreate)
        {
            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
            {
                // validate the functions
                DoProcessUiMessage(1, $"start validating the functions...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_F4SDAnalyticsFunctions", out var Query))
                    return false;
                var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                if (n > 0)
                {
                    DoProcessUiMessage(1, $"the validation of the functions finished successful.");
                    DoProcessUiMessage(0, "");
                    return true;
                }
                if (!ForceCreate && n < 1)
                {
                    DoProcessUiMessage(2, $"the functions are not complete...");
                    return false;
                }
                if (ForceCreate)
                {
                    // create the functions
                    DoProcessUiMessage(2, $"the functions are not complete, creating them...");
                    if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Create_F4SDAnalyticsFunctions", out Query))
                        return false;
                    await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, null, requestInfo, Token);

                    // validate the creates functions
                    if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_F4SDAnalyticsFunctions", out Query))
                        return false;
                    n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                    if (n > 0)
                    {
                        DoProcessUiMessage(2, $"the functions were created successfull.");
                        DoProcessUiMessage(0, "");
                        return true;
                    }

                    DoProcessUiMessage(2, "An error occurred when creating the functions.");
                }
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the funtions aborted with an exceptional error.");
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        private async Task<bool> ValidateF4SDAnalyticsViewsAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool ForceCreate)
        {
            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
            {
                // validate the view tables
                DoProcessUiMessage(1, $"start validating the view tables...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_F4SDAnalyticsViews", out var Query))
                    return false;

                var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                if (n > 0)
                {
                    DoProcessUiMessage(1, $"the validation of the view tables finished successful.");
                    DoProcessUiMessage(0, "");
                    return true;
                }

                if (!ForceCreate && n < 1)
                {
                    DoProcessUiMessage(2, $"the view tables are not complete...");
                    return false;
                }

                if (ForceCreate)
                {
                    // create the view tables
                    DoProcessUiMessage(2, $"the view tables are not complete, creating them...");
                    if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Create_F4SDAnalyticsViews", out Query))
                        return false;
                    Query.Query = Query.Query.Replace("$DatabaseHistory$", InfrastructureConfig.HistoryDB.Connection.Database);
                    await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, null, requestInfo, Token);

                    // validate the creates view tables
                    if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Validate_F4SDAnalyticsViews", out Query))
                        return false;
                    n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                    if (n > 0)
                    {
                        DoProcessUiMessage(2, $"the view tables were created successfull.");
                        return true;
                    }

                    DoProcessUiMessage(2, "An error occurred when creating the view tables.");
                }
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the view tables aborted with an exceptional error.");
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        private async Task<bool> UpdateF4SDAnalyticsMainTablesAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool ForceCreate)
        {
            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
            {
                // validate the main tables
                DoProcessUiMessage(1, $"start update the main tables...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Update_F4SDAnalyticsMainTables", out var Query))
                    return false;
                var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, Query, null, requestInfo, Token);
                if (n == 0)
                {
                    DoProcessUiMessage(1, $"the update of the main tables was successfull, nothing updated.");
                    return true;
                }
                if (n > 0)
                {
                    DoProcessUiMessage(1, $"the update of the main tables finished successful.");
                    DoProcessUiMessage(0, "");
                    return true;
                }
                else
                {
                    DoProcessUiMessage(2, $"the main tables could not be updated.");
                    LogEntry($"the main tables could not be updated.", LogLevels.Fatal);
                    return false;

                }
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the main tables aborted with an exceptional error.");
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        private async Task<bool> UpdateF4SDAnalyticsViewsAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool ForceCreate)
        {
            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
            {
                // validate the main tables
                DoProcessUiMessage(1, $"start update the views...");
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("Update_F4SDAnalyticsViews", out var Query))
                    return false;
                Query.Query = Query.Query.Replace("$DatabaseHistory$", InfrastructureConfig.HistoryDB.Connection.Database);
                var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, null, requestInfo, Token);
                if (n != null)
                {
                    DoProcessUiMessage(1, $"the update of the view tables finished successful.");
                    DoProcessUiMessage(0, "");
                    return true;
                }

                DoProcessUiMessage(1, $"the view tables could not be updated.");
                return false;

            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the update of the views aborted with an exceptional error.");
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        private async Task<Version> GetDatabaseTableVersion(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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;

            if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetDbTablesVersion", out var Query))
                return null;
            int apiError = 0;

            try
            {

                using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(DbConn, Query, null, Token))
                {
                    if (Reader != null)
                        try
                        {
                            if (Reader.HasRows)
                            {
                                if (await Reader.ReadAsync())
                                {
                                    try
                                    {
                                        var iVersion = Reader.GetInt32(0);
                                        var iRevision = Reader.GetInt32(1);
                                        var RetVal = new Version(iVersion, iRevision);
                                        return RetVal;
                                    }
                                    catch (Exception E)
                                    {
                                        cLogManager.DefaultLogger.LogException(E);
                                    }
                                }
                            }
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }
                }
            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, "", apiError, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
            return null;
        }

        private async Task InsertDbVersionAsync(cDbConnection DbConn, Version version, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateDbVersion", out var Query))
                    return;

                var Params = new Dictionary<string, object>(2) { { "version", version.Major }, { "revision", version.Minor } };

                await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);

            }
            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);
            }
        }

        private async Task<bool> ValidatePostUpdates(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                // get the actual db table version
                var currentVersion = await GetDatabaseTableVersion(DbConn, requestInfo, LogDeep + 1, Token);

                // get all db table updates not done
                var Updates = new SortedList<Version, cDbQuery>();
                foreach (var Entry in DataHistorySqlHelper.wellDefinedSqlStatements.Values)
                {
                    const string constUpdateName = "UpdateDbTablesV";
                    if (Entry.Name.StartsWith(constUpdateName))
                    {
                        var strVersion = Entry.Name.Remove(0, constUpdateName.Length);
                        if (Version.TryParse(strVersion, out var entryVersion))
                            if (entryVersion > currentVersion)
                                Updates.Add(entryVersion, Entry);
                    }
                }

                // do the updates
                var IsOk = true;
                foreach (var updateQuery in Updates)
                {
                    var n = await DataHistorySqlHelper.GetScalarResultAsync<int>(DbConn, updateQuery.Value, null, requestInfo, Token);
                    if (n == 0)
                    {
                        DoProcessUiMessage(1, $"the validation of the post update V{updateQuery.Key} was successfull, nothing updated.");
                        await InsertDbVersionAsync(DbConn, updateQuery.Key, requestInfo, LogDeep + 1, Token);
                    }
                    else if (n > 0)
                    {
                        DoProcessUiMessage(1, $"{n} updates where processed by post update V{updateQuery.Key}.");
                    }
                    else
                    {
                        LogEntry($"the post update V01 is not successful.", LogLevels.Fatal);
                        IsOk = false;
                    }
                }

                if (IsOk)
                    return true;
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the post updates aborted with an exceptional error.");
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            LogEntry($"the post updates could not be validated sucessfull.", LogLevels.Fatal);

            return false;
        }


        private async Task ValidateAgentApiConnection()
        {

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

            try
            {
                const int maxRetryCount = 3;
                TimeSpan delay = TimeSpan.FromSeconds(5);

                bool loggedInToAgentApi = false;
                for (int i = 0; i < maxRetryCount; i++)
                {
                    loggedInToAgentApi = await F4sdAgent.LogonToAgentApiAsync();
                    if (loggedInToAgentApi)
                        break;

                    LogEntry("Logon to agent API failed.", LogLevels.Warning);
                    await Task.Delay(delay);
                }

                if (!loggedInToAgentApi)
                {
                    DoProcessUiMessage(0, $"ERROR: Could not connect to the F4SD Agent API. Maybe there are invalid credentials configured.", LogLevels.Error);
                    return;
                }

                var _remoteScript = await F4sdAgent.GetRemoteScriptsAsync();
                if (_remoteScript == null)
                {
                    DoProcessUiMessage(0, $"ERROR: Retrieve remote scripts from Agent API.", LogLevels.Error);
                    return;
                }

                DoProcessUiMessage(0, $"Successful connection to the F4SD Agent API established. {_remoteScript.Count} remote scripts found.", LogLevels.Info);
            }
            catch (Exception E)
            {
                DoProcessUiMessage(0, $"ERROR: Unknown error while validating Agent API.", LogLevels.Error);
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }


        }

        public async Task<bool> ValidateHistoryDatabasesAsync(bool isWebservice, CancellationToken Token, 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;

            var RetVal = true;
            var DbName = InfrastructureConfig.HistoryDB.Connection.Database;
            try
            {
                DoProcessUiMessage(0, $"start validating the data history database '{DbName}'...");

                DoProcessUiMessage(0, "");
                await ValidateDatabaseAsync(requestInfo, LogDeep + 1, Token);
                if (Token.IsCancellationRequested) return false;

                using (var DbConn = new cDbConnection(mainDbConnection, Timeout: 3 * 60 * 60))
                {
                    if (!DbConn.IsOpen)
                        return false;

                    //DoProcessUiMessage(0, "");
                    //RetVal = await ValidateMainTablesAsync(DbConn, requestInfo, LogDeep + 1, Token);
                    //if (Token.IsCancellationRequested || !RetVal) return false;

                    DoProcessUiMessage(0, "");
                    foreach (var Connector in GetConnectors())
                    {
                        RetVal = await Connector.ValidateMainTablesAsync(DbConn, requestInfo, LogDeep + 1, Token);
                        if (Token.IsCancellationRequested || !RetVal) return false;
                        DoProcessUiMessage(0, "");
                    }

                    RetVal = await ValidatePostUpdates(DbConn, requestInfo, LogDeep + 1, Token);
                    DoProcessUiMessage(0, "");

                    var _Ok = true;
                    // check the existance of the tables and create them
                    foreach (var DbTable in ClusterConfig.Tables.Values)
                    {
                        if (DbTable.IsVirtual && DbTable.Cached != eDataHistoryTableCached.Yes)
                            continue;
                        _Ok &= await ValidateDbTableAsync(DbConn, DbTable, requestInfo, LogDeep + 1, Token);
                    }

                    // check the table references and create them
                    var lst = "";
                    foreach (var DbTable in ClusterConfig.Tables.Values)
                    {
                        if (DbTable.IsVirtual && DbTable.Cached != eDataHistoryTableCached.Yes)
                            continue;

                        var _sqlRefs = await DataHistorySqlHelper.GetTableReferences(DbConn.Connection, DbTable.SourceName, Token);
                        if (DbTable.HasScanId)
                        {
                            lst += DbTable.Name + "\n";
                            _Ok &= await ValidateDbTableReferenceScanHistoryAsync(DbConn, DbTable, _sqlRefs, requestInfo, LogDeep + 1, Token);
                        }
                        foreach (var Ref in DbTable.References.Values)
                            _Ok &= await ValidateDbTableReferencesAsync(DbConn, DbTable, Ref, _sqlRefs, requestInfo, LogDeep + 1, Token);
                    }

                    if (Token.IsCancellationRequested || !RetVal) return false;

                    await LoadGloablConfigValuesAsync(DbConn, requestInfo, LogDeep + 1, Token);

                    if (isWebservice)
                        await TokenCache.LoadAsync(DbConn, Token, requestInfo, LogDeep + 1);
                }

                DoProcessUiMessage(0, $"the validation of the data history database '{DbName}' finished.");

                if (!await F4sdAgent.CheckAndInitializeDbAccess(requestInfo, LogDeep + 1, CancellationToken.None))
                {
                    DoProcessUiMessage(0, $"Could not initialize the F4SD agent database correctly '{InfrastructureConfig.ClientAgent.Organization}'.");
                    return false;
                }

                await ValidateAgentApiConnection();
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the data history database '{DbName}' aborted with an exceptional error.");
                RetVal = false;
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }

        private async Task<bool> ValidateDbTableAsync(cDbConnection DbConn, cDataHistoryConfigTable DbTable, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 _TableExist = await DataHistorySqlHelper.CheckIfTableExistAsync(DbConn.Connection, DbTable.SourceName, requestInfo, LogDeep + 1);
                if (_TableExist)
                    LogEntry($"The database '{DbTable.SourceName}' already exist.", LogLevels.Debug);
                else
                {
                    LogEntry($"The database '{DbTable.SourceName}' does not exist, creating it...", LogLevels.Debug);
                    var Query = DataHistorySqlHelper.GetSqlCreateStatement(DbTable, cDataHistoryConfigClusters.confUseBinValuesAsPrimaryKey, true);
                    var _startTime2 = DateTime.UtcNow;
                    var CRes = await DataHistorySqlHelper.ExecuteAsync(DbConn.Connection, Query, null, Token);
                    if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("ValidateDbTableAsync.CreateTable", _startTime2, DbTable.SourceName, 0, requestInfo?.requestName);
                }

                // validate & create additional table columns
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("CheckAndCreateTableColumn", out var Query2))
                    return false;

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("CheckScanIdTableColumn", out var QueryScanId))
                    return false;

                bool ColumnCreated = false;
                foreach (var Column in DbTable.Columns.Values)
                {
                    try
                    {
                        var strNull = "NULL";
                        if (!DbTable.IsNullable(Column))
                            strNull = "NOT " + strNull;

                        var strQuery = string.Format(Query2.Query, DbTable.SourceName, Column.Name, Column.SqlType, strNull);
                        var _startTime2 = DateTime.UtcNow;
                        var RV = await DataHistorySqlHelper.GetScalarResultAsync<int?>(DbConn.Connection, strQuery, null, Token);
                        if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query2.Name, _startTime2, DbTable.SourceName + "." + Column.Name, 0, requestInfo?.requestName);
                        if (RV == 1)
                            ColumnCreated = true;

                        if (Column.SqlTypeBin != null)
                        {
                            strQuery = string.Format(Query2.Query, DbTable.SourceName, Column.Name + DataHistorySqlHelper.constBinValueNameAdd, Column.SqlTypeBin, strNull);
                            _startTime2 = DateTime.UtcNow;
                            RV = await DataHistorySqlHelper.GetScalarResultAsync<int?>(DbConn.Connection, strQuery, null, Token);
                            if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query2.Name, _startTime2, DbTable.SourceName + "." + Column.Name + DataHistorySqlHelper.constBinValueNameAdd, 0, requestInfo?.requestName);
                            if (RV == 1)
                                ColumnCreated = true;
                        }
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                }

                var strQueryScanId = string.Format(QueryScanId.Query, DbTable.SourceName);
                var _startTime3 = DateTime.UtcNow;
                var _n = await DataHistorySqlHelper.GetScalarResultAsync<int?>(DbConn.Connection, strQueryScanId, null, Token);
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(QueryScanId.Name, _startTime3, DbTable.SourceName, 0, requestInfo?.requestName);
                DbTable.HasScanId = _n == 1;

                if (ColumnCreated)
                {
                    var isOk = await Connectors[DbTable.ParentCluster.Origin].ColumnUpatePostProcessingAsync(DbConn, requestInfo, LogDeep + 1, Token);
                }

            }
            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;
        }

        private async Task<bool> ValidateDbTableReferenceScanHistoryAsync(cDbConnection DbConn, cDataHistoryConfigTable DbTable, Dictionary<string, cSqlReferenceInfo> _sqlRefs, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            if (DbTable.Type != eDataHistoryTableType.Events && DbTable.Type != eDataHistoryTableType.History)
                return true;

            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
            {
                if (_sqlRefs.ContainsKey($"FK_{DbTable.SourceName}_scan-history"))
                    return true;

                var sqlCmd = $"ALTER TABLE [{DbTable.SourceName}]  WITH CHECK ADD  CONSTRAINT [FK_{DbTable.SourceName}_scan-history] FOREIGN KEY([ScanId]) REFERENCES [main-scan-history] ([ScanId]) ON DELETE CASCADE;";
                var _Res = await DataHistorySqlHelper.ExecuteAsync(DbConn.Connection, sqlCmd, null, Token);
                return (_Res == -1);
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("ValidateDbTableReferenceScanHistoryAsync", _startTime, "DbTable.SourceName", 0, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
            return false;
        }

        private async Task<bool> ValidateDbTableReferencesAsync(cDbConnection DbConn, cDataHistoryConfigTable DbTable, cDataHistoryConfigTableReferenceInfo Ref, Dictionary<string, cSqlReferenceInfo> _sqlRefs, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 RetVal = true;
                var _RefName = DataHistorySqlHelper.GetReferencingName(DbTable, Ref);
                var _exists = false;
                foreach (var _refInfo in _sqlRefs.Values)
                {
                    if (_refInfo.Name == _RefName)
                    {
                        _exists = true;
                        break;
                    }
                }
                if (!_exists)
                {
                    var _Query = DataHistorySqlHelper.GetReferencingStatements(DbTable, Ref);
                    var _Res = await DataHistorySqlHelper.ExecuteAsync(DbConn.Connection, _Query, null, Token);
                    if (_Res != -1)
                        RetVal = false;
                }

                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("ValidateDbTableReferencesAsync", _startTime, "DbTable.SourceName", 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 async Task<bool> ValidateAgentDbUpdates(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var cmdList = DataHistorySqlHelper.wellDefinedSqlStatements.Where(v => v.Key.StartsWith("AgentDbUpdate")).ToDictionary(v => v.Key, v => v.Value);
                var cmdSorted = new SortedList<string, cDbQuery>(cmdList);
                DoProcessUiMessage(0, "");
                DoProcessUiMessage(0, "Start validating Agent DB");
                using (var DbConn = new cDbConnection(this.InfrastructureConfig.ClientAgent.Connection, Timeout: 3 * 60 * 60))
                {
                    foreach (var DbCmd in cmdSorted.Values)
                    {
                        try
                        {
                            if (String.IsNullOrWhiteSpace(DbCmd.Description))
                                DoProcessUiMessage(1, $"Processing SQL query {DbCmd.Name}...");
                            else
                                DoProcessUiMessage(1, DbCmd.Description + "...");

                            var resInt = await DataHistorySqlHelper.ExecuteAsync(DbConn, DbCmd, null, requestInfo, Token, Timeout: 3 * 60 * 60);

                            DoProcessUiMessage(2, $"...finished.");
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                    }
                }
                DoProcessUiMessage(0, "Validating Agent DB finished.");
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public async Task<bool> ValidateF4SDAnalyticsDBAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool ForceCreate = 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;

            var RetVal = true;
            F4SDAnalyticsValid = false;
            var DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
            if (DbConnection == null)
                return false;

            try
            {
                DoProcessUiMessage(0, "");
                DoProcessUiMessage(0, $"start validating the data  '{DbConnection.Database}' database...");

                var isValid = await ValidateF4SDAnalyticsDatabaseAsync(Token, requestInfo, LogDeep + 1, ForceCreate);

                if (!isValid)
                {
                    DoProcessUiMessage(0, "");
                    DoProcessUiMessage(0, $"the validation of the data database '{DbConnection.Database}' finished.");
                    return false;
                }
                if (Token.IsCancellationRequested) return false;

                using (var DbConn = new cDbConnection(DbConnection, Timeout: 3 * 60 * 60))
                {
                    if (!DbConn.IsOpen)
                    {
                        return false;
                    }
                    DoProcessUiMessage(0, "");
                    RetVal = await ValidateF4SDAnalyticsMainTablesAsync(DbConn, requestInfo, LogDeep + 1, Token, ForceCreate);
                    if (Token.IsCancellationRequested || !RetVal)
                    {
                        return false;
                    }

                    RetVal = await ValidateF4SDAnalyticsFunctionsAsync(DbConn, requestInfo, LogDeep + 1, Token, ForceCreate);
                    if (Token.IsCancellationRequested || !RetVal)
                    {
                        return false;
                    }

                    RetVal = await ValidateF4SDAnalyticsViewsAsync(DbConn, requestInfo, LogDeep + 1, Token, ForceCreate);
                    if (Token.IsCancellationRequested || !RetVal)
                    {
                        return false;
                    }

                    RetVal = await UpdateF4SDAnalyticsViewsAsync(DbConn, requestInfo, LogDeep + 1, Token, ForceCreate);
                    if (Token.IsCancellationRequested || !RetVal)
                    {
                        return false;
                    }

                    DoProcessUiMessage(0, "");
                    F4SDAnalyticsValid = true;
                    F4SDAnalyticsDb = InfrastructureConfig.F4SDAnalyticsDB;
                }

                DoProcessUiMessage(0, $"the validation of the data analytics database '{DbConnection.Database}' finished.");

            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"the validation of the data analytics database '{DbConnection.Database}' aborted with an exceptional error.");
                RetVal = false;
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }

        public async void CheckSessionsAsync(object sender, System.Timers.ElapsedEventArgs e)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var currentTimeStamp = DateTime.UtcNow;
                var _toClose = new List<cF4SDUserSessionParameters>();
                lock (SessionValues)
                {
                    foreach (var v in SessionValues)
                    {
                        var diff = (int)currentTimeStamp.Subtract(SessionValues[v.Key]).TotalMinutes;
                        if (diff >= F4SDAnalyticsDb.SessionTimeout)
                        {
                            var userSessions = new cF4SDUserSessionParameters();
                            userSessions.SessionId = v.Key;
                            userSessions.SessionDate = currentTimeStamp;
                            _toClose.Add(userSessions);
                        }
                    }
                }

                foreach (var s in _toClose)
                {
                    var _requestInfo = new cF4sdWebRequestInfo("CheckSessionsAsync", s.SessionId.ToString());
                    await CloseUserSessionAsync(s, _requestInfo, 1, CancellationToken.None);
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async void CheckCasesAsync(object sender, System.Timers.ElapsedEventArgs e)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var requestInfo = new cF4sdWebRequestInfo("CheckCasesAsync", null);
            if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceStart(0, requestInfo.requestName, requestInfo.id, requestInfo.created); }
            var apiError = 0;

            try
            {
                var currentTimeStamp = DateTime.UtcNow;
                var _toClose = new List<cF4SDCaseTimeParameters>();
                lock (CaseValues)
                {
                    foreach (var v in CaseValues)
                    {
                        var diff = (int)currentTimeStamp.Subtract(CaseValues[v.Key]).TotalMinutes;
                        if (diff > F4SDAnalyticsDb.CaseTimeout)
                        {
                            var caseData = new cF4SDCaseTimeParameters();
                            caseData.CaseId = v.Key;
                            caseData.StatusId = CaseStatus.Canceled;
                            _toClose.Add(caseData);
                        }
                    }
                }

                foreach (var s in _toClose)
                {
                    var requestInfo2 = new cF4sdWebRequestInfo("CheckCasesAsync", s.CaseId?.ToString());
                    await UpdateCaseStateAsync(s, requestInfo2, 1, CancellationToken.None);
                }

            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(0, requestInfo.requestName, requestInfo.id, requestInfo.created, requestInfo.created, ErrorCode: apiError); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        private async Task LoadGloablConfigValuesAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                DoProcessUiMessage(1, $"start loading database config values...");

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetConfigValues", out var Query))
                    return;

                GlobalConfigValues.Clear();
                int apiError = 0;

                try
                {
                    using (var _reader = await DataHistorySqlHelper.GetDataReaderAsync(DbConn, Query, null, Token))
                    {
                        if (_reader != null)
                            try
                            {
                                if (_reader.HasRows)
                                {
                                    while (await _reader.ReadAsync())
                                    {
                                        try
                                        {
                                            var Key = _reader.GetString(0);
                                            var Value = _reader.GetString(1);
                                            GlobalConfigValues[Key] = Value;
                                        }
                                        catch (Exception E)
                                        {
                                            cLogManager.DefaultLogger.LogException(E);
                                        }

                                        if (Token.IsCancellationRequested)
                                            return;
                                    }
                                }
                            }
                            catch (Exception E)
                            {
                                cLogManager.DefaultLogger.LogException(E);
                            }

                    }
                }
                catch (Exception E)
                {
                    apiError = E.HResult;
                    LogException(E);
                }
                finally
                {
                    if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, "", apiError, requestInfo?.requestName);
                }

                await GetSecTokenKeyAsync(DbConn, requestInfo, LogDeep + 1, Token);

                DoProcessUiMessage(1, "loading database config values finished.");
                DoProcessUiMessage(0, "");
            }
            catch (Exception E)
            {
                DoProcessUiMessage(1, $"loading global configuraton aborted with an exceptional error.");
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        private async Task GetSecTokenKeyAsync(cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (GlobalConfigValues.TryGetValue("Security.AccessTokenKey", out var _encKey))
                {
                    var _sec = cSecurePassword.Instance.Decode(_encKey);
                    BearerSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_sec));
                }
                else
                {
                    var _sec = Guid.NewGuid().ToString() + Guid.NewGuid().ToString() + Guid.NewGuid().ToString() + Guid.NewGuid().ToString();
                    _sec = _sec.Replace("-", "");
                    BearerSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_sec));

                    _sec = cSecurePassword.Instance.Encode(_sec);
                    await UpdateConfigValueAsync("Security.AccessTokenKey", _sec, DbConn, requestInfo, LogDeep + 1, Token);
                }

                JwtTokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidIssuer = JwtTokenIssuer,
                    ValidAudience = JwtTokenAudience,
                    IssuerSigningKey = BearerSecurityKey
                };
            }

            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);
            }
        }

        public string GenerateJsonWebToken(cF4sdUserInfo UserInfo)
        {

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

                var credentials = new SigningCredentials(BearerSecurityKey, SecurityAlgorithms.HmacSha256);

                var claims = new List<Claim> {
                    new Claim(JwtRegisteredClaimNames.NameId, UserInfo.Id.ToString()),
                    new Claim(JwtRegisteredClaimNames.UniqueName, UserInfo.Name),
                    new Claim(JwtRegisteredClaimNames.AuthTime, UserInfo.LogonTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'z'")),
                    new Claim("login_method", UserInfo.AccountType.ToString()),
                    new Claim("login_account", UserInfo.AdAccount.ToString()),
                    new Claim("account_sid", UserInfo.AdSid),
                    new Claim("language", UserInfo.Language ?? "")
                };
                if (UserInfo.Roles?.Count > 0)
                    foreach (var _roleEntry in UserInfo.Roles)
                        claims.Add(new Claim("roles", _roleEntry));

                UserInfo.LogonTime = DateTime.UtcNow;
                UserInfo.RenewUntil = UserInfo.LogonTime.AddMinutes(90);
                UserInfo.ValidUntil = UserInfo.LogonTime.AddMinutes(120);
                var token = new JwtSecurityToken(JwtTokenIssuer,
                    JwtTokenAudience,
                    claims,
                    notBefore: UserInfo.LogonTime.AddMinutes(-1),
                    expires: UserInfo.ValidUntil,
                    signingCredentials: credentials);

                var retVal2 = JwtTokenHandler.WriteToken(token);
                return retVal2;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        private async Task UpdateConfigValueAsync(string Key, string Value, cDbConnection DbConn, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateConfigValue", out var Query))
                    return;

                var _params = new Dictionary<string, object>()
                {
                    { "Key", Key},
                    { "Value", Value},
                };

                await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, _params, requestInfo, Token);
            }
            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);
            }
        }

        public async Task<bool> CreateCaseAsync(cF4SDCaseParameters Param, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 anonymous = InfrastructureConfig.F4SDAnalyticsDB?.AnonymousUser;
                var DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
                if (DbConnection == null)
                    return false;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("CreateCase", out var Query))
                    return false;

                using (var DbConn = new cDbConnection(DbConnection))
                    if (DbConn.IsOpen)
                    {
                        if (anonymous != null && (bool)anonymous)
                        {
                            Param.UserId = GetUserHashGuid((Guid)Param.UserId);
                        }
                        if (Param.SessionId != null && Param.SessionId != Guid.Empty)
                        {
                            if (Param.CaseId != null && Param.CaseId != Guid.Empty)
                            {
                                var Params = new Dictionary<string, object>() { { "SessionId", Param.SessionId }, { "CaseId", Param.CaseId }, { "UserId", Param.UserId } };

                                var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                                if (n >= 1)
                                {
                                    DoProcessUiMessage(2, $"Case is started. CaseId: " + Param.CaseId.ToString(), LogLevels.Debug);
                                    lock (CaseValues)
                                        CaseValues.Add((Guid)Param.CaseId, DateTime.UtcNow);
                                    return true;
                                }

                                DoProcessUiMessage(2, $"the insert  of the Case-data into F4SDAnalytics database  with exception error.", LogLevels.Warning);
                                return false;
                            }
                            DoProcessUiMessage(2, $"API Call - CreateCase  Invalid CaseId: 00000000-0000-0000-0000-000000000000  of the Case-data into F4SDAnalytics database.", LogLevels.Warning);
                            return false;
                        }
                        DoProcessUiMessage(2, $"API Call - CreateCase - Invalid SessionId: 00000000-0000-0000-0000-000000000000  of the Case-data into F4SDAnalytics database.", LogLevels.Warning);
                        return false;
                    }

                return false;

            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async Task<bool> UpdateCaseStateAsync(cF4SDCaseTimeParameters Param, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
                if (DbConnection == null)
                    return false;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateCaseState", out var Query))
                    return false;
                using (var DbConn = new cDbConnection(DbConnection))
                    if (DbConn.IsOpen)
                    {
                        if (Param.CaseId != null && Param.CaseId != Guid.Empty)
                        {
                            XElement ParamState = null;
                            if (Param.CaseTimes != null && Param.CaseTimes.Count > 0)
                            {
                                ParamState = new XElement("Root",
                                from prm in Param.CaseTimes
                                select new XElement("Item",
                                    new XElement("StatusId", (int)prm.StatusId),
                                    new XElement("TimeStamp", prm.CaseTime)
                            ));
                            }

                            var Params = new Dictionary<string, object>() { { "CaseId", Param.CaseId }, { "StatusId", Param.StatusId }, { "ActiveTime", Param.ActiveTime }, { "StatusXML", ParamState?.ToString() ?? String.Empty } };

                            var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                            if (n >= 1)
                            {
                                if (Param.StatusId == CaseStatus.Canceled || Param.StatusId == CaseStatus.Finished)
                                {
                                    DoProcessUiMessage(2, $"Case is closed. CaseId: " + Param.CaseId.ToString(), LogLevels.Debug);
                                    lock (CaseValues)
                                        CaseValues.Remove((Guid)Param.CaseId);
                                }
                                return true;
                            }

                            DoProcessUiMessage(2, $"Current CaseId: " + Param.CaseId.ToString() + " could not be found", LogLevels.Debug);
                            return false;
                        }
                        DoProcessUiMessage(2, $"API Call UpdateCaseState - Invalid CaseId: 00000000-0000-0000-0000-000000000000  of the Case-data into F4SDAnalytics database.", LogLevels.Warning);
                        return false;
                    }

                return false;

            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async Task<bool> ReportQuickActionExecutionAsync(cF4SDQuickActionParameters Param, cDataHistoryConfigSqlConnection DBConnection, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (DBConnection != null && DBConnection.Database == InfrastructureConfig.F4SDAnalyticsDB?.Connection.Database
                    && DataHistorySqlHelper.GetWellKnownSqlStatement("AnalyticsReportQuickActionExecution", out var Query))
                {

                    using (var DbConn = new cDbConnection(DBConnection))
                        if (DbConn.IsOpen)
                        {
                            if (Param.CaseId != null && Param.CaseId != Guid.Empty)
                            {
                                var Params = new Dictionary<string, object>() { { "CaseId", Param.CaseId }, { "QActionId", Param.QuickActionId },
                                                                        { "DeviceId", Param.DeviceId },
                                                                        { "Start", Param.Start }, { "Finish", Param.Finish },
                                                                        { "Parameters", Param.Paramaters }, { "Result", Param.Result },
                                                                        { "ErrorCode", Param.ErrorCode }, { "ErrorDescription", Param.ErrorDescription },
                                                                        { "ExecutionType", Param.ExecutionType.ToString() },{ "ResultType", Param.ResultType.ToString() }};

                                var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                                if (n >= 1)
                                {
                                    return true;
                                }

                                DoProcessUiMessage(2, $"the insert  of the QuickActionExecution-data into F4SDAnalytics database  with exception error.", LogLevels.Warning);
                                return false;
                            }
                            DoProcessUiMessage(2, $"API Call ReportQuickActionExecution - Invalid CaseId: 00000000-0000-0000-0000-000000000000  of the QuickActionExecution-data into F4SDAnalytics database.", LogLevels.Warning);
                            return false;
                        }

                }
                if (DBConnection != null && DBConnection.Database == InfrastructureConfig.HistoryDB.Connection.Database
                    && DataHistorySqlHelper.GetWellKnownSqlStatement("ReportQuickActionExecution", out var Query2))
                {
                    using (var DbConn = new cDbConnection(DBConnection))
                        if (DbConn.IsOpen)
                        {
                            var Params = new Dictionary<string, object>() { { "UserId", Param.UserId }, { "QActionId", Param.QuickActionId },
                                                                        { "DeviceId", Param.DeviceId },{ "QActionName", Param.QuickActionName },
                                                                        { "Start", Param.Start }, { "Finish", Param.Finish },
                                                                        { "Parameters", Param.Paramaters }, { "Result", Param.Result },
                                                                        { "ErrorCode", Param.ErrorCode }, { "ErrorDescription", Param.ErrorDescription },
                                                                        { "ExecutionType", Param.ExecutionType.ToString() },{ "ResultType", Param.ResultType.ToString() }};

                            var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query2, Params, requestInfo, Token);
                            if (n >= 1)
                            {
                                return true;
                            }

                            DoProcessUiMessage(2, $"the insert  of the QuickActionExecution-data into History database  with exception error.");
                            return false;
                        }
                }
                return false;

            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async Task<bool> CreateUserSessionAsync(cF4SDUserSessionParameters Param, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 anonymous = InfrastructureConfig.F4SDAnalyticsDB?.AnonymousUser;
                var DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;

                if (anonymous != null && (bool)anonymous)
                {
                    Param.UserId = GetUserHashGuid((Guid)Param.UserId);
                }

                if (DbConnection == null)
                    return false;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("CreateUserSession", out var Query))
                    return false;
                using (var DbConn = new cDbConnection(DbConnection))
                    if (DbConn.IsOpen)
                    {
                        if (Param.SessionId != null && Param.SessionId != Guid.Empty)
                        {
                            var Params = new Dictionary<string, object>() { { "SessionId", Param.SessionId }, { "UserId", Param.UserId }, { "SessionDate", Param.SessionDate } };
                            var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                            if (n >= 1)
                            {
                                DoProcessUiMessage(2, $"Session is started. SessionId: " + Param.SessionId.ToString(), LogLevels.Debug);
                                lock (SessionValues)
                                    SessionValues[(Guid)Param.SessionId] = DateTime.UtcNow;

                                return true;
                            }

                            DoProcessUiMessage(2, $"the insert  of the Usersession-data into F4SDAnalytics database  with exception error.", LogLevels.Warning);
                            return false;

                        }
                        DoProcessUiMessage(2, $"API Call CreateUserSession - Invalid SessionId: 00000000-0000-0000-0000-000000000000  of the Usersession-data into F4SDAnalytics database.", LogLevels.Warning);
                        return false;


                    }

                return false;
            }
            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;
        }

        public async Task<bool> CloseUserSessionAsync(cF4SDUserSessionParameters Param, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
                if (DbConnection == null)
                    return false;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("CloseUserSession", out var Query))
                    return false;
                using (var DbConn = new cDbConnection(DbConnection))
                    if (DbConn.IsOpen)
                    {
                        if (Param.SessionId != null && Param.SessionId != Guid.Empty)
                        {
                            var Params = new Dictionary<string, object>() { { "SessionId", Param.SessionId }, { "SessionDate", Param.SessionDate } };

                            var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                            if (n >= 1)
                            {
                                DoProcessUiMessage(2, $"Session is closed. SessionId: " + Param.SessionId.ToString(), LogLevels.Debug);
                                lock (SessionValues)
                                    SessionValues.Remove((Guid)Param.SessionId);

                                return true;
                            }

                            DoProcessUiMessage(2, $"the update  of the Usersession-data into F4SDAnalytics database  with exception error.", LogLevels.Warning);
                            if (cLogManager.DefaultLogger.IsDebug)
                            {
                                var _msg = new List<string>()
                            {
                                $"query: {Query}",
                                $"session id: {Param.SessionId}",
                                $"session date: {Param.SessionDate}",
                            };
                                cLogManager.DefaultLogger.LogList(LogLevels.Debug, _msg);
                            }
                            return false;
                        }
                        DoProcessUiMessage(2, $"API Call CreateUserSession - Invalid SessionId: 00000000-0000-0000-0000-000000000000  of the Usersession-data into F4SDAnalytics database.", LogLevels.Warning);
                        return false;

                    }

                return false;

            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async Task<bool> UpdateCaseAsync(Guid CaseId, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
                if (DbConnection == null)
                    return false;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateCases", out var Query))
                    return false;
                using (var DbConn = new cDbConnection(DbConnection))
                    if (DbConn.IsOpen)
                    {
                        if (CaseId != null && CaseId != Guid.Empty)
                        {
                            var Params = new Dictionary<string, object>() { { "CaseId", CaseId } };

                            var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                            if (n >= 1)
                            {
                                DoProcessUiMessage(2, $"Casestatus is updated. CaseId: " + CaseId.ToString(), LogLevels.Debug);
                                lock (CaseValues)
                                    CaseValues.Add(CaseId, DateTime.UtcNow);
                                return true;
                            }

                            DoProcessUiMessage(2, $"the update  of the CaseStatus with CaseId '{CaseId}'  into F4SDAnalytics database returned no legal update count ({n}).", LogLevels.Warning);
                            return false;
                        }
                        DoProcessUiMessage(2, $"API Call  KeepAliveCase - Invalid CaseId: 00000000-0000-0000-0000-000000000000 to update  of the CaseStatus  into F4SDAnalytics database.", LogLevels.Warning);
                        return false;
                    }

                return false;

            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async Task<bool> ReportTicketAsync(cApiM42Ticket Param, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 anonymous = InfrastructureConfig.F4SDAnalyticsDB?.AnonymousUser;
                Guid.TryParse(requestInfo.userInfo?.Id.ToString(), out Guid userGuid);
                if (anonymous != null && (bool)anonymous)
                    Param.User = GetUserHashGuid(userGuid).ToString();
                else
                    Param.User = userGuid.ToString();

                var DbConnection = InfrastructureConfig.F4SDAnalyticsDB?.Connection;
                if (DbConnection == null)
                    return false;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("InsertTicket", out var Query))
                    return false;


                using (var DbConn = new cDbConnection(DbConnection))
                    if (DbConn.IsOpen)
                    {

                        if (Param.Ticket != null && Param.Ticket != Guid.Empty)
                        {
                            if (Param.CaseId != null && Param.CaseId != Guid.Empty)
                            {
                                var startTime = Param.workTimes.FirstOrDefault(x => x.Key == "StartTime").Value;
                                var endTime = Param.workTimes.FirstOrDefault(x => x.Key == "EndTime").Value;
                                var workingTime = Param.workTimes.FirstOrDefault(x => x.Key == "NettoWorkingTime").Value;

                                var Params = new Dictionary<string, object>() { { "TicketId", Param.Ticket }, { "Status", Param.StatusIdValue }, { "CaseId", Param.CaseId }, { "UserId", Param.User }, { "Start", startTime }, { "Finish", endTime }, { "ActiveTime", workingTime } };

                                var n = await DataHistorySqlHelper.ExecuteAsync(DbConn, Query, Params, requestInfo, Token);
                                if (n >= 1)
                                {
                                    DoProcessUiMessage(2, $"Ticket-Data is saved . TicketNumber: " + Param.Ticket, LogLevels.Debug);
                                    lock (CaseValues)
                                        CaseValues.Add((Guid)Param.CaseId, DateTime.UtcNow);
                                    return true;
                                }

                                DoProcessUiMessage(2, $"the insert  of the Ticket-data into F4SDAnalytics database  with exception error.", LogLevels.Warning);
                                return false;
                            }
                            DoProcessUiMessage(2, $"API Call - InsertTicket  Invalid CaseId: 00000000-0000-0000-0000-000000000000  of the Ticket-data into F4SDAnalytics database.", LogLevels.Warning);
                            return false;
                        }
                        DoProcessUiMessage(2, $"API Call - InsertTicket - Invalid TicketId: 00000000-0000-0000-0000-000000000000  of the Ticket-data into F4SDAnalytics database.", LogLevels.Warning);
                        return false;
                    }

                return false;

            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public bool KeepAliveSession(Guid SessionId, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (SessionId != null && SessionId != Guid.Empty)
                {
                    lock (SessionValues)
                        SessionValues[SessionId] = DateTime.UtcNow;

                    return true;
                }

                if (cLogManager.DefaultLogger.IsDebug) LogEntry($"API Call  KeepAliveCase - Invalid CaseId: 00000000-0000-0000-0000-000000000000 ", LogLevels.Warning);
                return false;
            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        public async Task<bool> KeepAliveCase(Guid CaseId, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (CaseId != null && CaseId != Guid.Empty)
                {
                    if (CaseValues.ContainsKey(CaseId))
                    {
                        lock (CaseValues)
                            CaseValues[CaseId] = DateTime.UtcNow;
                    }
                    else
                    {
                        await UpdateCaseAsync(CaseId, requestInfo, LogDeep + 1, Token);
                    }
                    return true;
                }
                if (cLogManager.DefaultLogger.IsDebug) LogEntry($"API Call  KeepAliveCase - Invalid CaseId: 00000000-0000-0000-0000-000000000000 ", LogLevels.Warning);
                return false;

            }
            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;
        }

        public void DoProcessUiMessage(int Indent, string Message, LogLevels logLevel = LogLevels.Info)
        {
            try
            {
                var strIndent = new string(' ', Indent * 2);
                cLogManager.DefaultLogger.LogEntry(logLevel, strIndent + Message);

                ProcessUiMessage?.Invoke(cDataHistoryUiStatusMessage.Create(Indent, Message));
            }
            catch { }
        }

        public void DoProcessUiMessage(int Indent, cXmlParserNodeMessage Message)
        {
            DoProcessUiMessage(Indent, cXmlParser.GetMessageString(Message));
        }

        public void DoProcessUiMessage(int Indent, List<cXmlParserNodeMessage> Messages)
        {
            foreach (var Entry in Messages)
                DoProcessUiMessage(Indent, Entry);
        }

        public void CleanupUiMessageHandler()
        {
            if (ProcessUiMessage != null)
            {
                foreach (var Entry in ProcessUiMessage.GetInvocationList())
                    try
                    {
                        ProcessUiMessage -= Entry as DataHistoryUiMessageFunc;
                    }
                    catch { }
                ProcessUiMessage = null;
            }

        }

        internal class cFasdApiSearchResultExt
        {
            internal cFasdApiSearchResultEntry Result = new cFasdApiSearchResultEntry();
            internal string upn_internal = null;
            internal int? IdAgent;
        }

        public class cUsageData
        {
            internal Int32 id;
            internal Int64 duration;
            internal DateTime lastEvent;
        }


        public async Task<cF4sdUserInfo> GetCurrentUserInfoAsync(CancellationToken Token, 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
            {
                if (!ActiveDirectory.WinAuthenticationGropsResolved)
                {
                    ActiveDirectory.ResolveWinAuthenticationGroups();

                    if (!ActiveDirectory.WinAuthenticationGropsResolved)
                    {
                        LogEntry("Could not resolve authentication groups", LogLevels.Warning);
                        return null;
                    }
                }

                WindowsIdentity user = WindowsIdentity.GetCurrent();
                return await GetWinUserInfoAsync(user, CultureInfo.CurrentCulture.DisplayName, true, Token, requestInfo, logDeep + 1);
            }
            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 null;

        }

        public async Task<cF4sdUserInfo> GetWinUserInfoAsync(IIdentity Identity, string Language, bool justWinRoles, CancellationToken Token, 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
            {
                if (Identity == null)
                    return null;

                if (!(Identity is WindowsIdentity _winUser))
                {
                    LogEntry($"could not logon with specified identity due to incompatible authentication type (name={Identity.Name}, type={Identity.AuthenticationType}");
                    return null;
                }
                if (!_winUser.IsAuthenticated)
                {
                    LogEntry($"the specified winuser is not authenticated (name={Identity.Name}, type={Identity.AuthenticationType}");
                    return null;
                }

                var _sid = _winUser.User.ToString();
                var _retVal = new cF4sdUserInfo()
                {
                    Id = Guid.Empty,
                    LogonTime = DateTime.UtcNow,
                    AccountType = cF4sdUserInfo.enumAccountType.AdUser,
                    AdAccount = _winUser.Name,
                    Name = _winUser.Name,
                    AdSid = _sid,
                    Language = Language
                };

                LogEntry($"The authenticated user is: name={Identity.Name}, type={Identity.AuthenticationType}");

                bool IsEntraIdDevelop = false;

                if (!justWinRoles)
                {
                    if (!string.IsNullOrEmpty(_sid))
                    {
                        Dictionary<string, List<cFasdApiSearchResultExt>> _ids = null;
                        if (RunningInIISExpress)
                        {
                            // check if we are in EntraID develop mode (running in IIS Express and the user is an Entra ID User)
                            Guid azureId = Guid.Empty;
                            azureId = cDataHistoryCollectorActiveDirectory.ConvertAzureSidToGuid(_sid);
                            if (azureId != Guid.Empty)
                                _ids = await UserSearchByEntraIdDevelopAsync(azureId, Token, requestInfo, LogDeep + 1);
                        }
                        if (_ids == null || _ids.Count == 0)
                            _ids = await UserSearchBySidsAsync(new List<string> { _sid }, Token, requestInfo, LogDeep + 1);
                        else
                            IsEntraIdDevelop = true;

                        if (Token.IsCancellationRequested)
                            return null;

                        if (_ids?.Count > 0)
                        {
                            var _entry = _ids?.First().Value;
                            if (_entry?.Count > 0)
                            {
                                var _result = _entry.First().Result;
                                if (_result != null)
                                {
                                    _retVal.Name = _result.Name;
                                    _retVal.Id = _result.id;
                                }
                            }
                        }
                    }

                    if (_retVal.Id == Guid.Empty)
                    {
                        LogEntry($"Could not find user {_winUser.Name} with SID {_sid} for authentication in table 'main-user'.", LogLevels.Warning);
                        LogEntry($"Please check, if at least one of the defined AD user scan nodes contains this user.", LogLevels.Warning);
                        return null;
                    }

                }

                var _membershipGroups = InfrastructureConfig?.Authorisation?.MembershipGroups?.GroupsAd;
                var _resMembershipGroups = new List<cF4SDMembershipGroup>();

                if (IsEntraIdDevelop)
                {
                    // if we are in EntraID develop mode, we have fake all ad group memberships
                    if (_membershipGroups != null)
                        foreach (var _entryDom in _membershipGroups.Values)
                            foreach (var _entryGroup in _entryDom.Values)
                                _resMembershipGroups.Add(_entryGroup);
                }
                else
                {
                    var _dom = Domain.GetComputerDomain();
                    var _pc = new PrincipalContext(ContextType.Domain);
                    var _groups = _winUser.Groups;


                    var _msgLog = new List<string>();
                    if (cLogManager.DefaultLogger.IsDebug)
                    {
                        _msgLog.Add($"the winuser {_retVal.AdAccount} logged on");
                        _msgLog.Add($"  is member of ad security groups:");
                    }
                    foreach (var _group in _groups)
                    {
                        if (Token.IsCancellationRequested)
                            return null;
                        if (_group is SecurityIdentifier _si)
                        {
                            cF4SDMembershipGroupAd _memGrp = null;
                            if (_membershipGroups != null)
                                foreach (var _entryDom in _membershipGroups.Values)
                                {
                                    foreach (var _entryGroup in _entryDom.Values)
                                    {
                                        if (_si.Value == _entryGroup.SID)
                                        {
                                            _resMembershipGroups.Add(_entryGroup);
                                            _memGrp = _entryGroup;

                                        }
                                    }
                                }

                            if (DefaultLogger.IsDebug)
                            {
                                try
                                {
                                    var _gp = GroupPrincipal.FindByIdentity(_pc, IdentityType.Sid, _si.Value);
                                    if (_gp != null)
                                    {
                                        if (_memGrp != null)
                                            _msgLog.Add($"    {_gp.SamAccountName} ({_gp.DistinguishedName}) is member of '{_memGrp.Name}'");
                                        else
                                            _msgLog.Add($"    {_gp.SamAccountName} ({_gp.DistinguishedName})");
                                    }
                                    else
                                        _msgLog.Add($"    could not find group {_si.Value}");
                                }
                                catch (Exception E)
                                {
                                    LogException(E);
                                }
                            }
                        }
                    }

                    if (cLogManager.DefaultLogger.IsDebug)
                        cLogManager.DefaultLogger.LogList(LogLevels.Debug, _msgLog);

                }

                if (_membershipGroups?.Count > 0)
                    AddRolesByMembership(_retVal, _resMembershipGroups);

                if (Token.IsCancellationRequested)
                    return null;

                if (!string.IsNullOrEmpty(_retVal.AdSid) && (M42WpmCollector != null))
                    await M42WpmCollector.AddM42UserInfosAsync(_retVal, Token, requestInfo, LogDeep + 1);

                var _token = GenerateJsonWebToken(_retVal);

                _retVal.Token = _token;

                return _retVal;
            }
            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 null;
        }


        private async Task<Dictionary<string, List<cFasdApiSearchResultExt>>> searchSqlAsync(string strQuery, Dictionary<string, object> SqlParam, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, string QueryName, string PhoneSearch = null, bool SupportCancellation = 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;

            int apiError = 0;

            try
            {
                if (strQuery == null)
                    return null;

                using (var DbConn = new cDbConnection(mainDbConnection, Timeout: 10))
                {
                    if (!DbConn.IsOpen)
                        return null;

                    using (var Reader = await DataHistorySqlHelper.GetTableResultAsync(DbConn.Connection, strQuery, SqlParam, Token, SupportCancellation: SupportCancellation))
                    {
                        var output = new Dictionary<string, List<cFasdApiSearchResultExt>>();

                        if (Reader != null)
                            try
                            {
                                if (Reader.HasRows)
                                {
                                    var maxStateVal = Enum.GetValues(typeof(enumF4sdSearchResultStatus)).Cast<int>().Max<int>();
                                    var minStateVal = Enum.GetValues(typeof(enumF4sdSearchResultStatus)).Cast<int>().Min<int>();

                                    while (await Reader.ReadAsync(Token))
                                    {
                                        try
                                        {
                                            var Entry = new cFasdApiSearchResultExt();
                                            var intType = Reader.GetInt32(0);
                                            Entry.Result.Type = (enumF4sdSearchResultClass)intType;

                                            Entry.upn_internal = Reader.IsDBNull(4) ? null : Reader.GetString(4);

                                            Entry.IdAgent = Reader.IsDBNull(5) ? (int?)null : Reader.GetInt32(5);

                                            var strName = Reader.GetString(1);
                                            if (PhoneSearch != null && Entry.Result.Type == enumF4sdSearchResultClass.Phone) // phone type
                                            {
                                                string strPhone = null;
                                                var strPh = Reader.IsDBNull(8) ? null : Reader.GetString(8);
                                                if (strPh != null)
                                                {
                                                    var arrPh = strPh.Split(' ');
                                                    foreach (var str in arrPh)
                                                    {
                                                        if (str.EndsWith(PhoneSearch))
                                                        {
                                                            strPhone = str;
                                                            break;
                                                        }
                                                    }
                                                }
                                                if (strPhone != null)
                                                {
                                                    Entry.Result.Name = strName;
                                                    Entry.Result.DisplayName = $"{strName} ({strPhone})";
                                                }
                                                else
                                                {
                                                    Entry.Result.Name = strName;
                                                    Entry.Result.DisplayName = strName;
                                                }


                                            }
                                            else
                                            {
                                                Entry.Result.Name = strName;
                                                Entry.Result.DisplayName = strName;
                                            }
                                            Entry.Result.id = Reader.GetFieldValue<Guid>(2);

                                            if (!Reader.IsDBNull(3))
                                            {
                                                var intStatus = Reader.GetInt32(3);
                                                if (intStatus <= maxStateVal && intStatus >= minStateVal)
                                                    Entry.Result.Status = (enumF4sdSearchResultStatus)intStatus;
                                            }

                                            var account = Reader.IsDBNull(6) ? null : Reader.GetString(6);
                                            var domain = Reader.IsDBNull(7) ? null : Reader.GetString(7);
                                            Entry.Result.Infos = new Dictionary<string, string>(2)
                                    {
                                        {"account", account },
                                        {"domain", domain },
                                    };

                                            if (output.TryGetValue(strName, out var lst))
                                            {
                                                lst.Add(Entry);
                                            }
                                            else
                                            {
                                                if (output.Count > 11)
                                                    break;

                                                lst = new List<cFasdApiSearchResultExt> { Entry };
                                                output.Add(strName, lst);
                                            }
                                        }
                                        catch (Exception E)
                                        {
                                            cLogManager.DefaultLogger.LogException(E);
                                        }
                                    }
                                }
                            }
                            catch (Exception E)
                            {
                                if (!Token.IsCancellationRequested || !SupportCancellation)
                                    cLogManager.DefaultLogger.LogException(E);
                            }

                        return output;
                    }

                }
            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(QueryName, _startTime, SqlParam, apiError, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return null;

        }

        private async Task<Dictionary<string, List<cFasdApiSearchResultExt>>> searchStringAsync(string search, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, string Field = null, bool Reverse = 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
            {

                var IsPhone = this.InfrastructureConfig.HistoryDB.SearchForPhoneNumbers && isDigits(search);

                string strQuery = null;
                if (Field == null)
                {
                    if (IsPhone)
                    {
                        strQuery = "SearchFulltextRev";
                        Reverse = true;
                    }
                    else
#if SIMULATE_SLOW_SEARCH
                        strQuery = "SearchFulltextWithDelay";
#else
                        strQuery = "SearchFulltext";
#endif
                }
                else
                {
                    if (Reverse)
                        strQuery = "SearchByFieldRev";
                    else
                        strQuery = "SearchByField";
                }

                var SqlParam = new Dictionary<string, object>();

                if (Field == null || !Reverse)
                {
                    var searchMask = $"\"{search}*\"";
                    SqlParam.Add("SEARCH", searchMask);
                }

                if (Reverse)
                {
                    char[] arrRev = (search + 'x').ToArray();
                    Array.Reverse(arrRev);
                    var strRev = new string(arrRev);
                    strRev = $"\"{strRev}*\"";
                    SqlParam.Add("REV", strRev);
                }

                SqlParam.Add("DAYS", this.InfrastructureConfig.ClientAgent.MaxDeviceAge);

                if (strQuery == null)
                    return null;

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement(strQuery, out var Query))
                    return null;

                strQuery = Query.Query;
                if (Field != null)
                    strQuery = string.Format(strQuery, Field);

                string strPhoneSearch = null;
                if (IsPhone)
                    strPhoneSearch = search;

                return await searchSqlAsync(strQuery, SqlParam, Token, requestInfo, LogDeep + 1, Query.Name, strPhoneSearch, 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 null;
        }

        private async Task<Dictionary<string, List<cFasdApiSearchResultExt>>> searchComputerNameAsync(string search, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, string Field = null, bool Reverse = 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 strQuery = "SearchComputerByName";
                var SqlParam = new Dictionary<string, object>();

                if (Field == null || !Reverse)
                {
                    var searchMask = $"\'{search}%\'";
                    SqlParam.Add("name", search + "%");
                }

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement(strQuery, out var Query))
                    return null;

                strQuery = Query.Query;
                if (Field != null)
                    strQuery = string.Format(strQuery, Field);

                return await searchSqlAsync(strQuery, SqlParam, Token, requestInfo, LogDeep + 1, Query.Name, SupportCancellation: 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 null;
        }

        public async Task<List<cFasdApiSearchResultEntry>> DefaultSearchAsync(string search, CancellationToken Token, 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
            {
                List<Task<bool>> taskList = new List<Task<bool>>();
                var RetVal = new List<cFasdApiSearchResultEntry>();
                var SearchWithLike = this.InfrastructureConfig.HistoryDB.SearchWithLike;
                if (SearchWithLike)
                {
                    var T1 = Task.Run(() => { return searchComputerNameAsync(search, Token, requestInfo, LogDeep + 1); });
                    var T2 = Task.Run(() => { return searchStringAsync(search, Token, requestInfo, LogDeep + 1); });
                    var output = await Task.WhenAll(T1, T2);

                    foreach (var Entry in output[0].Values)
                    {
                        if (Entry.Count == 1)
                            RetVal.Add(Entry[0].Result);
                        else if (Entry.Count > 1)
                        {
                            foreach (var Entry2 in Entry)
                            {
                                CompleteSearchEntry(Entry2);
                                RetVal.Add(Entry2.Result);
                            }
                        }
                    }

                    foreach (var Entry in output[1].Values)
                    {
                        if (RetVal.Count >= 11)
                            break;
                        bool alreadyExists = RetVal.Any(x => x.id == Entry[0].Result.id);
                        if (!alreadyExists)
                        {
                            if (Entry.Count == 1)
                                RetVal.Add(Entry[0].Result);
                            else if (Entry.Count > 1)
                            {
                                foreach (var Entry2 in Entry)
                                {
                                    CompleteSearchEntry(Entry2);
                                    RetVal.Add(Entry2.Result);
                                }
                            }
                        }

                    }
                }
                else
                {
                    var output = await searchStringAsync(search, Token, requestInfo, LogDeep + 1);

                    foreach (var Entry in output.Values)
                    {
                        if (Entry.Count == 1)
                            RetVal.Add(Entry[0].Result);
                        else if (Entry.Count > 1)
                        {
                            foreach (var Entry2 in Entry)
                            {
                                CompleteSearchEntry(Entry2);
                                RetVal.Add(Entry2.Result);
                            }
                        }
                    }
                }
                return RetVal;
            }
            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 null;
        }

        internal cFasdApiSearchResultExt CompleteSearchEntry(cFasdApiSearchResultExt entry)
        {
            var strDomain = "";
            if (entry.upn_internal != null)
            {
                var arr1 = entry.upn_internal.Split('@');
                if (arr1.Length > 1)
                {
                    var arr2 = arr1[1].Split('.');
                    if (arr2.Length > 0)
                        strDomain = arr2[0];
                }
            }

            var strDead = "";
            if (entry.IdAgent == null)
                strDead = "⚠ ";
            if (!string.IsNullOrEmpty(strDomain))
            {
                string c = strDomain[0].ToString().ToUpperInvariant();
                strDomain = c + strDomain.Remove(0, 1);
                entry.Result.Name += $" (" + strDead + strDomain + ")";
                entry.Result.DisplayName += $" (" + strDead + strDomain + ")";
            }
            return entry;
        }

        public Guid? DetailedSearchStart(string search, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var cancellationToken = new CancellationTokenSource();
                var _task = Task<cFasdApiSearchResultCollection>.Run(async () =>
                {
                    return await DetailedSearchAsync(search, cancellationToken.Token, requestInfo, LogDeep);
                });

                var RetVal = cDataHistoryCollectorSearchTaskCache.AddTask(cancellationToken, _task);

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

            return null;
        }

        public async Task<cFasdApiSearchResultCollection> DetailedSearchResultAsync(Guid taskID, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var _result = await cDataHistoryCollectorSearchTaskCache.GetResultAsync(taskID);
                return _result;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public async Task<cFasdApiSearchResultCollection> DetailedSearchAsync(string search, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var RetVal = new cFasdApiSearchResultCollection();
                var SearchWithLike = this.InfrastructureConfig.HistoryDB.SearchWithLike;
                if (SearchWithLike)
                {
                    var T1 = Task.Run(() => { return searchComputerNameAsync(search, Token, requestInfo, LogDeep + 1); });
                    var T2 = Task.Run(() => { return searchStringAsync(search, Token, requestInfo, LogDeep + 1); });
                    var output = await Task.WhenAll(T1, T2);

                    foreach (var Entry in output[0])
                    {
                        if (!RetVal.TryGetValue(Entry.Key, out var lst))
                        {
                            lst = new List<cFasdApiSearchResultEntry>();
                            RetVal.Add(Entry.Key, lst);
                        }

                        foreach (var Entry2 in Entry.Value)
                            lst.Add(Entry2.Result);
                    }

                    foreach (var Entry in output[1])
                    {
                        if (RetVal.Count >= 11)
                            break;
                        if (!RetVal.TryGetValue(Entry.Key, out var lst))
                        {
                            lst = new List<cFasdApiSearchResultEntry>();
                            RetVal.Add(Entry.Key, lst);
                            foreach (var Entry2 in Entry.Value)
                                lst.Add(Entry2.Result);
                        }

                    }
                }
                else
                {
                    var output = await searchStringAsync(search, Token, requestInfo, LogDeep + 1);

                    foreach (var Entry in output)
                    {
                        if (!RetVal.TryGetValue(Entry.Key, out var lst))
                        {
                            lst = new List<cFasdApiSearchResultEntry>();
                            RetVal.Add(Entry.Key, lst);
                        }

                        foreach (var Entry2 in Entry.Value)
                            lst.Add(Entry2.Result);
                    }
                }
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public async Task<cFasdApiSearchResultCollection> PhoneSearchAsync(string searchPhone, string searchName, CancellationToken Token, 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
            {
                Dictionary<string, List<cFasdApiSearchResultExt>>[] output;

                if (string.IsNullOrEmpty(searchName))
                {
                    var res1 = await searchStringAsync(searchPhone, Token, requestInfo, LogDeep + 1, "phone_rev", true);
                    output = new Dictionary<string, List<cFasdApiSearchResultExt>>[] { res1 };
                }
                else
                {
                    var T1 = Task.Run(() => { return searchStringAsync(searchPhone, Token, requestInfo, LogDeep + 1, "phone_rev", true); });
                    var T2 = Task.Run(() => { return searchStringAsync(searchName, Token, requestInfo, LogDeep + 1, "full_name", false); });
                    output = await Task.WhenAll(T1, T2);
                }

                var _tmpRetVal = new Dictionary<string, Dictionary<Guid, cFasdApiSearchResultEntry>>();
                foreach (var Entry3 in output)
                    foreach (var Entry in Entry3)
                    {
                        if (!_tmpRetVal.TryGetValue(Entry.Key, out var lst))
                        {
                            lst = new Dictionary<Guid, cFasdApiSearchResultEntry>();
                            _tmpRetVal.Add(Entry.Key, lst);
                        }

                        foreach (var Entry2 in Entry.Value)
                        {
                            bool replace = true;
                            if (lst.TryGetValue(Entry2.Result.id, out var _e))
                            {
                                if (_e.Type > Entry2.Result.Type)
                                    replace = false;
                            }
                            if (replace)
                                lst[Entry2.Result.id] = Entry2.Result;
                        }
                    }

                var RetVal = new cFasdApiSearchResultCollection();
                foreach (var Entry in _tmpRetVal)
                {
                    var lst = Entry.Value.Values.ToList();
                    RetVal[Entry.Key] = lst;
                }

                return RetVal;
            }
            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 null;
        }


        public async Task<Dictionary<string, List<cFasdApiSearchResultEntry>>> ComputerSearchByNameAsync(string Name, string Domain, CancellationToken Token, 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
            {
                string strQuery = null;
                var SqlParams = new Dictionary<string, object>(2) { { "name", Name } };
                if (string.IsNullOrWhiteSpace(Domain))
                    strQuery = "SearchComputerByName";
                else
                {
                    strQuery = "SearchComputerByNameAndDomain";
                    SqlParams.Add("domain", Domain);
                }

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement(strQuery, out var Query))
                    return null;

                var output = await searchSqlAsync(Query.Query, SqlParams, Token, requestInfo, LogDeep + 1, Query.Name, SupportCancellation: false);

                var RetVal = new Dictionary<string, List<cFasdApiSearchResultEntry>>();
                foreach (var Entry in output)
                {
                    if (!RetVal.TryGetValue(Entry.Key, out var lst))
                    {
                        lst = new List<cFasdApiSearchResultEntry>();
                        RetVal.Add(Entry.Key, lst);
                    }

                    foreach (var Entry2 in Entry.Value)
                        lst.Add(Entry2.Result);
                }

                return RetVal;
            }
            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 null;
        }

        internal async Task<Dictionary<string, List<cFasdApiSearchResultExt>>> UserSearchByEntraIdDevelopAsync(Guid AzureId, CancellationToken Token, 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 SqlParams = new Dictionary<string, object> { { "AzureId", AzureId } };

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("SearchUserByAzureIdDevelop", out var Query))
                    return null;

                var res = await searchSqlAsync(Query.Query, SqlParams, Token, requestInfo, LogDeep + 1, Query.Name, SupportCancellation: false);

                return res;
            }
            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 null;
        }

        internal async Task<Dictionary<string, List<cFasdApiSearchResultExt>>> UserSearchBySidsAsync(List<string> Sids, CancellationToken Token, 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 SqlParams = new Dictionary<string, object>(Sids.Count);
                var strIn = "";
                int i = 0;
                foreach (var Entry in Sids)
                {
                    var objSid = new SecurityIdentifier(Entry);
                    var arrB = new byte[objSid.BinaryLength];
                    objSid.GetBinaryForm(arrB, 0);
                    if (strIn != "")
                        strIn += ",";
                    strIn += "@SID" + i.ToString();
                    SqlParams.Add("SID" + i.ToString(), arrB);
                    i++;
                }

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("SearchUserBySid", out var Query))
                    return null;

                var strQuery = string.Format(Query.Query, strIn);
                var res = await searchSqlAsync(strQuery, SqlParams, Token, requestInfo, LogDeep + 1, Query.Name, SupportCancellation: false);

                return res;
            }
            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 null;
        }

        public async Task<Guid?> GetUserIdFromAccountAsync(string Account, string Domain, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (string.IsNullOrEmpty(Account) || string.IsNullOrEmpty(Domain))
                    return null;

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetUserIdFromAccount", out var Query))
                    return null;

                var Params = new Dictionary<string, object>()
                {
                    { "account", Account},
                    { "domain", Domain}
                };

                using (var Conn = new cDbConnection(mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return null;
                    }

                    var res = await DataHistorySqlHelper.GetScalarResultAsync<Guid?>(Conn, Query, Params, requestInfo, Token);

                    return res;
                }
            }
            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 null;
        }

        public async Task<cFasdApiSearchResultCollection> UserSearchByNameAndSidsAsync(string Name, List<string> SIDs, CancellationToken Token, 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
            {
                Dictionary<string, List<cFasdApiSearchResultExt>> output = null;

                if (SIDs != null && SIDs.Count != 0)
                    output = await UserSearchBySidsAsync(SIDs, Token, requestInfo, LogDeep + 1);

                if (output == null || output.Count == 0)
                    output = await searchStringAsync(Name, Token, requestInfo, LogDeep + 1);


                var RetVal = new cFasdApiSearchResultCollection();
                foreach (var Entry in output)
                {
                    if (!RetVal.TryGetValue(Entry.Key, out var lst))
                    {
                        lst = new List<cFasdApiSearchResultEntry>();
                        RetVal.Add(Entry.Key, lst);
                    }

                    foreach (var Entry2 in Entry.Value)
                        lst.Add(Entry2.Result);
                }

                return RetVal;
            }
            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 null;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool isDigits(string s)
        {
            if (s == null || s == "") return false;

            for (int i = 0; i < s.Length; i++)
                if ((s[i] ^ '0') > 9)
                    return false;

            return true;
        }

        public async Task<List<cDataHistoryInfoClassListEntry>> GetInfoClassListTest(enumFasdInformationClass infoClass, bool filtered, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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;

            var IsValid = false;
            var RetVal = new List<cDataHistoryInfoClassListEntry>();
            try
            {
                DoProcessUiMessage(0, $"Retrieving the {infoClass} list for testing purposes.");

                var queryName = $"Get{infoClass}ListTest";
                if (filtered)
                    queryName += "Filtered";
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement(queryName, out var Query))
                    return RetVal;

                using (var Conn = new cDbConnection(mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return RetVal;
                    }

                    int apiError = 0;

                    try
                    {
                        using (var Reader = await DataHistorySqlHelper.GetTableResultAsync(Conn, Query, null, Token))
                        {
                            if (Reader != null)
                                try
                                {
                                    IsValid = true;
                                    if (Reader.HasRows)
                                    {
                                        while (await Reader.ReadAsync())
                                        {
                                            try
                                            {
                                                var Entry = new cDataHistoryInfoClassListEntry
                                                {
                                                    Name = Reader.GetString(0),
                                                    id = Reader.GetGuid(1)
                                                };
                                                if (!string.IsNullOrWhiteSpace(Entry.Name) && (Entry.id != Guid.Empty))
                                                    RetVal.Add(Entry);
                                            }
                                            catch (Exception E)
                                            {
                                                cLogManager.DefaultLogger.LogException(E);
                                            }
                                        }
                                    }
                                }
                                catch (Exception E)
                                {
                                    IsValid = false;
                                    cLogManager.DefaultLogger.LogException(E);
                                }
                        }
                    }
                    catch (Exception E)
                    {
                        apiError = E.HResult;
                        LogException(E);
                    }
                    finally
                    {
                        if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, "", apiError, requestInfo?.requestName);
                    }

                }
            }
            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);
            }

            if (!IsValid)
                DoProcessUiMessage(1, $"Error while retrieving the {infoClass} list for testing purposes.");
            else
                DoProcessUiMessage(1, $"{RetVal.Count} {infoClass} entries found.");


            return RetVal;
        }

        public async Task<List<cF4sdApiSearchResultRelation>> GetUsageFromAgentData(enumFasdInformationClass infoClass, List<Guid> Ids, int Age, CancellationToken Token, 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 _relationService = GetStagedRelationService();
                if (_relationService == null)
                    return null;

                var _relatedTo = new List<cFasdApiSearchResultEntry>();
                foreach (var _idEntry in Ids)
                {
                    var _rel = new cFasdApiSearchResultEntry()
                    {
                        Type = cFasdApiSearchResultEntry.getSearchClass(infoClass),
                        id = _idEntry
                    };
                    _relatedTo.Add(_rel);
                }

                var taskId = await _relationService.StartGatheringRelatedObjects(_relatedTo, Age);
                if (Token.IsCancellationRequested)
                    return null;

                var _retVal = new List<cF4sdApiSearchResultRelation>();
                do
                {
                    var _rels = await _relationService.GetRelatedObjectsAsync(taskId.Id, Token);
                    if (Token.IsCancellationRequested)
                        return null;
                    if (_rels == null)
                        return null;

                    _retVal.AddRange(_rels.Relations);

                    if (_rels.IsComplete)
                        break;
                } while (true);

                return _retVal ;

            }
            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 null;
        }

        public async Task<List<cF4sdApiSearchResultRelation>> GetUsageFromOnlyAgentData(IEnumerable<cF4sdConnectorIds> ids, enumFasdInformationClass infoClass, int Age, CancellationToken Token, 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 _count = ids.Count();
                var Age2 = Age;
                if (_count == 1)
                    Age2 = 365;
                var AgentLimit = DateTime.UtcNow + TimeSpan.FromDays(-Age2);

                var lstAgentIds = new Dictionary<int, cDataHistoryUsageIdInfo>(_count);

                foreach (var Entry in ids)
                {
                    if (Entry.lastSeenByAgent != null && Entry.lastSeenByAgent >= AgentLimit)
                    {
                        if (Entry.agentId2 != null)
                            lstAgentIds[(int)Entry.agentId2] = new cDataHistoryUsageIdInfo() { Id = Entry.Id, AccountType = enumAccountType.Azure };
                        if (Entry.agentId != null)
                            lstAgentIds[(int)Entry.agentId] = new cDataHistoryUsageIdInfo() { Id = Entry.Id, AccountType = enumAccountType.AD };
                    }
                    if (Entry.localAgentIds != null)
                    {
                        foreach (var entryLocal in Entry.localAgentIds)
                        {
                            if (entryLocal.lastSeenByAgent >= AgentLimit)
                                lstAgentIds[entryLocal.agentId] = new cDataHistoryUsageIdInfo() { Id = Entry.Id, AccountType = enumAccountType.Local, account = entryLocal.account, domain = "" };
                        }
                    }
                }


                if (DefaultLogger.IsDebug)
                {
                    var _msgDebug = new List<string>()
                    {
                        $"{lstAgentIds?.Count} agent ids found by usage determination:"
                    };
                    foreach (var dbgEntry in lstAgentIds)
                        _msgDebug.Add($"{dbgEntry.Key}: accountType={dbgEntry.Value.AccountType}, account={dbgEntry.Value.account}, domain={dbgEntry.Value.domain}, id={dbgEntry.Value.Id}");
                    DefaultLogger.LogList(LogLevels.Debug, _msgDebug);
                }

                var taskList = new List<Task<List<cF4sdApiSearchResultRelation>>>(2);
                if (lstAgentIds.Count > 0)
                    taskList.Add(Task.Run<List<cF4sdApiSearchResultRelation>>(async () => { return await GetUsedInfoFromAgentData(infoClass, lstAgentIds, Age, Token, requestInfo, LogDeep + 1); }));

                if (taskList.Count == 0)
                    return null;

                var arrTask = taskList.ToArray();
                var RetVal = await Task.WhenAll<List<cF4sdApiSearchResultRelation>>(arrTask);

                var RetVal2 = new List<cF4sdApiSearchResultRelation>();
                foreach (var Entry in RetVal)
                {
                    if (Entry != null)
                        RetVal2.AddRange(Entry);
                }

                if (RetVal2.Count > 0)
                    return RetVal2;
            }
            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 null;
        }

        private async Task<List<cF4sdApiSearchResultRelation>> getTicketUsage(enumFasdInformationClass infoClass, List<cF4sdConnectorIds> lstTicketIds, int Age, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            return await M42WpmCollector?.getTicketUsage(lstTicketIds, Token, requestInfo, LogDeep);
        }

        private enumFasdInformationClass getResultClassForAgentUsage(enumFasdInformationClass infoClass)
        {
            switch (infoClass)
            {
                case enumFasdInformationClass.User:
                    return enumFasdInformationClass.Computer;
                case enumFasdInformationClass.Computer:
                    return enumFasdInformationClass.User;
            }
            return enumFasdInformationClass.Unknown;
        }

        private async Task<List<cF4sdApiSearchResultRelation>> GetUsedInfoFromAgentData(enumFasdInformationClass infoClass, Dictionary<int, cDataHistoryUsageIdInfo> AgentIdInfos, int Age, CancellationToken Token, 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 lstTasks = new List<Task<List<cF4sdApiSearchResultRelation>>>(AgentIdInfos.Count);
                foreach (var AgentId in AgentIdInfos)
                    lstTasks.Add(GetSingleUsedInfoFromAgentData(infoClass, AgentId.Value, AgentId.Key, Age, Token, requestInfo, LogDeep + 1));

                var res = await Task.WhenAll(lstTasks.ToArray());

                var RetVal = new List<cF4sdApiSearchResultRelation>();
                double _sumDuration = 0;
                for (int i = 0; i < res.Length; i++)
                {
                    var Entry = res[i];
                    if (Entry == null)
                        continue;

                    foreach (var Entry2 in Entry)
                    {
                        if (Entry2 == null)
                            continue;
                        RetVal.Add(Entry2);
                        _sumDuration += Entry2.UsingLevel;
                    }
                }

                if (_sumDuration == 0)
                    _sumDuration = 1;

                foreach (var Entry in RetVal)
                    Entry.UsingLevel /= _sumDuration;

                return RetVal;
            }
            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 null;
        }

        private async Task<List<cF4sdApiSearchResultRelation>> GetSingleUsedInfoFromAgentData(enumFasdInformationClass infoClass, cDataHistoryUsageIdInfo Id, int AgentId, int Age, CancellationToken Token, 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 _usageData = await F4sdAgent.GetSingleUsedInfoFromAgentData(infoClass, AgentId, Age, Token, requestInfo, LogDeep + 1);
                if (_usageData == null)
                    return null;

                var _infoClass = getResultClassForAgentUsage(infoClass);

                var RetVal = new List<cF4sdApiSearchResultRelation>();
                foreach (var _entry in _usageData)
                {
                    var deviceInfo = await GetMainInfoFromAgentId(_infoClass, _entry.id, requestInfo, LogDeep + 1, Token);
                    if (deviceInfo == null && infoClass == enumFasdInformationClass.Computer && _infoClass == enumFasdInformationClass.User && InfrastructureConfig.ClientAgent?.localAccountAssignments != null)
                        deviceInfo = await GetLocalUserInfoFromAgentId(_entry.id, requestInfo, LogDeep + 1, Token);

                    if (deviceInfo != null)
                    {
                        var _at = enumAccountType.unknow;
                        if (infoClass == enumFasdInformationClass.User)
                            _at = Id.AccountType;
                        deviceInfo.Identities?.Add(new cF4sdIdentityEntry() { Class = infoClass, Id = Id.Id, agentId = AgentId, AccountType = _at });
                        deviceInfo.UsingLevel = (double)_entry.duration;
                        deviceInfo.LastUsed = _entry.lastEvent;
                        if (deviceInfo.Infos == null)
                            deviceInfo.Infos = new Dictionary<string, string>();
                        if (!deviceInfo.Infos.ContainsKey("UserAccountType"))
                            deviceInfo.Infos.Add("UserAccountType", Id.AccountType.ToString());
                        if (infoClass == enumFasdInformationClass.User)
                        {
                            if (!string.IsNullOrEmpty(Id.account))
                                deviceInfo.Infos.Add("UserAccount", Id.account);
                            if (!string.IsNullOrEmpty(Id.domain))
                                deviceInfo.Infos.Add("UserDommain", Id.domain);
                        }
                        RetVal.Add(deviceInfo);

                        if (DefaultLogger.IsDebug)
                            deviceInfo.LogIt(LogLevels.Debug);
                    }
                }

                if (RetVal.Count > 0)
                    return RetVal;

            }
            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 null;
        }

        private async Task<cF4sdApiSearchResultRelation> GetMainInfoFromAgentId(enumFasdInformationClass infoClass, int agentID, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, cF4sdApiSearchResultRelation SearchEntry = null)
        {
            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;

            if (!DataHistorySqlHelper.GetWellKnownSqlStatement($"Get{infoClass}InfoFromAgentId", out var Query))
                return null;

            var Params = new Dictionary<string, object>() { { "id", agentID } };
            int apiError = 0;

            try
            {
                using (var ConnMain = new cDbConnection(mainDbConnection))
                using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(ConnMain, Query, Params, Token))
                {
                    if (Reader != null)
                        try
                        {
                            if (Reader.HasRows)
                            {
                                var maxStateVal = Enum.GetValues(typeof(enumF4sdSearchResultStatus)).Cast<int>().Max<int>();
                                var minStateVal = Enum.GetValues(typeof(enumF4sdSearchResultStatus)).Cast<int>().Min<int>();

                                if (await Reader.ReadAsync())
                                {
                                    try
                                    {
                                        var Id = Reader.GetGuid(0);
                                        var Name = Reader.GetString(1);
                                        var Status = enumF4sdSearchResultStatus.Unknown;
                                        if (!Reader.IsDBNull(2))
                                        {
                                            var intStatus = Reader.GetInt32(2);
                                            if (intStatus <= maxStateVal && intStatus >= minStateVal)
                                                Status = (enumF4sdSearchResultStatus)intStatus;
                                        }

                                        var account = Reader.IsDBNull(3) ? null : Reader.GetString(3);
                                        var domain = Reader.IsDBNull(4) ? null : Reader.GetString(4);
                                        var IsAzure = Reader.GetInt32(5);
                                        var AccountType = enumAccountType.unknow;
                                        if (infoClass == enumFasdInformationClass.User)
                                        {
                                            if (IsAzure == 1)
                                                AccountType = enumAccountType.Azure;
                                            else
                                                AccountType = enumAccountType.AD;
                                        }

                                        var RetVal = new cF4sdApiSearchResultRelation()
                                        {
                                            Type = cFasdApiSearchResultEntry.getSearchClass(infoClass),
                                            id = Id,
                                            Name = Name,
                                            DisplayName = Name,
                                            Status = Status,
                                            LastUsed = DateTime.UtcNow,
                                            UsingLevel = 1.0,
                                        };

                                        RetVal.Identities = new cF4sdIdentityList { new cF4sdIdentityEntry() { Class = infoClass, Id = RetVal.id, agentId = agentID, AccountType = AccountType } };

                                        RetVal.Infos = new Dictionary<string, string>
                                        {
                                            ["account"] = account,
                                            ["domain"] = domain
                                        };
                                        if (infoClass == enumFasdInformationClass.User)
                                            RetVal.Infos["UserAccountType"] = AccountType.ToString();

                                        if (SearchEntry != null)
                                        {
                                            RetVal.UsingLevel = SearchEntry.UsingLevel;
                                            RetVal.LastUsed = SearchEntry.LastUsed;
                                            if (SearchEntry.Identities != null)
                                                RetVal.Identities?.AddRange(SearchEntry.Identities);
                                        }

                                        return RetVal;

                                    }
                                    catch (Exception E)
                                    {
                                        cLogManager.DefaultLogger.LogException(E);
                                    }
                                }
                            }
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }
                }

            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        private async Task<cF4sdApiSearchResultRelation> GetLocalUserInfoFromAgentId(int agentID, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug)
            {
                CM = MethodBase.GetCurrentMethod();
                LogMethodBegin(CM);
                LogEntry($"agentId={agentID}", LogLevels.Debug);
            }

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

            if (!DataHistorySqlHelper.GetWellKnownSqlStatement($"GetLocalUserInfoFromAgentId", out var Query))
                return null;

            var Params = new Dictionary<string, object>() { { "id", agentID } };
            int apiError = 0;

            try
            {

                using (var ConnMain = new cDbConnection(mainDbConnection))
                using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(ConnMain, Query, Params, Token))
                {
                    if (Reader != null)
                        try
                        {
                            if (Reader.HasRows)
                            {
                                var maxStateVal = Enum.GetValues(typeof(enumF4sdSearchResultStatus)).Cast<int>().Max<int>();
                                var minStateVal = Enum.GetValues(typeof(enumF4sdSearchResultStatus)).Cast<int>().Min<int>();

                                if (await Reader.ReadAsync())
                                {
                                    try
                                    {
                                        var Id = Reader.GetGuid(0);
                                        var Name = Reader.GetString(1);
                                        var Status = enumF4sdSearchResultStatus.Unknown;
                                        if (!Reader.IsDBNull(2))
                                        {
                                            var intStatus = Reader.GetInt32(2);
                                            if (intStatus <= maxStateVal && intStatus >= minStateVal)
                                                Status = (enumF4sdSearchResultStatus)intStatus;
                                        }

                                        var account = Reader.IsDBNull(3) ? null : Reader.GetString(3);

                                        var RetVal = new cF4sdApiSearchResultRelation()
                                        {
                                            Type = enumF4sdSearchResultClass.User,
                                            id = Id,
                                            Name = Name,
                                            DisplayName = Name,
                                            Status = Status,
                                            LastUsed = DateTime.UtcNow,
                                            UsingLevel = 1.0,
                                        };

                                        RetVal.Identities = new cF4sdIdentityList() { new cF4sdIdentityEntry() { Class = enumFasdInformationClass.User, Id = Id, agentId = agentID, AccountType = enumAccountType.Local } };

                                        RetVal.Infos = new Dictionary<string, string>
                                        {
                                            ["account"] = account,
                                            ["UserAccountType"] = "Local"
                                        };

                                        LogEntry($"Local account info found: id={Id}, account={account} ", LogLevels.Debug);

                                        return RetVal;

                                    }
                                    catch (Exception E)
                                    {
                                        cLogManager.DefaultLogger.LogException(E);
                                    }
                                }
                            }
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }
                }

            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetTableResultsMainDb(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, bool instantly, bool IsSecondCall, CancellationToken Token, 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
            {
                Task<List<cF4SDHealthCardRawData.cHealthCardTable>>[] tasks = new Task<List<cF4SDHealthCardRawData.cHealthCardTable>>[Tables.Count];
                for (int i = 0; i < Tables.Count; i++)
                {
                    tasks[i] = getTableRawData(Tables[i], Identities, RefTime, MaxAge, instantly, IsSecondCall, Token, requestInfo, LogDeep + 1);
                }

                var Results = await Task.WhenAll(tasks).ConfigureAwait(false);

                var RetVal = new List<cF4SDHealthCardRawData.cHealthCardTable>();
                foreach (var Entry in Results)
                {
                    if (Entry == null)
                        continue;
                    RetVal.AddRange(Entry);
                }


                return RetVal;
            }
            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 null;
        }

        public async Task<cF4SDHealthCardRawData.cHealthCardDetailsTable> GetDetailsTableSingleResultMainDb(cDataHistoryConfigTable Table, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                using (var Conn = new cDbConnection(mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return null;
                    }

                    var Cols = new List<string>();
                    foreach (var col in Table.Columns.Values)
                    {
                        Cols.Add(col.Name);
                        if (col.SqlTypeBin != null)
                            Cols.Add(col.Name + "_bin");
                    }

                    if (!Identities.TryGetValue(Table.ParentCluster.InformationClass, out var _ids))
                        return null;

                    var _valId = _ids.Id;
                    var _rowId = Table.KeyColumns.First()?.SourceName;
                    var _rowTime = Table.EventTimeColumn?.SourceName;

                    if (string.IsNullOrEmpty(_rowId) || string.IsNullOrEmpty(_rowTime))
                        return null;

                    var Filter = new Dictionary<string, object>(1) { { _rowId, _valId } };
                    using (var _reader = await DataHistorySqlHelper.getDbReaderDetailTable(Conn, Table.SourceName, Filter, _rowTime, MaxAge, Token))
                    {
                        if (_reader == null)
                            return null;

                        if (_reader.HasRows)
                        {
                            var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable
                            {
                                Name = Table.Name,
                                Columns = Table.Columns.Values.Select(v => v.Name).ToList(),
                                Values = new Dictionary<int, List<object[]>>()
                            };

                            var _colInfos = Table.Columns.Values.ToList();
                            Dictionary<string, int> _dic = new Dictionary<string, int>(Table.Columns.Count);
                            var _idx = 0;
                            foreach (var _colInfo in Table.Columns.Values)
                            {
                                _dic[_colInfo.SourceName] = _idx;
                                _idx++;
                            }

                            var _arrIdxAssign = Enumerable.Repeat<int>(-1, Table.Columns.Count).ToArray();
                            for (int i = 0; i < _reader.FieldCount; i++)
                            {
                                var _n = _reader.GetName(i);
                                if (_dic.TryGetValue(_n, out var _ix))
                                    _arrIdxAssign[_ix] = i;
                            }

                            while (await _reader.ReadAsync(Token))
                            {
                                if (Token.IsCancellationRequested)
                                    return null;

                                var _row = Enumerable.Repeat<object>(null, Table.Columns.Count).ToArray();

                                DateTime? EventTime = null;
                                for (int i = 0; i < Table.Columns.Count; i++)
                                {
                                    var _n = _arrIdxAssign[i];
                                    if (_n >= 0)
                                    {
                                        var obj = _reader.GetValue(_n);
                                        var _obj2 = DataHistorySqlHelper.GetSqlDataValueFromHistoryType(obj, _colInfos[i].ValueType, _colInfos[i].Cardinal);
                                        _row[i] = _obj2[0];
                                        if (_reader.GetName(_n) == Table.EventTimeColumn.SourceName)
                                            if (_row[i] is DateTime dt)
                                                EventTime = dt;
                                    }
                                }

                                if (EventTime != null)
                                {
                                    var indexDay = cDataHistoryCollector.getDayIndex((DateTime)EventTime, RefTime, MaxAge + 1);
                                    if (!retVal.Values.TryGetValue(indexDay, out var entryDay))
                                    {
                                        entryDay = new List<object[]>();
                                        retVal.Values.Add(indexDay, entryDay);
                                    }
                                    entryDay.Add(_row);
                                }
                            }

                            return retVal;
                        }

                    }
                }

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

            return null;
        }


        public async Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>> GetDetailsTableResultsMainDb(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, CancellationToken Token, 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 _lstTsk = new List<Task<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(Tables.Count);
                foreach (var _table in Tables)
                    _lstTsk.Add(GetDetailsTableSingleResultMainDb(_table, Identities, RefTime, MaxAge, Token));

                var arrRet = await Task.WhenAll(_lstTsk);

                return arrRet.Where(v => v != null).ToList();
            }
            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 null;
        }


        public static void CorrectUsage(cDataHistoryCollector Collector, cF4SDHealthCardRawData data, List<Int64> Usage)
        {
            try
            {
                if (data?.Tables == null || Usage == null)
                    return;

                foreach (var _table in data.Tables.Values)
                {
                    if (_table.IsStatic)
                        continue;

                    if (Collector.ClusterConfig.Tables.TryGetValue(_table.Name, out var _configTable) && _configTable.Type != eDataHistoryTableType.History)
                        continue;

                    foreach (var _column in _table.Columns.Values)
                    {
                        if (_column.Values == null || _column.Values.Count == 0)
                            continue;
                        int _l = 0;
                        if (_table.StartingIndex < 0)
                            _l = Math.Min(_column.Values.Count + _table.StartingIndex, Usage.Count);
                        else
                            _l = Math.Min(_column.Values.Count, Usage.Count - _table.StartingIndex);

                        for (int i = 0; i < _l; i++)
                        {
                            int i1 = 0;
                            int i2 = 0;
                            if (_table.StartingIndex < 0)
                            {
                                i1 = i;
                                i2 = i - _table.StartingIndex;
                            }
                            else
                            {
                                i1 = i + _table.StartingIndex;
                                i2 = i;
                            }

                            if (Usage[i1] <= 0)
                                _column.Values[i2] = null;
                        }
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        public async Task<cF4SDHealthCardRawData> GetTableResults(List<string> Tables, List<cF4sdIdentityEntry> Identities, DateTime RefTime, int MaxAge, bool instantly, CancellationToken Token, 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 _tables = new List<string>();
                foreach (var t in Tables)
                {
                    if (!_tables.Contains(t))
                        _tables.Add(t);
                }

                var Ids = await getConntectorIds(Identities, Token, requestInfo, LogDeep + 1).ConfigureAwait(false);

                var dicVirtualTables = new Dictionary<enumDataHistoryOrigin, List<cDataHistoryConfigTable>>();
                var lstDbTables = new List<cDataHistoryConfigTable>();

                foreach (var strTable in _tables)
                {
                    var tableConfig = this.GetTableFromName(strTable);
                    if (tableConfig == null)
                    {
                        LogEntry($"The table '{strTable}' could not be found in the data clusters definitions", LogLevels.Warning);
                        continue;
                    }

                    if (lstDbTables.Contains(tableConfig))
                        continue;

                    if (tableConfig.Type == eDataHistoryTableType.Events || tableConfig.Type == eDataHistoryTableType.Selection)
                        continue;

                    if (tableConfig.IsVirtual)
                    {
                        if (Connectors.ContainsKey(tableConfig.ParentCluster.Origin))
                        {
                            if (!dicVirtualTables.TryGetValue(tableConfig.ParentCluster.Origin, out var lstTables))
                            {
                                lstTables = new List<cDataHistoryConfigTable>();
                                dicVirtualTables.Add(tableConfig.ParentCluster.Origin, lstTables);
                            }
                            if (!lstTables.Contains(tableConfig))
                                lstTables.Add(tableConfig);
                        }
                    }
                    else
                    {
                        lstDbTables.Add(tableConfig);
                    }

                }

                var c = dicVirtualTables.Count;
                var idx = 0;
                if (lstDbTables.Count > 0)
                {
                    c++;
                    idx = 1;
                }

                Task<List<cF4SDHealthCardRawData.cHealthCardTable>>[] tasks = new Task<List<cF4SDHealthCardRawData.cHealthCardTable>>[c];
                if (lstDbTables.Count > 0)
                    tasks[0] = GetTableResultsMainDb(lstDbTables, Ids, RefTime, MaxAge, instantly, false, Token, requestInfo, LogDeep + 1);

                foreach (var Entry in dicVirtualTables)
                {
                    var C = Connectors[Entry.Key];
                    tasks[idx] = Task.Run(() => { return C.GetTableResultsVirtualAsync(Entry.Value, Ids, RefTime, MaxAge, instantly, null, Token, requestInfo, LogDeep + 1); });
                    idx++;
                }

                var Results = await Task.WhenAll(tasks).ConfigureAwait(false);

                var RetVal = new cF4SDHealthCardRawData();
                var IsIncompleted = false;
                foreach (var Entry in Results)
                {
                    if (Entry == null)
                        continue;
                    foreach (var subEntry in Entry)
                        if (!RetVal.Tables.ContainsKey(subEntry.Name))
                        {
                            RetVal.Tables.Add(subEntry.Name, subEntry);
                            if (instantly)
                                subEntry.IsIncomplete = false;
                            else
                                IsIncompleted |= subEntry.IsIncomplete;
                        }
                }

                // detect the usage information
                List<Int64> _usage = null;
                if (RetVal.Tables.TryGetValue("agnt-computer-event-numerical", out var usageTable))
                {
                    cF4SDHealthCardRawData.cHealthCardTableColumn usageCol1;
                    if (!usageTable.Columns.TryGetValue("DeviceActivity", out usageCol1))
                        usageCol1 = null;

                    cF4SDHealthCardRawData.cHealthCardTableColumn usageCol2;
                    if (!usageTable.Columns.TryGetValue("UserActivity", out usageCol2))
                        usageCol2 = null;

                    if (usageCol1 == null)
                    {
                        usageCol1 = usageCol2;
                        usageCol2 = null;
                    }


                    if (usageCol1?.Values != null)
                    {
                        var _l = usageCol1.Values.Count;
                        if (usageCol2?.Values != null)
                            _l = Math.Max(_l, usageCol2.Values.Count);

                        _usage = new List<Int64>(_l);
                        foreach (var _entry in usageCol1.Values)
                        {
                            Int64 _v = 0;
                            try
                            {
                                _v = Convert.ToInt64(_entry);
                            }
                            catch { }
                            _usage.Add(_v);
                        }

                        if (usageCol2?.Values != null)
                        {
                            var _i = 0;
                            foreach (var _entry in usageCol1.Values)
                            {
                                Int64 _v = 0;
                                try
                                {
                                    _v = Convert.ToInt64(_entry);
                                }
                                catch { }
                                if (_i < _usage.Count)
                                    _usage[_i] = Math.Max(_usage[_i], _v);
                                else
                                    _usage.Add(_v);
                                _i++;
                            }

                        }
                        CorrectUsage(this, RetVal, _usage);
                    }
                }

                if (IsIncompleted && !instantly)
                {
                    RetVal.Id = Guid.NewGuid();
                    foreach (var Entry in dicVirtualTables)
                    {
                        var C = Connectors[Entry.Key];
                        C.AddTableResultVirtualSuccessorTasks(Entry.Value, Ids, RefTime, MaxAge, _usage, RetVal.Id, requestInfo, LogDeep + 1);
                    }
                    AddTableResultSuccessorTasks(lstDbTables, Ids, RefTime, MaxAge, _usage, RetVal.Id, requestInfo, LogDeep + 1);
                }

                return RetVal;
            }
            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 null;
        }

        public cDataHistoryConfigTable GetTableFromName(string Name)
        {
            try
            {
                if (ClusterConfig.AlternateTableNames.TryGetValue(Name, out var _aln))
                    Name = _aln;
                if (ClusterConfig.Tables.TryGetValue(Name, out var _retVal))
                    return _retVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }

            return null;
        }

        public async Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>> GetDetailsTableResultsAsync(List<string> Tables, List<cF4sdIdentityEntry> Identities, DateTime RefTime, int MaxAge, CancellationToken Token, 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 Ids = await getConntectorIds(Identities, Token, requestInfo, LogDeep + 1).ConfigureAwait(false);

                var dicVirtualTables = new Dictionary<enumDataHistoryOrigin, List<cDataHistoryConfigTable>>();
                var lstDbTables = new List<cDataHistoryConfigTable>();
                foreach (var strTable in Tables)
                {
                    var tableConfig = GetTableFromName(strTable);
                    if (tableConfig == null)
                    {
                        LogEntry($"The table '{strTable}' could not be found in the data clusters definitions", LogLevels.Warning);
                        continue;
                    }

                    if (lstDbTables.Contains(tableConfig))
                        continue;

                    if (tableConfig.Type != eDataHistoryTableType.Events && tableConfig.Type != eDataHistoryTableType.StaticDetails)
                        continue;

                    if (tableConfig.IsVirtual)
                    {
                        if (Connectors.ContainsKey(tableConfig.ParentCluster.Origin))
                        {
                            if (!dicVirtualTables.TryGetValue(tableConfig.ParentCluster.Origin, out var lstTables))
                            {
                                lstTables = new List<cDataHistoryConfigTable>();
                                dicVirtualTables.Add(tableConfig.ParentCluster.Origin, lstTables);
                            }
                            lstTables.Add(tableConfig);
                        }
                    }
                    else
                    {
                        lstDbTables.Add(tableConfig);
                    }

                }

                var c = dicVirtualTables.Count;
                var idx = 0;
                if (lstDbTables.Count > 0)
                {
                    c++;
                    idx = 1;
                }

                Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>[] tasks = new Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>[c];
                if (lstDbTables.Count > 0)
                    tasks[0] = GetDetailsTableResultsMainDb(lstDbTables, Ids, RefTime, MaxAge, Token, requestInfo, LogDeep + 1);

                foreach (var Entry in dicVirtualTables)
                {
                    var C = Connectors[Entry.Key];
                    tasks[idx] = C.GetDetailsTableResultsVirtualAsync(Entry.Value, Ids, RefTime, MaxAge, Token, requestInfo, LogDeep + 1);
                    idx++;
                }

                var Results = await Task.WhenAll(tasks).ConfigureAwait(false);

                var RetVal = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();
                foreach (var Entry in Results)
                {
                    if (Entry == null)
                        continue;
                    RetVal.AddRange(Entry);
                }

                return RetVal;
            }
            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 null;
        }

        public async Task<Int64> GetSelectionTableResultCountAsync(string Table, List<cF4sdIdentityEntry> Identities, string search, cF4sdWebRequestInfo requestInfo, CancellationToken Token, bool resetFilter = false, List<string> filterParams = null)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!ClusterConfig.Tables.TryGetValue(Table, out var _table))
                {
                    LogEntry($"Could not find table '{Table}' in table list.", LogLevels.Warning);
                    return -1;
                }

                if (!this.Connectors.TryGetValue(_table.ParentCluster.Origin, out var _module))
                {
                    LogEntry($"Could not find module '{_table.ParentCluster.Origin}' in module list.", LogLevels.Warning);
                    return -1;
                }

                var _retVal = await _module.GetSelectionTableResultCountAsync(_table, Identities, search, requestInfo, Token, resetFilter, filterParams);

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

            return -1;
        }

        public async Task<cF4SDHealthCardRawData.cHealthCardTable> GetSelectionTableResultAsync(string Table, List<cF4sdIdentityEntry> Identities, string search, int PageSize, int Page, cF4sdWebRequestInfo requestInfo, CancellationToken Token, bool resetFilter = false, List<string> filterParams = null)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!ClusterConfig.Tables.TryGetValue(Table, out var _table))
                    return null;

                if (!this.Connectors.TryGetValue(_table.ParentCluster.Origin, out var _module))
                    return null;

                var _retVal = await _module.GetSelectionTableResultAsync(_table, Identities, search, PageSize, Page, requestInfo, Token, resetFilter, filterParams);

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

            return null;
        }

        public async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetCachedHistoricTable(cDataHistoryConfigTable tableConfig, Dictionary<string, object> Filter, DateTime RefTime, int MaxAge, cDataHistoryScanTiming Timing, CancellationToken Token, 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
            {
                using (var Conn = new cDbConnection(mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return null;
                    }

                    using (var _reader = await DataHistorySqlHelper.getDbReaderHistoricTable(Conn, tableConfig.SourceName, Filter, RefTime + TimeSpan.FromDays(-MaxAge - 1), Token))
                    {
                        if (_reader == null)
                            return null;

                        if (_reader.FieldCount > 2)
                        {
                            var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                            {
                                Name = tableConfig.Name,
                                InformationClass = tableConfig.ParentCluster.InformationClass,
                                Origin = tableConfig.ParentCluster.Origin,
                                StartingIndex = int.MaxValue,
                                IsStatic = false,
                                TableType = tableConfig.Type
                            };
                            var maxIndex = int.MinValue;

                            var Cols = new Dictionary<int, cF4SDHealthCardRawData.cHealthCardTableColumn>(_reader.FieldCount);
                            for (int i = 0; i < _reader.FieldCount; i++)
                            {
                                var _propName = _reader.GetName(i);
                                if (tableConfig.Columns.TryGetValue(_propName, out var _col))
                                {
                                    var _ci = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = _col.Name };
                                    Cols[i] = _ci;
                                    retVal.Columns[_col.Name] = _ci;
                                }
                                if (tableConfig.Columns.TryGetValue(_propName + "_bin", out var _col2))
                                {
                                    var _ci = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = _col.Name + "_bin" };
                                    Cols[i] = _ci;
                                    retVal.Columns[_col.Name + "_bin"] = _ci;
                                }
                            }


                            var TimeFrames = new Dictionary<int, KeyValuePair<DateTime, DateTime>>();

                            if (_reader.HasRows)
                            {
                                while (await _reader.ReadAsync())
                                {
                                    DateTime timeFrom = _reader.GetDateTime(0);
                                    DateTime timeTo = _reader.GetDateTime(1);

                                    var indexTimeFrame = cScanTimeInfo.GetScanIntervalIndex(RefTime, timeFrom, timeTo, Timing);

                                    if (maxIndex == int.MinValue)
                                    {
                                        maxIndex = indexTimeFrame - 1;
                                    }

                                    if (TimeFrames.ContainsKey(indexTimeFrame))
                                        continue;
                                    TimeFrames.Add(indexTimeFrame, new KeyValuePair<DateTime, DateTime>(timeFrom, timeTo));

                                    foreach (var entryCol in Cols)
                                    {
                                        var colInfo = entryCol.Value;
                                        var colIndex = entryCol.Key;
                                        var objValue = _reader[colIndex];
                                        objValue = objValue == DBNull.Value ? null : objValue;

                                        if (Cols.TryGetValue(colIndex, out var _col))
                                        {
                                            if (indexTimeFrame > maxIndex)
                                            {
                                                for (int j = maxIndex + 1; j < indexTimeFrame; j++)
                                                    _col.Values.Add(null);
                                                _col.Values.Add(objValue);
                                            }
                                            else if (indexTimeFrame < retVal.StartingIndex)
                                            {
                                                for (int j = indexTimeFrame + 1; j < retVal.StartingIndex; j++)
                                                    _col.Values.Insert(0, null);
                                                _col.Values.Insert(0, objValue);
                                            }
                                            else
                                            {
                                                _col.Values[indexTimeFrame - retVal.StartingIndex] = objValue;
                                            }

                                        }
                                    }

                                    retVal.StartingIndex = Math.Min(retVal.StartingIndex, indexTimeFrame);
                                    maxIndex = Math.Max(maxIndex, indexTimeFrame);
                                }
                            }

                            if (maxIndex >= retVal.StartingIndex)
                            {
                                retVal.TimeFrames = new DateTime[maxIndex - retVal.StartingIndex + 1, 2];

                                for (int i = retVal.StartingIndex; i <= maxIndex; i++)
                                {
                                    if (TimeFrames.TryGetValue(i, out var _tf))
                                    {
                                        retVal.TimeFrames[i - retVal.StartingIndex, 0] = _tf.Key;
                                        retVal.TimeFrames[i - retVal.StartingIndex, 1] = _tf.Value;
                                    }
                                    else
                                    {
                                        retVal.TimeFrames[i - retVal.StartingIndex, 0] = DateTime.MinValue;
                                        retVal.TimeFrames[i - retVal.StartingIndex, 1] = DateTime.MinValue;
                                    }
                                }
                            }

                            return new List<cF4SDHealthCardRawData.cHealthCardTable>() { retVal };
                        }
                    }
                }
            }
            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 null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> getTableRawDataStatic(DbDataReader _reader, cDataHistoryConfigTable tableConfig, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (await _reader.ReadAsync(Token))
                {
                    if (Token.IsCancellationRequested)
                        return null;

                    var objValues = new object[_reader.FieldCount];
                    var n = _reader.GetValues(objValues);
                    var ColNames = new string[_reader.FieldCount];
                    if (ColNames.Length == _reader.FieldCount)
                    {
                        var RetVal = new cF4SDHealthCardRawData.cHealthCardTable();
                        for (int i = 0; i < _reader.FieldCount; i++)
                        {
                            var objVal = objValues[i] == DBNull.Value ? null : objValues[i];
                            var strCol = _reader.GetName(i);
                            RetVal.Columns.Add(strCol, new cF4SDHealthCardRawData.cHealthCardTableColumn(RetVal) { ColumnName = strCol, Values = new List<object>() { objVal } });
                        }

                        RetVal.Name = tableConfig.Name;
                        RetVal.InformationClass = tableConfig.ParentCluster.InformationClass;
                        RetVal.Origin = tableConfig.ParentCluster.Origin;
                        RetVal.IsStatic = tableConfig.Type == eDataHistoryTableType.Static;
                        RetVal.TableType = tableConfig.Type;

                        return new List<cF4SDHealthCardRawData.cHealthCardTable>() { RetVal };
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> getTableRawDataHistoric(DbDataReader _reader, cDataHistoryConfigTable tableConfig, DateTime RefTime, int MaxAge, bool instantly, bool IsSecondCall, CancellationToken Token)
        {

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

                cHistoricAggregationEntry[] arrColAgg = new cHistoricAggregationEntry[tableConfig.Columns.Count];
                var i = 0;
                foreach (var col in tableConfig.Columns.Values)
                {
                    if (col is cDataHistoryConfigColumn _colInfo)
                        arrColAgg[i] = new cHistoricAggregationEntry(_colInfo, MaxAge + 1, null);
                    i++;
                }

                var _colInfos = tableConfig.Columns.Values.ToList();
                Dictionary<string, int> _dic = new Dictionary<string, int>(tableConfig.Columns.Count);
                var _idx = 0;
                foreach (var _colInfo in tableConfig.Columns.Values)
                {
                    _dic[_colInfo.SourceName] = _idx;
                    _idx++;
                }

                var _idxTimeCol = -1;
                var _arrIdxAssign = Enumerable.Repeat<int>(-1, tableConfig.Columns.Count).ToArray();
                for (i = 0; i < _reader.FieldCount; i++)
                {
                    var _n = _reader.GetName(i);
                    if (_dic.TryGetValue(_n, out var _ix))
                        _arrIdxAssign[_ix] = i;
                    if (_n == tableConfig.EventTimeColumn.SourceName)
                        _idxTimeCol = i;
                }
                if (_idxTimeCol < 0)
                    return null;


                while (await _reader.ReadAsync(Token))
                {
                    if (Token.IsCancellationRequested)
                        return null;

                    var _eventTime = _reader.GetDateTime(_idxTimeCol);
                    for (i = 0; i < tableConfig.Columns.Count; i++)
                    {
                        var _n = _arrIdxAssign[i];
                        if (_n >= 0)
                        {
                            var obj = _reader.GetValue(_n);
                            var _obj2 = DataHistorySqlHelper.GetSqlDataValueFromHistoryType(obj, _colInfos[i].ValueType, _colInfos[i].Cardinal);
                            arrColAgg[i].addValue(_obj2[0], _eventTime, RefTime, 0);
                        }
                    }

                }

                var RetValHistoric = new cF4SDHealthCardRawData.cHealthCardTable()
                {
                    Name = tableConfig.Name,
                    InformationClass = tableConfig.ParentCluster.InformationClass,
                    Origin = tableConfig.ParentCluster.Origin,
                    IsStatic = false,
                    TableType = eDataHistoryTableType.History
                };

                i = 0;
                foreach (var Entry in tableConfig.Columns.Values)
                {
                    var agg = arrColAgg[i];

                    var col = new cF4SDHealthCardRawData.cHealthCardTableColumn(RetValHistoric)
                    {
                        ColumnName = agg.Column.Name,
                        Values = agg.getValues()
                    };
                    RetValHistoric.Columns[agg.Column.Name] = col;

                    i++;
                }

                return new List<cF4SDHealthCardRawData.cHealthCardTable>() { RetValHistoric };
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public void AddTableResultSuccessorTasks(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, List<Int64> UsageInfo, Guid CacheId, 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 lstTables = new List<string>();
                var myTables = new List<cDataHistoryConfigTable>();
                foreach (var Table in Tables)
                {
                    if (Table.ParentCluster.Origin != enumDataHistoryOrigin.Main)
                        continue;

                    if (!Table.LateDelivery)
                        continue;

                    if (Table.Cached != eDataHistoryTableCached.Yes)
                        continue;

                    myTables.Add(Table);
                    lstTables.Add(Table.Name);
                }

                if (lstTables.Count > 0)
                {
                    var ct = new CancellationTokenSource().Token;
                    var tsk = GetTableResultsMainDb(myTables, Identities, RefTime, MaxAge, false, true, ct, requestInfo, LogDeep + 1);
                    SuccessorCache.AddTask(CacheId, lstTables, tsk, UsageInfo, ct);
                }
            }
            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);
            }
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> getTableRawData(cDataHistoryConfigTable tableConfig, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, bool instantly, bool IsSecondCall, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            if (tableConfig.LateDelivery && !instantly && !IsSecondCall)
            {
                return new List<cF4SDHealthCardRawData.cHealthCardTable>() {
                    new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = tableConfig.Name,
                        InformationClass = enumFasdInformationClass.Ticket,
                        Origin = tableConfig.ParentCluster.Origin,
                        IsIncomplete = true
                    }
                };
            }

            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 Cols = new List<string>();
                foreach (var col in tableConfig.Columns.Values)
                {
                    Cols.Add(col.Name);
                    if (col.SqlTypeBin != null)
                        Cols.Add(col.Name + "_bin");
                }

                if (Identities.TryGetValue(tableConfig.ParentCluster.InformationClass, out var Identity))
                    if (Connectors.TryGetValue(tableConfig.ParentCluster.Origin, out var C))
                    {
                        var IdValues = await C.GetIds(Identity, tableConfig.ParentCluster.InformationClass, requestInfo, LogDeep + 1);
                        if (IdValues != null && tableConfig.KeyColumns.Count == IdValues.Count)
                        {
                            var Filters = new Dictionary<string, object>();
                            for (int i = 0; i < IdValues.Count; i++)
                            {
                                if (IdValues[i] == null)
                                {
                                    LogEntry($"Empty filter value used on getting raw data for table {tableConfig.Name}.", LogLevels.Warning);
                                    return null;
                                }
                                var Col = tableConfig.KeyColumns[i];
                                var ColName = Col.Name; if (Col.SqlTypeBin != null)
                                    ColName += "_bin";
                                Filters.Add(ColName, IdValues[i]);
                            }

                            using (var Conn = new cDbConnection(mainDbConnection))
                            {
                                if (!Conn.IsOpen)
                                {
                                    LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                                    return null;
                                }

                                using (var _reader = await DataHistorySqlHelper.getDbReaderSelectAsync(Conn, tableConfig.SourceName, Cols, Filters, Token).ConfigureAwait(false))
                                {
                                    if (_reader.HasRows)
                                        switch (tableConfig.Type)
                                        {
                                            case eDataHistoryTableType.Static:
                                                return await getTableRawDataStatic(_reader, tableConfig, Token);
                                            case eDataHistoryTableType.History:
                                                return await getTableRawDataHistoric(_reader, tableConfig, RefTime, MaxAge, instantly, IsSecondCall, Token);
                                        }
                                }
                            }

                        }

                    }
            }
            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 null;
        }

        public async Task<Dictionary<enumFasdInformationClass, cF4sdConnectorIds>> getConntectorIds(List<cF4sdIdentityEntry> Identities, CancellationToken Token, 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 tasks = new Task<cF4sdConnectorIds>[Identities.Count];
                for (int i = 0; i < Identities.Count; i++)
                {
                    tasks[i] = getConnectorIds(Identities[i].Class, Identities[i], Token, requestInfo, LogDeep + 1);
                }

                var Result = await Task.WhenAll(tasks).ConfigureAwait(false);

                var RetVal = Result.Where(v => v != null).ToDictionary<cF4sdConnectorIds, enumFasdInformationClass>(x => x.infoClass);

                return RetVal;
            }
            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 null;
        }

        public async Task<List<cF4sdConnectorIds>> getConnectorIdList(List<cF4sdIdentityEntry> Identities, CancellationToken Token, 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 tasks = new Task<cF4sdConnectorIds>[Identities.Count];
                for (int i = 0; i < Identities.Count; i++)
                {
                    tasks[i] = getConnectorIds(Identities[i].Class, Identities[i], Token, requestInfo, LogDeep + 1, WithLocalAccounts: true);
                }

                var Result = await Task.WhenAll(tasks).ConfigureAwait(false);

                var RetVal = Result.Where(v => v != null).ToList();

                return RetVal;
            }
            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 null;
        }

        private async Task<cF4sdConnectorIds> getConnectorIdsFromDb(enumFasdInformationClass infoClass, cF4sdIdentityEntry Identity, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, bool WithLocalAccounts = 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
            {

                int apiError = 0;

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement($"GetConnectorIdsFrom{infoClass}Id", out var Query))
                    return null;

                var Params = new Dictionary<string, object>() { { "id", Identity.Id } };

                using (var conn = new cDbConnection(mainDbConnection))
                {
                    if (!conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return null;
                    }

                    cF4sdConnectorIds RetVal = null;


                    try
                    {
                        using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(conn, Query, Params, Token))
                        {
                            if (Reader != null)
                                try
                                {
                                    if (Reader.HasRows)
                                    {
                                        if (await Reader.ReadAsync())
                                        {
                                            var Entry = new cF4sdConnectorIds()
                                            {
                                                infoClass = infoClass,
                                                Id = Identity.Id
                                            };

                                            Entry.sid = Reader.IsDBNull(0) ? null : (byte[])Reader.GetValue(0);
                                            Entry.agentId = Reader.IsDBNull(1) ? (int?)null : Reader.GetInt32(1);
                                            Entry.agentId2 = Reader.IsDBNull(4) ? (int?)null : Reader.GetInt32(4);
                                            Entry.name = Reader.GetString(2);
                                            Entry.lastSeenByAgent = Reader.IsDBNull(3) ? (DateTime?)null : Reader.GetDateTime(3);
                                            Entry.tenantId = Reader.IsDBNull(5) ? Guid.Empty : Reader.GetGuid(5);
                                            Entry.intuneId = Reader.IsDBNull(6) ? Guid.Empty : Reader.GetGuid(6);
                                            Entry.azureDeviceId = Reader.IsDBNull(7) ? Guid.Empty : Reader.GetGuid(7);
                                            Entry.citrixUserId = Reader.IsDBNull(8) ? (int?)null : Reader.GetInt32(8);
                                            Entry.citrixTenantId = Reader.IsDBNull(9) ? String.Empty : Reader.GetString(9);

                                            if (Identity.agentId != null)
                                                Entry.agentId = Identity.agentId;

                                            Entry.accountType = Identity.AccountType;

                                            RetVal = Entry;
                                        }
                                    }
                                }
                                catch (Exception E)
                                {
                                    LogException(E);
                                }
                        }
                    }
                    catch (Exception E)
                    {
                        apiError = E.HResult;
                        LogException(E);
                    }
                    finally
                    {
                        if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                    }


                    var _doLocalAccounts = WithLocalAccounts && RetVal != null && infoClass == enumFasdInformationClass.User && InfrastructureConfig.ClientAgent?.localAccountAssignments != null;

                    if (DefaultLogger.IsDebug)
                    {
                        var _msgDebug = new List<string>() {
                            "local account lookup infos:",
                            $"with local accounts: {WithLocalAccounts}",
                            $"RetVal agent id: {RetVal?.agentId}",
                            $"info class: {infoClass}",
                            $"Local account assignments count: {InfrastructureConfig.ClientAgent?.localAccountAssignments?.Count}",
                            $"do local accounts: {_doLocalAccounts}"
                        };
                        DefaultLogger.LogList(LogLevels.Debug, _msgDebug);
                    }

                    if (_doLocalAccounts)
                    {
                        var lst = new List<int>();
                        Params = new Dictionary<string, object>() { { "ID", Identity.Id } };
                        var _count = 0;

                        var _startTime2 = DateTime.UtcNow;
                        apiError = 0;

                        if (DataHistorySqlHelper.GetWellKnownSqlStatement("GetLocalUserFromId", out Query))

                            try
                            {
                                using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(conn, Query, Params, Token))
                                {
                                    if (Reader != null)
                                        try
                                        {
                                            if (Reader.HasRows)
                                            {
                                                while (await Reader.ReadAsync())
                                                {
                                                    _count++;
                                                    var localAgentId = Reader.GetInt32(0);
                                                    var lastSeen = Reader.IsDBNull(1) ? (DateTime?)null : Reader.GetDateTime(1);
                                                    var localAccount = Reader.GetString(2);
                                                    if (lastSeen != null)
                                                    {
                                                        if (RetVal.localAgentIds == null)
                                                            RetVal.localAgentIds = new List<cF4sdConnectorIds.localIdEntry>();
                                                        RetVal.localAgentIds.Add(new cF4sdConnectorIds.localIdEntry() { agentId = localAgentId, lastSeenByAgent = (DateTime)lastSeen, account = localAccount });
                                                    }
                                                }
                                            }
                                        }
                                        catch (Exception E)
                                        {
                                            LogException(E);
                                        }
                                }
                            }
                            catch (Exception E)
                            {
                                apiError = E.HResult;
                                LogException(E);
                            }
                            finally
                            {
                                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                            }


                        LogEntry($"local account assignement for id={Identity.Id} found: {_count}, {RetVal?.localAgentIds?.Count} of them are valid.", LogLevels.Debug);
                    }

                    return RetVal;
                }

            }
            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 null;
        }


        private async Task<cF4sdConnectorIds> getConnectorIds(enumFasdInformationClass infoClass, cF4sdIdentityEntry Identity, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, bool WithLocalAccounts = 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
            {
                if (infoClass == enumFasdInformationClass.Ticket)
                    return new cF4sdConnectorIds() { Id = Identity.Id, infoClass = enumFasdInformationClass.Ticket };
                if (infoClass == enumFasdInformationClass.VirtualSession)
                    return new cF4sdConnectorIds() { Id = Identity.Id, Instance = Identity.Instance, infoClass = enumFasdInformationClass.VirtualSession };
                if (infoClass == enumFasdInformationClass.MobileDevice)
                    return new cF4sdConnectorIds() { Id = Identity.Id, infoClass = enumFasdInformationClass.MobileDevice };

                var lstTasks = new List<Task>();

                var _mainTask = getConnectorIdsFromDb(infoClass, Identity, Token, requestInfo, LogDeep + 1, WithLocalAccounts);
                lstTasks.Add(_mainTask);

                var _needForeignUserInfo = false;
                if (infoClass == enumFasdInformationClass.User)
                    foreach (var module in this.Connectors.Values)
                        _needForeignUserInfo |= module.UseForeignUserIds();

                Task<cForeignUserInfo> _userInfoTask = null;
                if (_needForeignUserInfo)
                {
                    _userInfoTask =  GetForeignUserInfoAsync(Identity.Id, Token);
                    lstTasks.Add(_userInfoTask);
                }
                    

                await Task.WhenAll(lstTasks);

                var _result = _mainTask.Result;

                if (_result == null)
                    return null;

                if (_needForeignUserInfo)
                    _result.ForeignUserInfo = _userInfoTask?.Result;

                return _result;
            }
            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 null;
        }

        public static object getJsonValueFromString(string strValue, enumFasdValueType ValueType)
        {
            try
            {
                switch (ValueType)
                {
                    default:
                        string RetVal0 = null;
                        try
                        {
                            RetVal0 = JsonConvert.DeserializeObject<string>(strValue);
                        }
                        catch { }
                        if (RetVal0 == null)
                            RetVal0 = strValue;
                        return RetVal0;
                    case enumFasdValueType.INT:
                        return JsonConvert.DeserializeObject<Int32>(strValue);
                    case enumFasdValueType.BIGINT:
                        return JsonConvert.DeserializeObject<Int64>(strValue);
                    case enumFasdValueType.FLOAT:
                        return JsonConvert.DeserializeObject<float>(strValue);
                    case enumFasdValueType.DATETIME:
                        var DT = JsonConvert.DeserializeObject<DateTime>(strValue);
                        return DT;
                    // ToDo: VERSION deserialization
                    //case enumFasdValueType.VERSION:
                    //    var strVal1 = JsonConvert.DeserializeObject<string>(strValue);
                    //    if (Version.TryParse(strVal1, out var RetVersion))
                    //        return RetVersion;
                    //    return null;
                    case enumFasdValueType.BOOLEAN:
                        var strVal2 = JsonConvert.DeserializeObject<string>(strValue);
                        return cXmlParser.GetBoolFromString(strVal2);
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        public static void ComputeEventDetailColumns(cDataHistoryConfigTable TableConfig, cF4SDHealthCardRawData.cHealthCardDetailsTable TableResult)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (TableResult.Columns == null || TableResult.Columns.Count == 0 || TableResult.Values == null)
                    return;


                var lstComputations = new List<cDetailsComputationInfo>();
                var index = TableResult.Columns.Count;
                foreach (var Entry in TableConfig.Columns.Values)
                    if (Entry is cDataHistoryConfigColumnComputation comp)
                    {
                        var valueIndexes = new int[comp.Computation.Columns.Count];
                        for (int i = 0; i < comp.Computation.Columns.Count; i++)
                        {
                            var compVal = comp.Computation.Columns[i];
                            var idx = TableResult.Columns.IndexOf(compVal.Name);
                            if (idx < 0)
                            {
                                valueIndexes = null;
                                break;
                            }
                            valueIndexes[i] = idx;
                        }

                        if (valueIndexes == null)
                            continue;

                        var compInfo = new cDetailsComputationInfo() { Computation = comp.Computation, ValueIndexes = valueIndexes };
                        lstComputations.Add(compInfo);
                        TableResult.Columns.Add(comp.Name);
                    }

                foreach (var day in TableResult.Values.Values)
                    foreach (var Entry in day)
                    {
                        var idx = index;
                        foreach (var compInfo in lstComputations)
                        {
                            var vals = new object[compInfo.ValueIndexes.Length];
                            for (int j = 0; j < vals.Length; j++)
                                vals[j] = Entry[compInfo.ValueIndexes[j]];
                            Entry[idx] = compInfo.Computation.Compute(vals);
                            idx++;
                        }
                    }


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


        }

        public async Task<DateTime?> GetLastScanTime(string ScanName, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetLastScanTime", out var Query))
                    return null;

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

                DateTime? retVal = null;
                using (var conn = new cDbConnection(mainDbConnection))
                {
                    if (!conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return null;
                    }

                    retVal = await DataHistorySqlHelper.GetScalarResultAsync<DateTime?>(conn, Query, Params, requestInfo, CancellationToken.None);
                }
                if (retVal == null)
                {
                    retVal = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                    await SetLastScanTime(ScanName, (DateTime)retVal, requestInfo, LogDeep + 1, Token);
                }

                return retVal;

            }
            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 null;
        }

        public async Task<bool> SetLastScanTime(string ScanName, DateTime LastTime, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("SetLastScanTime", out var Query))
                    return false;

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

                using (var conn = new cDbConnection(mainDbConnection))
                {
                    if (!conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return false;
                    }

                    var retVal = await DataHistorySqlHelper.GetScalarResultAsync<int?>(conn, Query, Params, requestInfo, Token);

                    return (retVal != null && retVal > 0);
                }

            }
            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;
        }

        public async Task<Guid> CreateScanHistoryEntry(string ScanName, DateTime TimeFrom, DateTime TimeTo, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 RetVal = Guid.Empty;
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("CreateScanHistoryEntry", out var Query))
                    return RetVal;

                var RetVal2 = Guid.NewGuid();
                var Params = new Dictionary<string, object>() { { "ID", RetVal2 }, { "Name", ScanName }, { "From", TimeFrom }, { "To", TimeTo }, { "Execute", DateTime.UtcNow } };
                using (var conn = new cDbConnection(mainDbConnection))
                {
                    if (!conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return RetVal;
                    }

                    var rv = await DataHistorySqlHelper.GetScalarResultAsync<int?>(conn, Query, Params, requestInfo, Token);
                    if (rv == 1)
                        return RetVal2;
                }
            }
            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 Guid.Empty;

        }

        public async Task ConfirmScanHistoryEntry(Guid Id, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("ConfirmScanHistoryEntry", out var Query))
                    return;

                var Params = new Dictionary<string, object>() { { "ID", Id } };
                using (var conn = new cDbConnection(mainDbConnection))
                {
                    if (!conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return;
                    }

                    var ScanName = await DataHistorySqlHelper.GetScalarResultAsync<string>(conn, Query, Params, requestInfo, Token);
                    if (!string.IsNullOrEmpty(ScanName))
                        await SetLastScanTime(ScanName, DateTime.UtcNow, requestInfo, LogDeep + 1, Token);

                }
            }
            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);
            }
        }

        public async Task RemoveLastScanHistoryEntry(string ScanName, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("RemoveLastScanHistoryEntry", out var Query))
                    return;

                var Params = new Dictionary<string, object>() { { "Name", ScanName } };
                using (var conn = new cDbConnection(mainDbConnection))
                {
                    if (!conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return;
                    }

                    var executionTime = await DataHistorySqlHelper.GetScalarResultAsync<DateTime?>(conn, Query, Params, requestInfo, Token);
                    if (executionTime == null)
                        await SetLastScanTime(ScanName, new DateTime(1970, 1, 1, 0, 0, 0), requestInfo, LogDeep + 1, Token);
                    else
                        await SetLastScanTime(ScanName, (DateTime)executionTime, requestInfo, LogDeep + 1, Token);
                }
            }
            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);
            }
        }

        public async Task<Dictionary<string, cDataHistoryConfigColumnBase>> GetTableColumnValidationAsync(cDataHistoryConfigTable TableConfig, string Instance, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, cDbConnection MainConn = null)
        {
            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;

            var closeDbConnection = false;
            if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetTableColumnValidation", out var Query))
                return null;

            var Params = new Dictionary<string, object>() { { "Table", TableConfig.SourceName }, { "Instance", Instance } };
            int apiError = 0;

            try
            {

                if (MainConn == null)
                {
                    closeDbConnection = true;
                    MainConn = new cDbConnection(mainDbConnection);
                    if (!MainConn.IsOpen)
                        return null;
                }

                var RetVal = new Dictionary<string, cDataHistoryConfigColumnBase>();
                DbTransaction trans = MainConn.Connection.BeginTransaction(System.Data.IsolationLevel.Snapshot);
                try
                {
                    using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(MainConn, Query, Params, Token))
                    {
                        if (Reader != null)
                            try
                            {
                                if (Reader.HasRows)
                                {
                                    while (await Reader.ReadAsync() && !Token.IsCancellationRequested)
                                    {
                                        var strColumn = Reader.GetString(0);
                                        if (TableConfig.Columns.TryGetValue(strColumn, out var Col))
                                            RetVal[Col.Name] = Col;
                                    }
                                }
                            }
                            catch (Exception E)
                            {
                                LogException(E);
                            }
                    }

                    if (!Token.IsCancellationRequested)
                    {
                        trans.Commit();
                        return RetVal;
                    }
                }
                catch (Exception E)
                {
                    LogException(E);
                    trans.Rollback();
                }


            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (closeDbConnection && MainConn != null)
                {
                    MainConn.Dispose();
                }
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public async Task<bool> SetTableColumnValidationAsync(cDataHistoryConfigTable TableConfig, string Instance, HashSet<string> ValidColumns, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, cDbConnection MainConn = null)
        {
            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;

            var closeDbConnection = false;


            try
            {

                if (MainConn == null)
                {
                    closeDbConnection = true;
                    MainConn = new cDbConnection(mainDbConnection);
                    if (!MainConn.IsOpen)
                        return false;
                }

                var RetVal = new Dictionary<string, bool>();
                using (DbTransaction trans = MainConn.Connection.BeginTransaction(System.Data.IsolationLevel.Serializable))
                {
                    bool doCommit = false;

                    try
                    {
                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetTableColumnValidationEx", out var Query))
                            return false;

                        var Params = new Dictionary<string, object>() { { "Table", TableConfig.SourceName }, { "Instance", Instance } };
                        int apiError = 0;
                        var _startTime2 = DateTime.UtcNow;
                        try
                        {
                            using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(MainConn, Query, Params, Token, Transaction: trans))
                            {
                                if (Reader != null)
                                    try
                                    {
                                        if (Reader.HasRows)
                                        {
                                            while (await Reader.ReadAsync() && !Token.IsCancellationRequested)
                                            {
                                                var strColumn = Reader.GetString(0);
                                                var bIsValid = Reader.GetInt16(1);
                                                RetVal[strColumn] = bIsValid == 1;
                                            }
                                        }
                                    }
                                    catch (Exception E)
                                    {
                                        LogException(E);
                                    }
                            }
                        }
                        catch (Exception E)
                        {
                            apiError = E.HResult;
                            LogException(E);
                        }
                        finally
                        {
                            if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                        }



                        if (Token.IsCancellationRequested)
                            return false;

                        var toDelete = new List<string>();
                        var toUpdate = new Dictionary<string, bool>();
                        var toInsert = new Dictionary<string, bool>();

                        foreach (var Column in TableConfig.Columns.Values)
                        {
                            var isValid = ValidColumns.Contains(Column.Name);
                            if (RetVal.TryGetValue(Column.Name, out var ColInfo))
                            {
                                if (ColInfo != isValid)
                                    toUpdate[Column.Name] = isValid;
                            }
                            else
                            {
                                toInsert[Column.Name] = isValid;
                            }
                        }

                        foreach (var ColumnName in ValidColumns)
                        {
                            if (!RetVal.ContainsKey(ColumnName))
                                toDelete.Add(ColumnName);
                        }

                        // do the table inserts
                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("InsertTableColumnValidation", out Query))
                            return false;
                        foreach (var updEntry in toInsert)
                        {
                            Params = new Dictionary<string, object>() { { "Table", TableConfig.SourceName }, { "Instance", Instance }, { "Column", updEntry.Key }, { "isValid", updEntry.Value ? 1 : 0 } };
                            await DataHistorySqlHelper.ExecuteAsync(MainConn, Query, Params, requestInfo, Token, Transaction: trans);
                        }

                        // do the table updates
                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateTableColumnValidation", out Query))
                            return false;
                        foreach (var updEntry in toUpdate)
                        {
                            Params = new Dictionary<string, object>() { { "Table", TableConfig.SourceName }, { "Instance", Instance }, { "Column", updEntry.Key }, { "isValid", updEntry.Value ? 1 : 0 } };
                            await DataHistorySqlHelper.ExecuteAsync(MainConn, Query, Params, requestInfo, Token, Transaction: trans);
                        }

                        // do the table deletions
                        if (!DataHistorySqlHelper.GetWellKnownSqlStatement("DeleteTableColumnValidation", out Query))
                            return false;
                        foreach (var updEntry in toDelete)
                        {
                            Params = new Dictionary<string, object>() { { "Table", TableConfig.SourceName }, { "Instance", Instance }, { "Column", updEntry } };
                            await DataHistorySqlHelper.ExecuteAsync(MainConn, Query, Params, requestInfo, Token, Transaction: trans);
                        }

                        doCommit = true;
                        return true;
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                    finally
                    {
                        if (doCommit)
                            trans.Commit();
                        else
                            trans.Rollback();
                    }
                }

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (closeDbConnection && MainConn != null)
                {
                    MainConn.Dispose();
                }
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public async Task<List<string>> GetQuickActionAllList()
        {
            var returnValue = new List<string>();
            foreach (var Connector in GetConnectors())
            {
                var _quickActions = await Connector.GetQuickActionList();
                if (_quickActions != null && _quickActions.Count > 0)
                    returnValue.AddRange(_quickActions);
            }


            return returnValue;
        }

        public bool CheckCleanupTimeframes()
        {
            if (InfrastructureConfig.HistoryDB.CleanupTimeFrames == null)
                return true;

            if (InfrastructureConfig.HistoryDB.CleanupTimeFrames.Count == 0)
                return true;

            foreach (var TF in InfrastructureConfig.HistoryDB.CleanupTimeFrames)
            {
                if (TF.Check())
                    return true;
            }

            return false;
        }

        private async Task<HashSet<Guid>> getCleanupScanIds(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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;

            if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetScanHistoryCleanup", out var Query))
                return null;

            var Params = new Dictionary<string, object>() { { "Age1", InfrastructureConfig.HistoryDB.DaysToCache }, { "Age2", 60 } };
            int apiError = 0;

            try
            {

                using (var Conn = new cDbConnection(mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main database '{mainDbConnection.Database}', aborting  query'", LogLevels.Error);
                        return null;
                    }

                    using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(Conn, Query, Params, Token))
                    {
                        if (Reader != null)
                            try
                            {
                                var RetVal = new HashSet<Guid>();
                                if (Reader.HasRows)
                                {
                                    while (await Reader.ReadAsync() && !Token.IsCancellationRequested)
                                        try
                                        {
                                            var _id = Reader.GetGuid(0);
                                            RetVal.Add(_id);
                                        }
                                        catch (Exception E)
                                        {
                                            LogException(E);
                                        }
                                }

                                return RetVal;
                            }
                            catch (Exception E)
                            {
                                LogException(E);
                            }
                            finally
                            {
                                if (CM != null) LogMethodEnd(CM);
                            }
                    }
                }
            }
            catch (Exception E)
            {
                apiError = E.HResult;
                LogException(E);
            }
            finally
            {
                if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Params, apiError, requestInfo?.requestName);
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return null;

        }

        public async Task<int> CheckCleanup(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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 l = await getCleanupScanIds(requestInfo, LogDeep + 1, Token);
                if (l != null)
                    return l.Count;
            }
            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 0;
        }

        public async Task<int> DoCleanup(bool DontUseScanTiming, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (!DontUseScanTiming && !CheckCleanupTimeframes())
                {
                    DoProcessUiMessage(0, "The current time is not within the defined scan time intervals.");

                    return -2;
                }


                // get the historic scan ids to cleanup
                var lstIds = await getCleanupScanIds(requestInfo, LogDeep + 1, Token);
                if (lstIds == null)
                    return -1;
                if (lstIds.Count == 0)
                {
                    DoProcessUiMessage(0, "No outdated scans found. Cleanup is not nescessary.");
                    return 0;
                }

                DoProcessUiMessage(0, $"{lstIds.Count} outdated scans found. Starting cleanup...");

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("DeleteScanHistoryEntry", out var Query))
                    return -1;

                Query.TimeoutFactor = 100;
                using (var Conn = new cDbConnection(mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main database '{mainDbConnection.Database}', aborting  query'", LogLevels.Error);
                        return -1;
                    }
                    var ValidRemoves = 0;
                    foreach (var _id in lstIds)
                    {
                        try
                        {
                            if (!DontUseScanTiming && !CheckCleanupTimeframes())
                                return ValidRemoves;

                            var Params = new Dictionary<string, object>(1) { { "ID", _id } };
                            var n = await DataHistorySqlHelper.ExecuteAsync(Conn, Query, Params, requestInfo, Token);
                            if (n != null && n > 0)
                                ValidRemoves += (int)n;
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                    }


                    DoProcessUiMessage(0, $"{ValidRemoves} history scans removed.");

                    return ValidRemoves;
                }
            }
            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 -1;
        }

        public override async Task<bool> WritePropertyAsync(cF4SDWriteParameters PropertiesToWrite, cF4sdWebRequestInfo requestInfo, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                // check if properties are writable
                if (!this.ClusterConfig.Tables.TryGetValue(PropertiesToWrite.TableName, out var tableConfig))
                    return false;

                if (PropertiesToWrite.Values == null)
                    return false;

                var lstIllegal = new List<string>();
                foreach (var Entry in PropertiesToWrite.Values)
                {
                    if (!tableConfig.Columns.TryGetValue(Entry.Key, out var colInfo) || !colInfo.IsWritable)
                        lstIllegal.Add(Entry.Key);
                }
                foreach (var Entry in lstIllegal)
                    PropertiesToWrite.Values.Remove(Entry);

                if (PropertiesToWrite.Values.Count == 0)
                    return false;

                var result = lstIllegal.Count == 0;

                if (this.Connectors.TryGetValue(tableConfig.ParentCluster.Origin, out var _mod))
                {
                    var res = await _mod.WritePropertyAsync(PropertiesToWrite, requestInfo, Token);
                    if (res)
                    {

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

            return false;

        }

        private Guid GetUserHashGuid(Guid userId)
        {
            // check, if we have a GUID
            if (!Guid.TryParse(userId.ToString(), out var _guid))
                return Guid.Empty;

            // convert the GUID into a byte arry and 'salt' it a little bit
            var _arr = _guid.ToByteArray();
            if (_arr.Length != 16) return Guid.Empty;
            var _arrSalt = new byte[3 * 16];
            for (byte i = 0; i < 16; i++)
            {
                byte j = Convert.ToByte(i * 3);
                _arrSalt[j] = _arr[i];
                _arrSalt[j + 1] = _arr[i];
                _arrSalt[j + 2] = j;
            }

            // get the md5 hash value (128 bit = 16 byte)
            var _md5 = MD5.Create();
            var _arrHash = _md5.ComputeHash(_arrSalt);
            if (_arrHash == null) return Guid.Empty;
            if (_arrHash.Length != 16) return Guid.Empty;

            // convert the hash value into a GUID
            return new Guid(_arrHash);
        }

        public void AddRolesByMembership(cF4sdUserInfo userInfo, List<cF4SDMembershipGroup> _membershipGroups)
        {
            if (userInfo.Roles == null)
                userInfo.Roles = new List<string>();
            AddRolesByMembership(userInfo.Roles, _membershipGroups, userInfo.Name);
        }

        public void AddRolesByMembership(List<string> Roles, List<cF4SDMembershipGroup> _membershipGroups, string userName)
        {
            if (_membershipGroups?.Count > 0)
            {
                var _roles = InfrastructureConfig?.Authorisation?.Roles;
                if (_roles != null)
                    foreach (var _roleEntry in _roles.Values)
                    {
                        if (_roleEntry.MembershipGroups != null)
                            foreach (var _grp in _roleEntry.MembershipGroups)
                            {
                                if (_membershipGroups.Contains(_grp))
                                {
                                    if (Roles?.Contains(_roleEntry.Name) != true)
                                    {
                                        if (Roles == null)
                                            Roles = new List<string>();
                                        Roles.Add(_roleEntry.Name);
                                        Debug.WriteLine($"    {userName} is member of '{_roleEntry.Name}'");
                                    }
                                }
                            }
                    }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static int getDayIndex(DateTime EventTime, DateTime RefTime, int HistorySize)
        {
            var deltaTime = (EventTime - RefTime).TotalDays;
            int indexDay;
            if (deltaTime >= 0)
                indexDay = (int)Math.Floor(deltaTime);
            else
                indexDay = -(int)Math.Ceiling(-deltaTime);

            if (indexDay > 0)
                indexDay = 0;
            indexDay = -indexDay;
            if (indexDay >= HistorySize)
                indexDay = HistorySize - 1;

            return indexDay;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool CheckIfNotEmpty(object _o)
        {
            if (_o == null) return false;
            if (_o is string _s && string.IsNullOrEmpty(_s)) return false;
            return true;
        }

        public override async Task<cScanTimeInfo> GetScanTimeInfoAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            await Task.CompletedTask;
            return null;
        }

        public override async Task<bool> DoScanAsync(bool Always, bool Rescan, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool EnhancedDebug = false)
        {
            await Task.CompletedTask;
            return true;
        }

        public static string getStingList(List<string> L)
        {
            if (L == null || L.Count == 0)
                return null;

            var RetVal = "";
            foreach (var Entry in L)
            {
                if (string.IsNullOrEmpty(Entry))
                    continue;
                if (RetVal != "")
                    RetVal += ',';
                RetVal += Entry;
            }
            return RetVal;
        }

        public cCockpitConfiguration GetCockpitConfiguration(cF4sdWebRequestInfo requestInfo)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var retVal = new cCockpitConfiguration();

                var AgentConfig = InfrastructureConfig?.ClientAgent;
                if (AgentConfig != null)
                {
                    retVal.agentApiConfiguration = F4sdAgent.GetAgentApiConfiguration(false);
                }

                if (InfrastructureConfig?.M42Wpm?.Server != null)
                {
                    var _lang = requestInfo.Culture?.TwoLetterISOLanguageName?.ToUpperInvariant() ?? "";
                    retVal.m42ServerConfiguration = new cM42ServerConfiguration()
                    {
                        Server = InfrastructureConfig.M42Wpm.Server,
                        DisplayName = InfrastructureConfig.M42Wpm.DisplayNames.GetValue(_lang, null)
                    };
                }

                if (RefreshGlobalConfigurationParameters())
                    lock (GlobalParametersConfigLock)
                        retVal.GlobalConfig = GlobalParametersConfigFile.Parameters;

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

            return null;
        }

        public cCollectorStatusInfo CheckCollectorStatus(cF4sdWebRequestInfo requestInfo)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var _cfgInfrastructure = new cDataHistoryConfigInfrastructure();
                _cfgInfrastructure.LoadFromFile(out var PMs);
                var retVal = new cCollectorStatusInfo();

                retVal.HasM42Config = _cfgInfrastructure.M42Wpm != null;
                retVal.HasIntuneConfig = (_cfgInfrastructure.AzureTenants?.Values.Any(t => t.ScanIntuneDevices) == true);
                retVal.HasMobileDeviceConfig = (_cfgInfrastructure.AzureTenants?.Values.Any(t => t.WithMobileDevices) == true);
                retVal.HasCitrixConfig = (InfrastructureConfig.Citrix != null);

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

            return null;
        }

        public override async Task<cF4SdUserInfoChange> ValidateTokenAsync(cF4SDTokenRegistration TokenRegistration, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                if (TokenCache == null)
                    return null;

                if (TokenRegistration.UserId != requestInfo.userInfo?.Id)
                    return null;

                cF4SdUserInfoChange _userInfoChange = null;
                switch (TokenRegistration?.TokenType)
                {
                    case cF4SDTokenRegistration.enumTokenType.M42Bearer:
                        _userInfoChange = await this.M42WpmCollector.ValidateTokenAsync(TokenRegistration, requestInfo, LogDeep + 1, Token);
                        break;
                }

                if (_userInfoChange == null || _userInfoChange.ValidLogonsUntil == null || _userInfoChange.ValidLogonsUntil.Count == 0)
                    return null;

                var validPeriod = _userInfoChange.ValidLogonsUntil.First().Value;
                if (validPeriod.validUntil <= DateTime.UtcNow)
                    return null;

                await TokenCache.SetAsync(TokenRegistration, validPeriod, Token, requestInfo, LogDeep + 1);

                return _userInfoChange;
            }
            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 null;
        }

        public override async Task<cF4SDAdditionalUserInfo> GetAdditionalUserInfo(enumAdditionalAuthentication Type, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            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
            {
                switch (Type)
                {
                    case enumAdditionalAuthentication.M42WinLogon:
                        return await M42WpmCollector.GetAdditionalUserInfo(Type, requestInfo, LogDeep + 1, Token);
                }
            }
            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 null;
        }

        public HashSet<enumFasdInformationClass> GetSupportedInformationClasses() => new HashSet<enumFasdInformationClass>() { enumFasdInformationClass.User, enumFasdInformationClass.Computer };

        public async Task<cF4sdStagedSearchResultRelations> GetRelationsAsync(IEnumerable<cF4sdConnectorIds> ids, enumFasdInformationClass informationClass, int age, CancellationToken token = default)
        {
            List<cF4sdApiSearchResultRelation> usageRelations = await GetUsageFromOnlyAgentData(ids, informationClass, age, token, null, 1);
            return new cF4sdStagedSearchResultRelations() { Relations = usageRelations };
        }

        public void RegisterTokenValidator(cF4SDTokenRegistration.enumTokenType tokenType, cDataHistoryCollectorModule module)
        {
            _tokenValidators[tokenType] = module;
        }

        public async override Task<int> ValidateTokenInfo(cDataHistoryCollectorTokenCache.cTokenInfo tokenInfo)
        {
            if (_tokenValidators.TryGetValue(tokenInfo.type, out var module))
                return await module.ValidateTokenInfo(tokenInfo);
            return 0;
        }

        public class cForeignUserInfoEntry
        {
            public Guid MainID { get; set; }
            public enumDataHistoryOrigin Origin { get; set; }
            public string InstanceID { get; set; }
            public string ForeignID { get; set; }
        }

        public class cForeignUserInfo : Dictionary<enumDataHistoryOrigin, List<cForeignUserInfoEntry>> { }

        public async Task<cForeignUserInfo> GetForeignUserInfoAsync(Guid MainID, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetForeignUserInfo", out var QueryGetUser))
                    return null;

                using (var Conn = new cDbConnection(Collector.mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{Collector.mainDbConnection.Database}', aborting scan...'", LogLevels.Error);
                        return null;
                    }

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

                    using (var Reader = await DataHistorySqlHelper.GetDataReaderAsync(Conn, QueryGetUser, Params, token))
                    {
                        if (Reader != null)
                            if (Reader.HasRows)
                            {
                                var _retVal = new cForeignUserInfo();
                                while (await Reader.ReadAsync(token))
                                {
                                    try
                                    {
                                        var Entry = new cForeignUserInfoEntry();
                                        Entry.MainID = Reader.GetGuid(0);
                                        Entry.Origin = (enumDataHistoryOrigin)Reader.GetInt32(1);
                                        Entry.InstanceID = Reader.GetString(2);
                                        Entry.ForeignID = Reader.GetString(3);

                                        if (!_retVal.TryGetValue(Entry.Origin, out var _lst))
                                        {
                                            _lst = new List<cForeignUserInfoEntry>();
                                            _retVal.Add(Entry.Origin, _lst);
                                        }
                                        _lst.Add(Entry);
                                    }
                                    catch (Exception E)
                                    {
                                        cLogManager.DefaultLogger.LogException(E);
                                    }
                                }

                                return _retVal;

                            }
                    }

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

            return null;
        }
    }

    public class cScanTimeInfo
    {
        public readonly DateTime LastScan;
        public DateTime? NextScan = null;

        public SortedList<DateTime, DateTime> Intervals = new SortedList<DateTime, DateTime>();

        public cScanTimeInfo(DateTime LastScan)
        {
            this.LastScan = LastScan;
        }


        private DateTime privGetScanTimeIntervals(DateTime time, DateTime LastScan, cDataHistoryScanTiming Timing)
        {
            try
            {
                var refTime = TimeZoneInfo.ConvertTimeFromUtc(time, Timing.Timezone);
                refTime = refTime.Date + Timing.Offset;
                refTime = refTime.ToUniversalTime();

                while (refTime > time)
                    refTime -= TimeSpan.FromDays(1);

                var diff = time - refTime;
                var nDiff = diff.TotalMilliseconds / Timing.Interval.TotalMilliseconds;
                nDiff = Math.Floor(nDiff);

                var retVal = refTime + TimeSpan.FromMilliseconds(nDiff * Timing.Interval.TotalMilliseconds);

                DateTime intv2 = retVal;
                DateTime intv1 = intv2 - Timing.Interval;
                while (LastScan < intv2)
                {
                    Intervals.Add(intv1, intv2);
                    intv2 -= Timing.Interval;
                    intv1 = intv2 - Timing.Interval;
                }
                ;

                return retVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }

            return DateTime.MaxValue;
        }

        public DateTime GetScanTime(DateTime time, cDataHistoryScanTiming Timing)
        {
            return privGetScanTimeIntervals(time, LastScan, Timing);
        }

        public void GetFirstScanTime(cDataHistoryScanTiming Timing)
        {
            var _now = DateTime.UtcNow;
            NextScan = privGetScanTimeIntervals(_now, _now - Timing.Interval, Timing);
        }

        static public int GetScanIntervalIndex(DateTime time, DateTime timeFrom, DateTime timeTo, cDataHistoryScanTiming Timing)
        {
            try
            {
                var refTime = TimeZoneInfo.ConvertTimeFromUtc(time, Timing.Timezone);
                refTime = refTime.Date + Timing.Offset;
                refTime = refTime.ToUniversalTime();

                var Diff = (time - refTime).TotalMilliseconds / Timing.Interval.TotalMilliseconds;
                Diff = Math.Round(Diff);
                var nDiff = Convert.ToInt32(Diff);
                refTime = refTime.AddMilliseconds(nDiff * Timing.Interval.TotalMilliseconds);


                var timeCenter = timeFrom.AddTicks((timeTo - timeFrom).Ticks / 2);
                Diff = (refTime - timeCenter).TotalMilliseconds / Timing.Interval.TotalMilliseconds;
                Diff = Math.Ceiling(Diff);
                nDiff = Convert.ToInt32(Diff);

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

            return -1;
        }

    }

    public class cDataHistoryInfoClassListEntry
    {
        public string Name { get; set; }
        public Guid id { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }

    public class cF4sdConnectorIds
    {
        public class localIdEntry
        {
            public int agentId;
            public DateTime lastSeenByAgent;
            public string account;
        }

        public enumFasdInformationClass infoClass { get; set; } = enumFasdInformationClass.Unknown;
        public Guid Id { get; set; }
        public Guid Instance { get; set; } = Guid.Empty;

        public int? agentId { get; set; } = null;
        public int? agentId2 { get; set; } = null;
        public List<localIdEntry> localAgentIds { get; set; } = null;
        public byte[] sid { get; set; } = null;

        public string name { get; set; } = null;

        public DateTime? lastSeenByAgent { get; set; } = null;

        public enumAccountType accountType { get; set; } = enumAccountType.unknow;

        public Guid tenantId { get; set; }
        public Guid intuneId { get; set; }
        public Guid azureDeviceId { get; set; }

        public cDataHistoryCollector.cForeignUserInfo ForeignUserInfo = null;

        [Obsolete]
        public string citrixTenantId { get; set; }

        [Obsolete]
        public int? citrixUserId { get; set; }
    }

    public class cDataHistoryUiStatusMessage
    {
        public int Indent = 0;
        public string Message;

        public static cDataHistoryUiStatusMessage Create(int Indent, string Message)
        {
            return new cDataHistoryUiStatusMessage()
            {
                Indent = Indent,
                Message = Message
            };
        }
    }

    public class cCollectorStatusInfo
    {
        public bool HasM42Config;
        public bool HasIntuneConfig;
        public bool HasMobileDeviceConfig;
        public bool HasCitrixConfig;
    }

    public class cDataHistorySHistoryScanInfo
    {
        public Guid ID;
        public string Scan;
        public DateTime TimeFrom;
        public DateTime TimeTo;
    }

    internal class cDataHistoryUsageIdInfo
    {
        internal Guid Id;
        internal enumAccountType AccountType = enumAccountType.unknow;
        internal string account = null;
        internal string domain = null;
    }

    public class cF4sdWebRequestInfo
    {
        public readonly string requestName;
        public readonly string id = null;
        public readonly DateTime created = DateTime.UtcNow;
        public readonly cF4sdUserInfo userInfo = null;
        public readonly CultureInfo Culture = null;
        public cM42WebClient M42WebClient = null;
        public int SqlDuration = 0;
        private List<(string, int)> privSqlDurations { get; set; } = null;
        public List<(string, int)> SqlDurations
        {
            get
            {
                if (privSqlDurations == null)
                    privSqlDurations = new List<(string, int)>();
                return privSqlDurations;
            }
        }

        public cF4sdWebRequestInfo(string requestName, string id, cF4sdUserInfo userInfo = null)
        {
            try
            {
                this.requestName = requestName;
                this.id = requestName + "-" + id;
                this.userInfo = userInfo;
                if (!string.IsNullOrEmpty(userInfo?.Language))
                {
                    try
                    {
                        this.Culture = CultureInfo.GetCultureInfoByIetfLanguageTag(userInfo.Language);
                    }
                    catch { }
                }
                if (cLogManager.DefaultLogger.IsDebug) LogEntry($"authentication user language: {userInfo?.Language}, user culture: {this.Culture?.Name}", LogLevels.Debug);
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }
    }

    public class cHistoricAggregationEntry
    {
        internal cDataHistoryConfigColumn Column;
        internal int JsonSubValue = -1;
        internal int HistorySize = 0;

        internal object LatestValue = null;
        internal DateTime? LastestEvent = null;

        internal Int64?[] aggInt = null;
        internal Double?[] aggDouble = null;
        internal Double?[] weightDouble = null;
        internal String[] aggString = null;
        internal List<string>[] listString = null;
        internal Boolean?[] aggBoolean = null;
        internal DateTime?[] aggEvent = null;
        internal DateTime?[] aggTime = null;
        internal Version[] aggVersion = null;
        internal Guid?[] aggGuid = null;

        private object lockAggregation = new object();

        internal cHistoricAggregationEntry(cDataHistoryConfigColumn Column, int HistorySize, Dictionary<string, string[]> JsonSettings)
        {
            this.Column = Column;
            this.HistorySize = HistorySize;

            if (Column.SourceJsonColumn >= 0)
                JsonSubValue = Column.SourceJsonColumn;
            else if (Column.SourceJsonField != null && JsonSettings != null)
            {
                if (JsonSettings.TryGetValue(Column.SourceName, out var jsonList))
                {
                    for (int i = 0; i < jsonList.Length; i++)
                        if (jsonList[i] == Column.SourceJsonField)
                        {
                            JsonSubValue = i;
                            break;
                        }
                }
            }

            switch (Column.AggregationType)
            {
                case eDataHistoryAggregationType.average:
                    aggDouble = new Double?[HistorySize];
                    weightDouble = new Double?[HistorySize];
                    return;
                case eDataHistoryAggregationType.latestTime:
                    aggEvent = new DateTime?[HistorySize];
                    return;
                case eDataHistoryAggregationType.count:
                case eDataHistoryAggregationType.valuecount:
                    aggInt = new Int64?[HistorySize];
                    return;
                case eDataHistoryAggregationType.Unknown:
                    return;
                case eDataHistoryAggregationType.latestValue:
                case eDataHistoryAggregationType.first:
                    aggEvent = new DateTime?[HistorySize];
                    break;
            }

            switch (Column.ValueType)
            {
                case enumFasdValueType.INT:
                case enumFasdValueType.BIGINT:
                    aggInt = new Int64?[HistorySize];
                    return;
                case enumFasdValueType.FLOAT:
                    aggDouble = new Double?[HistorySize];
                    return;
                case enumFasdValueType.DATETIME:
                    aggTime = new DateTime?[HistorySize];
                    return;
                case enumFasdValueType.VERSION:
                    switch (Column.AggregationType)
                    {
                        case eDataHistoryAggregationType.sum:
                            listString = new List<string>[HistorySize];
                            return;
                    }
                    aggVersion = new Version[HistorySize];
                    return;
                case enumFasdValueType.GUID:
                    aggGuid = new Guid?[HistorySize];
                    return;
                case enumFasdValueType.BOOLEAN:
                    aggBoolean = new bool?[HistorySize];
                    return;
                case enumFasdValueType.STRING:
                case enumFasdValueType.IPV4:
                    switch (Column.AggregationType)
                    {
                        case eDataHistoryAggregationType.sum:
                            listString = new List<string>[HistorySize];
                            return;
                    }
                    aggString = new String[HistorySize];
                    return;
            }
        }

        internal void addValue(object Value, DateTime EventTime, DateTime RefTime, Int32 EventDuration)
        {
            try
            {
                var indexDay = cDataHistoryCollector.getDayIndex(EventTime, RefTime, HistorySize);

                if (Value is cDataHistoryCustomJsonMeasure _cm)
                {
                    if (_cm.Values == null)
                        return;
                    if (_cm.ErrorCode != null && _cm.ErrorCode != 0)
                        return;
                    if (!string.IsNullOrEmpty(_cm.ErrorDescription))
                        return;
                    if (_cm.ResultCode != null && _cm.ResultCode != 0)
                        return;

                    var _s = _cm.Values.GetType().ToString();

                    if (_cm.Values is JValue _jVal)
                        Value = _jVal.Value;
                    else if (_cm.Values is JArray _jArr)
                    {
                        var _res = new object[_jArr.Count];
                        var i = 0;
                        foreach (var _jEntry in _jArr)
                        {
                            _res[i] = null;
                            if (_jEntry is JValue _jVal2)
                            {
                                _res[i] = _jVal2.Value;
                            }
                            i++;
                        }
                        Value = _res;
                    }
                }

                if ((JsonSubValue >= 0) && (Value is object[] arrVals))
                    Value = arrVals[JsonSubValue];

                lock (lockAggregation)
                {
                    if ((LastestEvent == null || EventTime >= LastestEvent) && !(Value is object[]))
                    {
                        LatestValue = Value;
                        LastestEvent = EventTime;
                    }

                    switch (Column.AggregationType)
                    {
                        case eDataHistoryAggregationType.average:
                            if (cDataHistoryCollector.CheckIfNotEmpty(Value))
                            {
                                switch (Column.ValueType)
                                {
                                    case enumFasdValueType.INT:
                                    case enumFasdValueType.BIGINT:
                                    case enumFasdValueType.FLOAT:
                                        var val01 = Convert.ToDouble(Value);
                                        if (weightDouble[indexDay] == null)
                                            weightDouble[indexDay] = 0;
                                        weightDouble[indexDay] += EventDuration;
                                        if (aggDouble[indexDay] == null)
                                            aggDouble[indexDay] = 0;
                                        aggDouble[indexDay] += val01 * EventDuration;
                                        return;
                                }
                            }
                            return;
                        case eDataHistoryAggregationType.latestTime:
                            if (aggEvent[indexDay] == null || aggEvent[indexDay] < EventTime)
                                aggEvent[indexDay] = EventTime;
                            return;
                        case eDataHistoryAggregationType.count:
                            if (aggInt[indexDay] == null)
                                aggInt[indexDay] = 1;
                            else
                                aggInt[indexDay]++;
                            return;
                        case eDataHistoryAggregationType.valuecount:
                            if (cDataHistoryCollector.CheckIfNotEmpty(Value))
                            {
                                if (aggInt[indexDay] == null)
                                    aggInt[indexDay] = 1;
                                else
                                    aggInt[indexDay]++;
                            }
                            return;
                        case eDataHistoryAggregationType.Unknown:
                            return;
                    }

                    switch (Column.ValueType)
                    {
                        case enumFasdValueType.INT:
                        case enumFasdValueType.BIGINT:
                            if (cDataHistoryCollector.CheckIfNotEmpty(Value))
                            {
                                var val02 = Convert.ToInt64(Value);
                                switch (Column.AggregationType)
                                {
                                    case eDataHistoryAggregationType.min:
                                        if (aggInt[indexDay] == null)
                                            aggInt[indexDay] = val02;
                                        else
                                            aggInt[indexDay] = Math.Min((Int64)aggInt[indexDay], val02);
                                        return;
                                    case eDataHistoryAggregationType.max:
                                        if (aggInt[indexDay] == null)
                                            aggInt[indexDay] = val02;
                                        else
                                            aggInt[indexDay] = Math.Max((Int64)aggInt[indexDay], val02);
                                        return;
                                    case eDataHistoryAggregationType.sum:
                                        if (aggInt[indexDay] == null)
                                            aggInt[indexDay] = val02;
                                        else
                                            aggInt[indexDay] += val02;
                                        return;
                                    case eDataHistoryAggregationType.first:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggInt[indexDay] = val02;
                                        }
                                        else
                                        {
                                            if (EventTime < aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggInt[indexDay] = val02;
                                            }
                                        }
                                        return;
                                    case eDataHistoryAggregationType.latestValue:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggInt[indexDay] = val02;
                                        }
                                        else
                                        {
                                            if (EventTime > aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggInt[indexDay] = val02;
                                            }
                                        }
                                        return;
                                }
                            }
                            return;
                        case enumFasdValueType.FLOAT:
                            if (cDataHistoryCollector.CheckIfNotEmpty(Value))
                            {
                                var val03 = Convert.ToDouble(Value);
                                switch (Column.AggregationType)
                                {
                                    case eDataHistoryAggregationType.min:
                                        if (aggDouble[indexDay] == null)
                                            aggDouble[indexDay] = val03;
                                        else
                                            aggDouble[indexDay] = Math.Min((Double)aggDouble[indexDay], val03);
                                        return;
                                    case eDataHistoryAggregationType.max:
                                        if (aggDouble[indexDay] == null)
                                            aggDouble[indexDay] = val03;
                                        else
                                            aggDouble[indexDay] = Math.Max((Double)aggDouble[indexDay], val03);
                                        return;
                                    case eDataHistoryAggregationType.sum:
                                        if (aggDouble[indexDay] == null)
                                            aggDouble[indexDay] = val03;
                                        else
                                            aggDouble[indexDay] += val03;
                                        return;
                                    case eDataHistoryAggregationType.first:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggDouble[indexDay] = val03;
                                        }
                                        else
                                        {
                                            if (EventTime < aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggDouble[indexDay] = val03;
                                            }
                                        }
                                        return;
                                    case eDataHistoryAggregationType.latestValue:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggDouble[indexDay] = val03;
                                        }
                                        else
                                        {
                                            if (EventTime > aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggDouble[indexDay] = val03;
                                            }
                                        }
                                        return;
                                }
                            }
                            return;
                        case enumFasdValueType.DATETIME:
                            if (cDataHistoryCollector.CheckIfNotEmpty(Value))
                            {
                                var val04 = Convert.ToDateTime(Value);
                                switch (Column.AggregationType)
                                {
                                    case eDataHistoryAggregationType.min:
                                        if (aggTime[indexDay] == null)
                                            aggTime[indexDay] = val04;
                                        else if (val04 < aggTime[indexDay])
                                            aggTime[indexDay] = val04;
                                        return;
                                    case eDataHistoryAggregationType.max:
                                        if (aggTime[indexDay] == null)
                                            aggTime[indexDay] = val04;
                                        else if (val04 > aggTime[indexDay])
                                            aggTime[indexDay] = val04;
                                        return;
                                    case eDataHistoryAggregationType.sum:
                                        return;
                                    case eDataHistoryAggregationType.first:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggTime[indexDay] = val04;
                                        }
                                        else
                                        {
                                            if (EventTime < aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggTime[indexDay] = val04;
                                            }
                                        }
                                        return;
                                    case eDataHistoryAggregationType.latestValue:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggTime[indexDay] = val04;
                                        }
                                        else
                                        {
                                            if (EventTime > aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggTime[indexDay] = val04;
                                            }
                                        }
                                        return;
                                }
                            }
                            return;
                        case enumFasdValueType.VERSION:
                            if (Value != null && Version.TryParse(Value.ToString(), out var val05))
                            {
                                switch (Column.AggregationType)
                                {
                                    case eDataHistoryAggregationType.min:
                                        if (aggVersion[indexDay] == null)
                                            aggVersion[indexDay] = val05;
                                        else if (val05 < aggVersion[indexDay])
                                            aggVersion[indexDay] = val05;
                                        return;
                                    case eDataHistoryAggregationType.max:
                                        if (aggVersion[indexDay] == null)
                                            aggVersion[indexDay] = val05;
                                        else if (val05 > aggVersion[indexDay])
                                            aggVersion[indexDay] = val05;
                                        return;
                                    case eDataHistoryAggregationType.sum:
                                        var strVal05 = val05.ToString();
                                        if (listString[indexDay] == null)
                                            listString[indexDay] = new List<string>() { strVal05 };
                                        else if (listString[indexDay].Count < 32 && !listString[indexDay].Contains(strVal05))
                                            listString[indexDay].Add(strVal05);
                                        return;
                                    case eDataHistoryAggregationType.first:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggVersion[indexDay] = val05;
                                        }
                                        else
                                        {
                                            if (EventTime < aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggVersion[indexDay] = val05;
                                            }
                                        }
                                        return;
                                    case eDataHistoryAggregationType.latestValue:
                                        if (aggEvent[indexDay] == null)
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggVersion[indexDay] = val05;
                                        }
                                        else
                                        {
                                            if (EventTime > aggEvent[indexDay])
                                            {
                                                aggEvent[indexDay] = EventTime;
                                                aggVersion[indexDay] = val05;
                                            }
                                        }
                                        return;
                                }
                            }
                            return;
                        case enumFasdValueType.GUID:
                            if (Value == null)
                                return;
                            Guid _guid = Guid.Empty;
                            if (Value is Guid _guid2)
                                _guid = _guid2;
                            else
                            {
                                if (Guid.TryParse(Value.ToString(), out var _guid3))
                                    _guid = _guid3;
                                else
                                    return;
                            }
                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.first:
                                    if (aggEvent[indexDay] == null)
                                    {
                                        aggEvent[indexDay] = EventTime;
                                        aggGuid[indexDay] = _guid;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent[indexDay])
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggGuid[indexDay] = _guid;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent[indexDay] == null)
                                    {
                                        aggEvent[indexDay] = EventTime;
                                        aggGuid[indexDay] = _guid;
                                    }
                                    else
                                    {
                                        if (EventTime > aggEvent[indexDay])
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggGuid[indexDay] = _guid;
                                        }
                                    }
                                    return;
                            }
                            return;
                        case enumFasdValueType.BOOLEAN:
                            if (!cDataHistoryCollector.CheckIfNotEmpty(Value))
                                return;

                            bool val06 = false;
                            if (Value is bool v)
                                val06 = v;
                            else if (Value is string val06Str)
                            {
                                switch (val06Str.Trim().ToLower())
                                {
                                    case "1":
                                    case "true":
                                    case "yes":
                                        val06 = true;
                                        break;
                                    case "0":
                                    case "false":
                                    case "no":
                                        break;
                                    default:
                                        return;
                                }
                            }
                            else if (Value is sbyte
                                    || Value is byte
                                    || Value is short
                                    || Value is ushort
                                    || Value is int
                                    || Value is uint
                                    || Value is long
                                    || Value is ulong)
                            {
                                var val06Int = Convert.ToInt64(Value);
                                if (val06Int == 0) { }
                                else if (val06Int == 1)
                                    val06 = true;
                                return;
                            }
                            else if (Value is float
                                    || Value is double
                                    || Value is decimal)
                            {
                                var val06Double = Convert.ToDouble(Value);
                                if (val06Double == 0) { }
                                else if (val06Double == 1)
                                    val06 = true;
                                else
                                    return;
                            }
                            else
                                return;

                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.min:
                                    if (aggBoolean[indexDay] == null)
                                        aggBoolean[indexDay] = val06;
                                    else
                                        aggBoolean[indexDay] &= val06;
                                    return;
                                case eDataHistoryAggregationType.max:
                                    if (aggBoolean[indexDay] == null)
                                        aggBoolean[indexDay] = val06;
                                    else
                                        aggBoolean[indexDay] |= val06;
                                    return;
                                case eDataHistoryAggregationType.sum:
                                    return;
                                case eDataHistoryAggregationType.first:
                                    if (aggEvent[indexDay] == null)
                                    {
                                        aggEvent[indexDay] = EventTime;
                                        aggBoolean[indexDay] = val06;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent[indexDay])
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggBoolean[indexDay] = val06;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent[indexDay] == null)
                                    {
                                        aggEvent[indexDay] = EventTime;
                                        aggBoolean[indexDay] = val06;
                                    }
                                    else
                                    {
                                        if (EventTime > aggEvent[indexDay])
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggBoolean[indexDay] = val06;
                                        }
                                    }
                                    return;
                            }
                            return;
                        default:
                            if (Value == null)
                                return;
                            var val07 = Value.ToString();
                            if (val07 == "")
                                return;
                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.sum:
                                    if (listString[indexDay] == null)
                                        listString[indexDay] = new List<string>() { val07 };
                                    else if (listString[indexDay].Count < 32 && !listString[indexDay].Contains(val07))
                                        listString[indexDay].Add(val07);
                                    return;
                                case eDataHistoryAggregationType.first:
                                    if (aggEvent[indexDay] == null)
                                    {
                                        aggEvent[indexDay] = EventTime;
                                        aggString[indexDay] = val07;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent[indexDay])
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggString[indexDay] = val07;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent[indexDay] == null)
                                    {
                                        aggEvent[indexDay] = EventTime;
                                        aggString[indexDay] = val07;
                                    }
                                    else
                                    {
                                        if (EventTime > aggEvent[indexDay])
                                        {
                                            aggEvent[indexDay] = EventTime;
                                            aggString[indexDay] = val07;
                                        }
                                    }
                                    return;
                            }
                            return;
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
                LogEntry($"error while processing historical value from {Column.ParentTable.Name}/{Column.Name}: {Value}", LogLevels.Error);
            }
        }

        internal List<object> getValues()
        {
            try
            {
                var RetVal = getValuesRaw();
                if (RetVal == null || !Column.FillEmptyEntries)
                    return RetVal;

                object LastVal = null;
                for (int i = RetVal.Count - 1; i >= 0; i--)
                {
                    if (RetVal[i] == null)
                        RetVal[i] = LastVal;
                    else
                        LastVal = RetVal[i];
                }

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

            return null;
        }

        internal List<object> getValuesRaw()
        {
            try
            {
                List<object> retVal;
                switch (Column.AggregationType)
                {
                    case eDataHistoryAggregationType.average:
                        switch (Column.ValueType)
                        {
                            case enumFasdValueType.INT:
                            case enumFasdValueType.BIGINT:
                            case enumFasdValueType.FLOAT:
                                retVal = new List<object>(HistorySize);
                                for (int i = 0; i < HistorySize; i++)
                                {
                                    if (weightDouble[i] == 0 || aggDouble[i] == null)
                                        retVal.Add(null);
                                    else
                                        retVal.Add(aggDouble[i] / weightDouble[i]);
                                }
                                return retVal;
                        }
                        return null;
                    case eDataHistoryAggregationType.latestTime:
                        retVal = aggEvent?.Select(x => (object)x).ToList();
                        return retVal;
                    case eDataHistoryAggregationType.count:
                    case eDataHistoryAggregationType.valuecount:
                        retVal = aggInt.Select(x => (object)x).ToList();
                        return retVal;
                    case eDataHistoryAggregationType.Unknown:
                        return null;
                }

                switch (Column.ValueType)
                {
                    case enumFasdValueType.INT:
                    case enumFasdValueType.BIGINT:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.sum:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggInt.Select(x => (object)x).ToList();
                                return retVal;
                        }
                        return null;
                    case enumFasdValueType.FLOAT:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.sum:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggDouble.Select(x => (object)x).ToList();
                                return retVal;
                        }
                        return null;
                    case enumFasdValueType.DATETIME:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggTime.Select(x => (object)x).ToList();
                                return retVal;
                        }
                        return null;
                    case enumFasdValueType.VERSION:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggVersion.Select(x => (object)x).ToList();
                                return retVal;
                            case eDataHistoryAggregationType.sum:
                                retVal = listString.Select(x => (object)cDataHistoryCollector.getStingList(x)).ToList();
                                return retVal;
                        }
                        return null;
                    case enumFasdValueType.GUID:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggGuid.Select(x => (object)x).ToList();
                                return retVal;
                        }
                        return null;
                    case enumFasdValueType.BOOLEAN:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggBoolean.Select(x => (object)x).ToList();
                                return retVal;
                        }
                        return null;
                    default:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                                retVal = aggString.Select(x => (object)x).ToList();
                                return retVal;
                            case eDataHistoryAggregationType.sum:
                                retVal = listString.Select(x => (object)cDataHistoryCollector.getStingList(x)).ToList();
                                return retVal;
                        }
                        return null;
                }

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

            return null;
        }
    }

    public class cDataHistoryCustomJsonMeasure
    {
        public int? ResultCode = null;
        public int? ErrorCode = null;
        public string ErrorDescription = null;
        public object Values;
    }

}
