using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Data.SqlTypes;
using System.DirectoryServices.ActiveDirectory;
using System.Dynamic;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

using C4IT.Configuration;
using C4IT.DataHistoryProvider;
using C4IT.FASD.Base;
using C4IT.FASD.Communication.Citrix;
using C4IT.HTTP;
using C4IT.Logging;
using C4IT.MultiLanguage;
using C4IT.XML;

using C4IT_DataHistoryProvider_Base.DataSources;

using Microsoft.Extensions.Logging.Abstractions;

using Newtonsoft.Json.Linq;

using static C4IT.DataHistoryProvider.cDataHistoryCollector;
using static C4IT.FASD.Base.cHealthCardTranslator;
using static C4IT.FASD.Communication.Citrix.cCitrixCommunicationMonitor;
using static C4IT.Logging.cLogManager;

namespace C4IT.DataHistoryProvider.Base.Modules.Citrix
{
    public class cDataHistoryCollectorCitrix : cDataHistoryCollectorModule, ISearchResultRelationProvider
    {
        public const string constConnectorName = "Citrix Virtual Apps and Desktops connector";
        public const string constLicenseId = "F2DA5E05-FD3C-4182-9164-9C722140BF85";

        // urls for static tables
        public const string constCitrixSession =                                "Sessions?$filter=SessionKey eq {0}";
        public const string constCitrixSessionMachine =                         "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=Machine($expand=CurrentLoadIndex,Catalog,DesktopGroup($select=Name)),Machine";
        public const string constCitrixSessionUser =                            "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=User";
        public const string constCitrixSessionConnections =                     "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=Connection";
        public const string constCitrixSessionCurrentConnection =               "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=CurrentConnection,LogOnMetrics";

        // urls for historic tables
        public const string constCitrixSessionMetrics =                         "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics";


        // urls for detail tables
        public const string constCitrixSessionMetricsIcaRTT =                   "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,IcaRttMs)";
        public const string constCitrixSessionMetricsIcaLatency =               "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,IcaLatency)";
        public const string constCitrixSessionMetricsClientL7Latency =          "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,ClientL7Latency)";
        public const string constCitrixSessionMetricsServerL7Latency =          "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,ServerL7Latency)";
        public const string constCitrixSessionMetricsInputBandwidthUsed =       "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,InputBandwidthUsed)";
        public const string constCitrixSessionMetricsOutputBandwidthUsed =      "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,OutputBandwidthUsed)";
        public const string constCitrixSessionMetricsOutputBandwidthAvailable = "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,OutputBandwidthAvailable)";
        public const string constCitrixSessionMetricsFps =                      "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,Fps)";
        public const string constCitrixSessionMetricsInputFps =                 "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,InputFps)";
        public const string constCitrixSessionMetricsOutputFps =                "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,OutputFps)";
        public const string constCitrixSessionMetricsWanLatency =               "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,WanLatency)";
        public const string constCitrixSessionMetricsDcLatency =                "Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,DcLatency)";
        public const string constCitrixSessionLogInSummaries =                  "Sessions?$filter=UserId in ({0})$select=Id,StartDate&$expand=User($select=UserName),LogOnSummaries($select=LogOnDuration,AuthenticationDuration,BrokingDuration,ProfileLoadDuration,GpoProcessingDuration,InteractiveSessionDuration)";
        public const string constCitrixUserConnections =                        "Sessions?$filter=UserId in ({0})&$expand=Connection";
        public const string constCitrixSessionLogInSummaries2 =                 "LogOnSummaries?$expand=Session($expand=User)&$filter=UserId eq {0}";

        public const string constCitrixSessionLogoff = "manage/Sessions/{0}/$logoff";
        public const string constCitrixSessionHide = "manage/Sessions/{0}/$hide";
        public const string constCitrixSessionSendMessage = "manage/Sessions/{0}/$sendMessage";

        // static tables
        public const string constTableNameSessionDetails = "citrix-session-details";

        public const string constTableNameSession = "citrix-session";
        public const string constTableNameSessionMachine = "citrix-session-machine";
        public const string constTableNameSessionUser = "citrix-session-user";
        public const string constTableNameSessionConnection = "citrix-session-connection";
        public const string constTableNameSessionCurrentConnection = "citrix-session-currentConnection";

        // history tables
        public const string constTableNameSessionMetrics = "citrix-session-metrics";

        // detail tables
        public const string constTableNameSessionDetailsIcaRttMS = "citrix-session-details-icaRttMS";
        public const string constTableNameSessionDetailsIcaLatency = "citrix-session-details-icaLatency";
        public const string constTableNameSessionDetailsClientL7Latency = "citrix-session-details-clientL7Latency";
        public const string constTableNameSessionDetailsServerL7Latency = "citrix-session-details-serverL7Latency";
        public const string constTableNameSessionDetailsInputBandwidthUsed = "citrix-session-details-inputBandwidthUsed";
        public const string constTableNameSessionDetailsOutputBandwidthUsed = "citrix-session-details-outputBandwidthUsed";
        public const string constTableNameSessionDetailsOutputBandwidthAvailable = "citrix-session-details-outputBandwidthAvailable";
        public const string constTableNameSessionDetailsFps = "citrix-session-details-fps";
        public const string constTableNameSessionDetailsInputFps = "citrix-session-details-inputFps";
        public const string constTableNameSessionDetailsOutputFps = "citrix-session-details-outputFps";
        public const string constTableNameSessionDetailsWanLatency = "citrix-session-details-wanLatency";
        public const string constTableNameSessionDetailsDcLatency = "citrix-session-details-dcLatency";

        public const int constCitrixUserPaging = 100;

        public bool DoScanSequential = false;

        public readonly List<cLdapPropertyInfo> UserMainProperties = new List<cLdapPropertyInfo>() {
            new cLdapPropertyInfo() { Name="objectSid", LdapType= enumLdapPropertyType.ldapSid, DbName=null},
            new cLdapPropertyInfo() { Name="userPrincipalName", LdapType= enumLdapPropertyType.ldapString, DbName="upn_external"},
        };

        public static Dictionary<string, string> UserTestReplacement = new Dictionary<string, string>()
        {
            { "07ac970e-8bde-454e-b14d-9c7f58cf8a8d:demouser@f4sd.dev", "burak.orakci@consulting4it.de"}
        };

        [Obsolete]
        private Dictionary<string, cCitrixCommunicationSystem> tenantCitrixCache = new Dictionary<string, cCitrixCommunicationSystem>();

        private Dictionary<Guid, cCitrixCommunicationSystem> cachedConnections = new Dictionary<Guid, cCitrixCommunicationSystem>();

        private List<cDataHistoryCitrixSystem> unauthorizedSystems = new List<cDataHistoryCitrixSystem>();

        public cDataHistoryCollectorCitrix(cDataHistoryCollector Collector) : base(Collector, enumDataHistoryOrigin.Citrix, constConnectorName, constLicenseId)
        {
            if (Collector?.InfrastructureConfig?.Citrix?.Systems != null)
            {
                foreach (var _system in Collector.InfrastructureConfig.Citrix.Systems)
                    unauthorizedSystems.Add(_system);
            }
        }

        [Obsolete]
        public async Task<cCitrixCommunicationSystem> GetCitrixCommunicationForSystemAsync(string tenant, bool force = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (string.IsNullOrEmpty(tenant))
                    return null;

                if (Collector?.InfrastructureConfig?.Citrix?.Systems == null)
                    return null;

                cDataHistoryCitrixCloudTenant _CitrixTenant = null;
                var systems = Collector.InfrastructureConfig.Citrix.Systems;
                if (systems is null)
                    return null;

                foreach (var _system in systems)
                {
                    if (!(_system is cDataHistoryCitrixCloudTenant Tenant))
                        continue;

                    if (Tenant.TenantID == tenant)
                    {
                        _CitrixTenant = Tenant;
                        break;
                    }
                }
                if (_CitrixTenant != null)
                {
                    if (_CitrixTenant != null && tenantCitrixCache.TryGetValue(_CitrixTenant.TenantID, out var cachedGraph))
                        return cachedGraph;

                    var logon = await LogonCitrixTenantAsync(_CitrixTenant, force: force);
                    if (logon != null)
                        tenantCitrixCache[_CitrixTenant.TenantID] = logon;

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

            return null;
        }

        private async Task<Guid?> GetSiteIdForSystem(cCitrixCommunicationSystem System, cDataHistoryCitrixSystem config, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var _customerInfo = await System.CVAD.GetCustomerInfoAsync(token);
                if (token.IsCancellationRequested)
                    return null;

                if (_customerInfo?.Sites == null || _customerInfo.Sites.Count == 0)
                {
                    Collector.DoProcessUiMessage(3, $"Could not get customer info for {config.Name}: {System.CVAD.Request.LastErrorMessage}", LogLevels.Error);
                    return null;
                }

                if (_customerInfo.Sites.Count == 1)
                {
                    var _id = _customerInfo.Sites.First().Value.Id;
                    if (config.SiteID == Guid.Empty)
                        return _id;
                    else if (_id == config.SiteID)
                        return _id;
                    else
                    {
                        Collector.DoProcessUiMessage(3, $"The configured site '{config.SiteID}' id does not match the authenticated site id '{_id}'.");
                        return null;
                    }
                }
                else
                {
                    if (config.SiteID == Guid.Empty)
                    {
                        Collector.DoProcessUiMessage(3, $"There are more than one site defined (Count={_customerInfo.Sites.Count}). You have to specify the Site ID in your configuration for {config.Name}");
                        return null;
                    }

                    foreach (var site in _customerInfo.Sites.Values)
                    {
                        if (site.Id == config.SiteID)
                            return site.Id;
                    }

                    Collector.DoProcessUiMessage(3, $"The specified Site ID in your configuration could not be found in the site list for {config.Name}");
                    return null;
                }

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

            return null;

        }

        private async Task<cCitrixCommunicationSystem> LogonToCloudTenantAsync(cDataHistoryCitrixCloudTenant Tenant, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var _citrixComm = new cCitrixCommunicationCloudBase("https://" + Tenant.Domain);

                var _logonInfo = new cOAuthLogonInfo
                {
                    Tenant = Tenant.TenantID.ToString(),
                    ClientID = Tenant.Credential?.User,
                    ClientSecret = Tenant.Credential?.NwCredential.Password,
                };

                var IsAuthenticated = await _citrixComm.LogonAsync(_logonInfo);

                if (!IsAuthenticated)
                {
                    Collector.DoProcessUiMessage(3, $"Could not login to cloud tenant '{Tenant.Name}': {_citrixComm.LastErrorMessage}", LogLevels.Error);
                    return null;
                }

                var CitrixSystem = new cCitrixCommunicationSystem(_citrixComm, Tenant.Name);

                var siteID = await GetSiteIdForSystem(CitrixSystem, Tenant, token);
                if (token.IsCancellationRequested)
                    return null;

                if (siteID == null)
                    return null;

                CitrixSystem.CVAD.CitrixInfo.SiteId = siteID?.ToString("D");

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

            return null;
        }

        private async Task<cCitrixCommunicationSystem> LogonToOnPremSystemAsync(cDataHistoryCitrixOnPrem System, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var strUrlCvad = $"https://{System.Server}";
                var _creds = System.Credential.NwCredential;

                // logon to the CVAD API
                var _citrixComm = new cCitrixCommunicationOnPremCvad(strUrlCvad);
                var IsAuthenticated = await _citrixComm.LogonWithCredentialsAsync(_creds);

                if (!IsAuthenticated)
                {
                    Collector.DoProcessUiMessage(3, $"Could not login to OnPrem CVAD API at'{System.Name}': {_citrixComm.LastErrorMessage}", LogLevels.Error);
                    return null;
                }

                // logon to the monitor API
                var strUrlMonitor = $"https://{System.Server}";

                var _citrixMonitor = new cCitrixCommunicationOnPremMonitor(strUrlMonitor);
                var _monitorResult = await _citrixMonitor.LogonWithCredentialsAsync(_creds);
                if (token.IsCancellationRequested)
                    return null;

                if (!_monitorResult)
                {
                    _citrixMonitor = null;
                    strUrlMonitor = $"http://{System.Server}";
                    _citrixMonitor = new cCitrixCommunicationOnPremMonitor(strUrlMonitor);
                    _monitorResult = await _citrixMonitor.LogonWithCredentialsAsync(_creds);

                    if (token.IsCancellationRequested)
                        return null;

                    if (!_monitorResult)
                    {
                        Collector.DoProcessUiMessage(3, $"Could not login to OnPrem monitor API at'{System.Name}': {_citrixComm.LastErrorMessage}", LogLevels.Error);
                        return null;
                    }
                }

                var CitrixSystem = new cCitrixCommunicationSystem(_citrixComm, _citrixMonitor, System.Name);

                var siteID = await GetSiteIdForSystem(CitrixSystem, System, token);
                if (token.IsCancellationRequested)
                    return null;

                if (siteID == null)
                    return null;

                CitrixSystem.CVAD.CitrixInfo.SiteId = siteID?.ToString("D");
                return CitrixSystem;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }


            return null;
        }

        private async Task<cCitrixCommunicationSystem> LogonToSystemAsync(cDataHistoryCitrixSystem System, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (System is cDataHistoryCitrixCloudTenant Tenant)
                    return await LogonToCloudTenantAsync(Tenant, token);
                else if (System is cDataHistoryCitrixOnPrem OnPrem)
                {
                    return await LogonToOnPremSystemAsync(OnPrem, token);
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
            return null;
        }

        public async Task<bool> ValidateConnectionsAsync(CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                Collector.DoProcessUiMessage(0, $"Validating Citrix connections...");

                List<cDataHistoryCitrixSystem> _toRemove = new List<cDataHistoryCitrixSystem>();
                foreach (var _system in unauthorizedSystems)
                {
                    Collector.DoProcessUiMessage(1, $"Try logon to {_system.Name}...");
                    var _ok = false;
                    var CitrixConn = await LogonToSystemAsync(_system, token);
                    if (token.IsCancellationRequested)
                        return false;
                    if (CitrixConn != null)
                    {
                        if (!Guid.TryParse(CitrixConn.CVAD.CitrixInfo.SiteId, out var siteId))
                            continue;

                        _toRemove.Add(_system);
                        cachedConnections[siteId] = CitrixConn;
                        _ok = true;
                    }
                    if (_ok)
                        Collector.DoProcessUiMessage(1, $"Successfull logon to {_system.Name}.");
                    else
                        Collector.DoProcessUiMessage(1, $"Could not logon to {_system.Name}.", LogLevels.Error);
                }

                foreach (var _system in _toRemove)
                    unauthorizedSystems.Remove(_system);

                if (unauthorizedSystems.Count > 0)
                    return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                Collector.DoProcessUiMessage(0, $"Validating Citrix connections finished.");
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public async Task<cCitrixCommunicationSystem> LogonCitrixTenantAsync(cDataHistoryCitrixCloudTenant Tenant, bool force = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var _domain = Tenant.Domain;
                var CitrixConn = new cCitrixCommunicationCloudBase(_domain);
                if (Tenant == null)
                {
                    return null;
                }
                var LI = new cOAuthLogonInfo()
                {
                    Tenant = Tenant?.TenantID.ToString(),
                    ClientID = Tenant.Credential?.User,
                    ClientSecret = Tenant.Credential?.NwCredential.Password,
                };
                var _ok = await CitrixConn.LogonAsync(LI);
                if (_ok)
                {
                    return new cCitrixCommunicationSystem(CitrixConn, Tenant.Name);
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public override bool UseForeignUserIds()
        {
            return true;
        }

        private class ScanSessionEntryProcessor : ScanEntryProcessor
        {
            private cCitrixCommunicationMonitor _comm;

            private Guid userId;

            private Guid instanceId;

            private string instanceName;

            public readonly List<cF4sdApiSearchResultRelation> sessionResult = new List<cF4sdApiSearchResultRelation>();

            public ScanSessionEntryProcessor(cCitrixCommunicationMonitor _comm, Guid userId, Guid instanceId, string instandName)
            {
                this._comm = _comm;
                this.userId = userId;
                this.instanceId = instanceId;
                this.instanceName = instandName;
            }

            public async override Task<bool> ProcessScannedItemAsync(dynamic _node, CancellationToken token)
            {
                try
                {
                    var endDate = DateTime.MinValue;
                    if (!DateTime.TryParse(_node.EndDate?.ToString(), out endDate))
                        endDate = DateTime.UtcNow;

                    var startDate = DateTime.MinValue;
                    if (!DateTime.TryParse(_node.StartDate?.ToString(), out startDate))
                        startDate = DateTime.UtcNow;

                    string SessionKey = _node.SessionKey.ToString();
                    if (!Guid.TryParse(SessionKey, out var SessionId))
                        SessionId = Guid.Empty;

                    var _duration = (endDate - startDate).TotalSeconds;

                    Enum.TryParse(_node?.ConnectionState.ToString(), out enumCitrixSessionStatus connectionState);

                    string machineName = _node?.Machine?.Name?.ToString();
                    if (machineName != null)
                        machineName = Regex.Replace(machineName, @"^.*\\", "");

                    var Infos = new Dictionary<string, string>()
                    {
                        { "Status", connectionState.ToString() },
                        { "StatusId", _node?.ConnectionState?.ToString() },
                        { "Domain", instanceName }
                    };

                    var sessionInfo = new cF4sdApiSearchResultRelation()
                    {
                        Type = enumF4sdSearchResultClass.VirtualSession,
                        Name = machineName,
                        DisplayName = machineName,
                        LastUsed = endDate,
                        UsingLevel = _duration == 0 ? 1 : _duration,
                        id = SessionId,
                        Instance = instanceId,
                        Status = enumF4sdSearchResultStatus.Active,
                        Infos = Infos,
                        Identities = new cF4sdIdentityList()
                            {
                                new cF4sdIdentityEntry(){ Class = enumFasdInformationClass.User, Id = this.userId},
                                new cF4sdIdentityEntry(){ Class = enumFasdInformationClass.VirtualSession, Id = SessionId, Instance = instanceId}
                            }
                    };

                    sessionResult.Add(sessionInfo);
                }
                catch (Exception E)
                {
                    LogException(E);
                }
                return false;
            }

        }

        public async Task<List<cF4sdApiSearchResultRelation>> GetUserSessionsForSystemAsync(cForeignUserInfoEntry UserInfo, int age, CancellationToken Token, cF4sdWebRequestInfo requestInfo)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                if (!Guid.TryParse(UserInfo.InstanceID, out var _SiteId))
                    return null;

                if (!int.TryParse(UserInfo.ForeignID, out var _CitrixId))
                    return null;

                if (!cachedConnections.TryGetValue(_SiteId, out var _comm))
                      return null;

                var scanProcessor = new ScanSessionEntryProcessor(_comm.Monitor, UserInfo.MainID, _SiteId, _comm.Name);
                await _comm.Monitor.ScanSessionsAsync(scanProcessor, -age, Token);

                // get the percentage of the Using Level by determining the maximal duration and compute the ratio
                double _maxDuration = 0;
                foreach (var _entry in scanProcessor.sessionResult)
                    _maxDuration = Math.Max(_maxDuration, _entry.UsingLevel);

                if (_maxDuration > 0)
                    foreach (var _entry in scanProcessor.sessionResult)
                        _entry.UsingLevel = _entry.UsingLevel / _maxDuration;

                return scanProcessor.sessionResult;

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

        public async Task<List<cF4sdApiSearchResultRelation>> GetUserSessionsAsync(IEnumerable<cF4sdConnectorIds> UserIds, int age, CancellationToken Token, cF4sdWebRequestInfo requestInfo)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var _retVal = new List<cF4sdApiSearchResultRelation>();
                foreach (var _userEntry in UserIds)
                {
                    if (_userEntry.ForeignUserInfo != null && _userEntry.ForeignUserInfo.TryGetValue(enumDataHistoryOrigin.Citrix, out var _entries2))
                        foreach (var _userEntry2 in _entries2)
                        {
                            var _res = await GetUserSessionsForSystemAsync(_userEntry2, age, Token, requestInfo);
                            if (_res != null)
                                _retVal.AddRange(_res);
                        }
                }

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

            return null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionMetricsAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionMetrics, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetrics, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call 
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null == res.Count > 0)
                {
                    JObject sessionInfos = res[0].Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSessionMetrics,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        Origin = enumDataHistoryOrigin.Citrix,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

                    foreach (var Entry in res)
                    {



                    }

                    var dicVals = new List<Dictionary<string, object>>();
                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        foreach (var prop in metricsObj.OfType<JObject>())
                        {
                            try
                            {
                                var metricDict = new Dictionary<string, object>();
                                foreach (var p in prop.Properties())
                                {
                                    if (p.Value is JValue v)
                                        metricDict[p.Name] = v.Value ?? null;
                                    else
                                        metricDict[p.Name] = p.Value;
                                }
                                dicVals.Add(metricDict);
                            }
                            catch (Exception E)
                            {
                                LogEntry($"Error getting session property from citrix", LogLevels.Error);
                                LogException(E);
                            }
                        }
                    }

                    const string collectDateColumnName = "CollectedDate";

                    var groupedValues = dicVals.Where(v => v.TryGetValue(collectDateColumnName, out var collectDate) && collectDate is DateTime)
                        .ToLookup(v => ((DateTime)v[collectDateColumnName]).Date, v => v);

                    for (int i = 0; i < MaxAge; i++)
                    {
                        DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                        var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                        foreach (var colInfo in SessionTableDetails.Columns.Values)
                        {
                            try
                            {
                                if (valuesOfDay is null)
                                {
                                    AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(null, colInfo.ValueType));
                                    continue;
                                }

                                var valuesOfColumn = valuesOfDay
                                    .Select(v =>
                                        {
                                            if (v.TryGetValue(colInfo.SourceName, out var colValue))
                                                return colValue;
                                            else
                                                return null;
                                        })
                                    .Where(v => v != null);

                                if (!valuesOfColumn.Any())
                                {
                                    AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(null, colInfo.ValueType));
                                    continue;
                                }
                                if (colInfo is cDataHistoryConfigColumn configColumn && configColumn.AggregationType != eDataHistoryAggregationType.Unknown)
                                {
                                    var aggregatedValue = GetAggregationValue(valuesOfColumn, configColumn.AggregationType, colInfo.ValueType);
                                    AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(aggregatedValue, colInfo.ValueType));
                                }
                            }
                            catch (Exception E)
                            {
                                LogException(E);
                            }
                        }
                    }

                    return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
                }
                else
                {
                    LogEntry($"Could not get detailed sessionmetrics information from citrix with id {Sessionid}. Sessionmetrics content is empty.", 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);
            }

            return null;

            void AddColumnData(cF4SDHealthCardRawData.cHealthCardTable table, string columnName, object value)
            {
                if (table.Columns.TryGetValue(columnName, out var columnValues))
                    columnValues.Values.Add(value);
                else
                    table.Columns.Add(columnName, new cF4SDHealthCardRawData.cHealthCardTableColumn(table) { ColumnName = columnName, Values = new List<object>() { value } });
            }
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>> GetSessionMetricsDetailsIcaRttMSAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsIcaRttMS, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsIcaRTT, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsIcaRttMS,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);


                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsIcaLatencyAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsIcaLatency, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsIcaLatency, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsIcaLatency,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsClientL7LatencyAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsClientL7Latency, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsClientL7Latency, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsClientL7Latency,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);


                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsServerL7LatencyAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsServerL7Latency, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsServerL7Latency, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsServerL7Latency,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsInputBandwidthUsedAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsInputBandwidthUsed, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsInputBandwidthUsed, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsInputBandwidthUsed,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsOutputBandwidthUsedAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsOutputBandwidthUsed, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsOutputBandwidthUsed, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsOutputBandwidthUsed,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsOutputBandwidthAvailableAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsOutputBandwidthAvailable, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsOutputBandwidthAvailable, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsOutputBandwidthAvailable,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = RefTime.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsFpsAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsFps, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsFps, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsFps,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsInputFpsAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsInputFps, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsInputFps, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsInputFps,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        //var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                        //                    .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v =>
                                            {
                                                var utc = DateTime.Parse(v[collectDateColumnName].ToString(), null,
                                                    System.Globalization.DateTimeStyles.AssumeUniversal | System.Globalization.DateTimeStyles.AdjustToUniversal);

                                                return (utc - RefTime.TimeOfDay).Date.AddDays(1);
                                            });

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = RefTime.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsOutputFpsAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsOutputFps, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsOutputFps, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsOutputFps,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsWanLatencyAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsWanLatency, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsWanLatency, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsWanLatency,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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.cHealthCardDetailsTable>> GetSessionMetricsDetailsDcLatencyAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, DateTime RefTime, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionDetailsDcLatency, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMetricsDcLatency, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retValList = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();


                    var vals = sessionInfos.Children();
                    var metricsObj = sessionInfos["SessionMetrics"] as JArray;

                    if (metricsObj != null)
                    {
                        var retVal = new cF4SDHealthCardRawData.cHealthCardDetailsTable()
                        {
                            Name = constTableNameSessionDetailsDcLatency,
                            Columns = SessionTableDetails.Columns.Values.Select(v => v.SourceName).ToList(),
                            Values = new Dictionary<int, List<object[]>>()

                        };

                        const string collectDateColumnName = "CollectedDate";

                        var groupedValues = metricsObj.Where(v => v[collectDateColumnName] != null && DateTime.TryParse(v[collectDateColumnName]?.ToString(), out _))
                                            .GroupBy(v => DateTime.Parse(v[collectDateColumnName].ToString()).Date);

                        for (int i = 0; i < MaxAge; i++)
                        {
                            DateTime referenceDate = DateTime.Now.AddDays(-i).Date;

                            var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key == referenceDate);

                            if (valuesOfDay == null)
                            {
                                retVal.Values[i] = new List<object[]>();
                                continue;
                            }
                            int index = 0;
                            foreach (var session in metricsObj)
                            {
                                try
                                {
                                    var dicVals = session.Children<JProperty>().ToDictionary(prop => prop.Name, prop => prop.Value?.ToObject<object>());

                                    var row = retVal.Columns.Select(colName => dicVals.TryGetValue(colName, out var value) ? value : null).ToArray();

                                    if (!retVal.Values.ContainsKey(i))
                                    {
                                        retVal.Values[i] = new List<object[]>();
                                    }
                                    retVal.Values[i].Add(row);
                                }
                                catch (Exception e)
                                {
                                    LogEntry($"Error processing session entry '{session.Path}'", LogLevels.Error);
                                    LogException(e);
                                }
                                index++;
                            }

                        }
                        retVal.Columns = SessionTableDetails.Columns.Values.Select(v => v.Name).ToList();
                        return new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>(1) { retVal };
                    }
                    else
                    {
                        LogEntry($"Could not get detailed Session information from citrix with id {Sessionid}. Session content is empty.", LogLevels.Warning);
                    }
                }
                return null;
            }
            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>> GetSessionAsync(Guid Sessionid, string TenantId, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSession, out var SessionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSession, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSession,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        Origin = enumDataHistoryOrigin.Citrix,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

                    var dicVals = new Dictionary<string, object>();
                    var vals = sessionInfos.Children();

                    foreach (var token in vals)
                    {
                        try
                        {
                            if (token is JProperty prop)
                                if (prop.Value is JValue val)
                                    dicVals[prop.Name] = val.Value;
                        }
                        catch (Exception E)
                        {
                            LogEntry($"Error getting session detail property from citrix '{token.Path}'", LogLevels.Error);
                            LogException(E);
                        }

                    }

                    foreach (var colInfo in SessionTableDetails.Columns.Values)
                    {
                        try
                        {
                            if (!dicVals.TryGetValue(colInfo.SourceName, out var objVal))
                                objVal = null;

                            objVal = ConvertToF4sdType(objVal, colInfo.ValueType);

                            var _col = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = colInfo.Name, Values = new List<object>(1) { objVal } };
                            retVal.Columns[colInfo.Name] = _col;
                        }
                        catch { }
                    }
                    return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
                }
                else
                {
                    LogEntry($"Could not get detailed session information from citrix with id {Sessionid}. Session content is empty.", 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);
            }

            return null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionUserAsync(Guid Sessionid, string TenantId, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionUser, out var UserTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionUser, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSessionUser,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        Origin = enumDataHistoryOrigin.Citrix,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

                    var dicVals = new Dictionary<string, object>();
                    var vals = sessionInfos.Children();
                    var userObj = sessionInfos["User"] as JObject;

                    if (userObj != null)
                    {
                        foreach (var prop in userObj.Properties())
                        {
                            try
                            {
                                if (prop.Value is JValue val)
                                {
                                    dicVals[prop.Name] = val.Value;
                                }
                                else
                                {
                                    dicVals[prop.Name] = prop.Value.ToString(Newtonsoft.Json.Formatting.None);
                                }
                            }
                            catch (Exception E)
                            {
                                LogEntry($"Error getting user property from citrix'{prop.Name}'", LogLevels.Error);
                                LogException(E);
                            }
                        }
                    }

                    foreach (var colInfo in UserTableDetails.Columns.Values)
                    {
                        try
                        {
                            if (!dicVals.TryGetValue(colInfo.SourceName, out var objVal))
                                objVal = null;

                            objVal = ConvertToF4sdType(objVal, colInfo.ValueType);

                            var _col = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = colInfo.Name, Values = new List<object>(1) { objVal } };
                            retVal.Columns[colInfo.Name] = _col;
                        }
                        catch { }
                    }
                    return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
                }
                else
                {
                    LogEntry($"Could not get detailed user information from citrix with id {Sessionid}. Session content is empty.", 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);
            }

            return null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionMachineAsync(Guid Sessionid, string TenantId, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionMachine, out var MachineTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionMachine, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSessionMachine,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        Origin = enumDataHistoryOrigin.Citrix,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

                    var dicVals = new Dictionary<string, object>();
                    var vals = sessionInfos.Children();
                    var machineObj = sessionInfos["Machine"] as JObject;
                    if (machineObj != null)
                    {
                        foreach (var prop in machineObj.Properties())
                        {
                            try
                            {
                                if (prop.Value is JValue val)
                                {
                                    dicVals[prop.Name] = val.Value;
                                }
                                else
                                {
                                    dicVals[prop.Name] = prop.Value.ToString(Newtonsoft.Json.Formatting.None);
                                }
                            }
                            catch (Exception E)
                            {
                                LogEntry($"Error getting Machine property from citrix'{prop.Name}'", LogLevels.Error);
                                LogException(E);
                            }
                        }
                    }

                    foreach (var colInfo in MachineTableDetails.Columns.Values)
                    {
                        try
                        {
                            if (!dicVals.TryGetValue(colInfo.SourceName, out var objVal))
                                objVal = null;

                            if (colInfo is cDataHistoryConfigColumn configColumn && !string.IsNullOrEmpty(configColumn.SourceJsonField))
                            {
                                var _objVal = JObject.Parse(objVal.ToString());
                                var prop = _objVal.Property(configColumn.SourceJsonField);
                                objVal = prop?.Value.ToString();
                            }
                            objVal = ConvertToF4sdType(objVal, colInfo.ValueType);

                            var _col = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = colInfo.Name, Values = new List<object>(1) { objVal } };
                            retVal.Columns[colInfo.Name] = _col;
                        }
                        catch { }
                    }
                    return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
                }
                else
                {
                    LogEntry($"Could not get detailed machine information from citrix with id {Sessionid}. Session content is empty.", 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);
            }

            return null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionCurrentConnectionAsync(Guid Sessionid, string TenantId, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionCurrentConnection, out var ConnectionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionCurrentConnection, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSessionCurrentConnection,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        Origin = enumDataHistoryOrigin.Citrix,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

                    var dicVals = new Dictionary<string, object>();
                    var vals = sessionInfos.Children();
                    var connectionObj = sessionInfos["CurrentConnection"] as JObject;
                    var logOnMetricsObj = sessionInfos["LogOnMetrics"] as JArray;
                    if (connectionObj != null)
                    {
                        foreach (var prop in connectionObj.Properties())
                        {
                            try
                            {
                                if (prop.Value is JValue val)
                                {
                                    dicVals[prop.Name] = val.Value;
                                }
                                else
                                {
                                    dicVals[prop.Name] = prop.Value.ToString(Newtonsoft.Json.Formatting.None);
                                }
                            }
                            catch (Exception E)
                            {
                                LogEntry($"Error getting connection property from citrix '{prop.Name}'", LogLevels.Error);
                                LogException(E);
                            }
                        }
                    }

                    JObject newestMetric = null;

                    if (logOnMetricsObj != null && logOnMetricsObj.Count > 0)
                    {
                        int maxId = logOnMetricsObj.Max(x => (int)x["Id"]);
                        newestMetric = logOnMetricsObj.FirstOrDefault(x => (int)x["Id"] == maxId) as JObject;
                    }
                    if (newestMetric != null)
                    {
                        foreach (var prop in newestMetric.Properties())
                        {
                            try
                            {
                                if (prop.Value is JValue val)
                                {
                                    dicVals[prop.Name] = val.Value;
                                }
                                else
                                {
                                    dicVals[prop.Name] = prop.Value.ToString(Newtonsoft.Json.Formatting.None);
                                }
                            }
                            catch (Exception E)
                            {
                                LogEntry($"Error getting connection property from citrix '{prop.Name}'", LogLevels.Error);
                                LogException(E);
                            }
                        }
                    }

                    foreach (var colInfo in ConnectionTableDetails.Columns.Values)
                    {
                        try
                        {
                            if (!dicVals.TryGetValue(colInfo.SourceName, out var objVal))
                                objVal = null;

                            objVal = ConvertToF4sdType(objVal, colInfo.ValueType);

                            var _col = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = colInfo.Name, Values = new List<object>(1) { objVal } };
                            retVal.Columns[colInfo.Name] = _col;
                        }
                        catch { }
                    }
                    return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
                }
                else
                {
                    LogEntry($"Could not get detailed connection information from citrix with id {Sessionid}. Session content is empty.", 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);
            }

            return null;
        }

        private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionConnectionsAsync(Guid Sessionid, string TenantId, 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 (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionConnection, out var ConnectionTableDetails))
                    return null;

                var strUrl = string.Format(constCitrixSessionConnections, Sessionid);

                var citrixCommunication = await GetCitrixCommunicationForSystemAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                // ToDo: Implement a paged walkthrough of results in case there are more results than the API can return in one call
                var res = await citrixCommunication.Monitor.Request.RequestListAsync(strUrl, false, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res[0].Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSessionConnection,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        Origin = enumDataHistoryOrigin.Citrix,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

                    var dicVals = new Dictionary<string, object>();
                    var vals = sessionInfos.Children();
                    var connectionObj = sessionInfos["Connection"] as JObject;

                    if (connectionObj != null)
                    {
                        foreach (var prop in connectionObj.Properties())
                        {
                            try
                            {
                                if (prop.Value is JValue val)
                                {
                                    dicVals[prop.Name] = val.Value;
                                }
                                else
                                {
                                    dicVals[prop.Name] = prop.Value.ToString(Newtonsoft.Json.Formatting.None);
                                }
                            }
                            catch (Exception E)
                            {
                                LogEntry($"Error getting connection property from citrix '{prop.Name}'", LogLevels.Error);
                                LogException(E);
                            }
                        }
                    }

                    foreach (var colInfo in ConnectionTableDetails.Columns.Values)
                    {
                        try
                        {
                            if (!dicVals.TryGetValue(colInfo.SourceName, out var objVal))
                                objVal = null;

                            objVal = ConvertToF4sdType(objVal, colInfo.ValueType);

                            var _col = new cF4SDHealthCardRawData.cHealthCardTableColumn(retVal) { ColumnName = colInfo.Name, Values = new List<object>(1) { objVal } };
                            retVal.Columns[colInfo.Name] = _col;
                        }
                        catch { }
                    }
                    return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
                }
                else
                {
                    LogEntry($"Could not get detailed connection information from citrix with id {Sessionid}. Session content is empty.", 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);
            }

            return null;
        }

        public override async Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>> GetDetailsTableResultsVirtualAsync(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 bIcaRttMSDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsIcaRttMS && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bIcaLatencyDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsIcaLatency && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bClientL7LatencyDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsClientL7Latency && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bServerL7LatencyDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsServerL7Latency && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bInputBandwidthUsedDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsInputBandwidthUsed && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bOutputBandwidthUsedDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsOutputBandwidthUsed && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bOutputBandwidthAvailableDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsOutputBandwidthAvailable && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bFpsDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsFps && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bInputFpsDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsInputFps && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bOutputFpsDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsOutputFps && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bWanLatencyDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsWanLatency && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);
                var bDcLatencyDetailExists = Tables.Exists(v => v.Name == constTableNameSessionDetailsDcLatency && v.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix);

                if (!Identities.TryGetValue(enumFasdInformationClass.VirtualSession, out var session) || session?.Id is null)
                    return null;
                if (!Identities.TryGetValue(enumFasdInformationClass.User, out var userIds) || userIds?.citrixTenantId is null)
                    return null;

                var listTasks = new List<Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>>(2);

                if (bIcaRttMSDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsIcaRttMSAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bIcaLatencyDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsIcaLatencyAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bClientL7LatencyDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsClientL7LatencyAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bServerL7LatencyDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsServerL7LatencyAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bInputBandwidthUsedDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsInputBandwidthUsedAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bOutputBandwidthUsedDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsOutputBandwidthUsedAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bOutputBandwidthAvailableDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsOutputBandwidthAvailableAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bFpsDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsFpsAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bInputFpsDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsInputFpsAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bOutputFpsDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsOutputFpsAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bWanLatencyDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsWanLatencyAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));

                if (bDcLatencyDetailExists)
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsDcLatencyAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));


                var arrRes = await Task.WhenAll(listTasks.ToArray());
                var retVal = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();
                foreach (var Entry in arrRes)
                    if (Entry != null)
                        retVal.AddRange(Entry);


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

                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 override async Task<List<string>> GetQuickActionList()
        {
            var quickactions = new List<string>{
                "Session Logoff",
                "Session Hidden",
                "Send Message to Session"

            };

            return await Task.FromResult(quickactions);
        }
        
        public async Task<dynamic> QuickActionRun(cF4SDServerQuickActionParameters jsonRequest, cF4sdWebRequestInfo requestInfo, CancellationToken Token)
        {
            dynamic result = null;
            switch (jsonRequest.Action)
            {
                case "SessionLogoff":
                    result = await SessionLogoff(jsonRequest.Identities, requestInfo, CancellationToken.None);
                    break;
                case "SessionHidden":
                    result = await SessionHidden(jsonRequest.Identities, requestInfo, CancellationToken.None);
                    break;
                case "SendMessageToSession":
                    result = await SendMessageToSession(jsonRequest.Identities, requestInfo, CancellationToken.None);
                    break;
            }
            return result;
        }

        public async Task<dynamic> SessionLogoff(List<cF4sdIdentityEntry> Identities, cF4sdWebRequestInfo requestInfo, CancellationToken Token)
        {
            var Ids = await Collector.getConntectorIds(Identities, Token, requestInfo, 1);

            if (!Ids.TryGetValue(enumFasdInformationClass.VirtualSession, out var session) || session?.Id is null)
                return null;

            if (!Ids.TryGetValue(enumFasdInformationClass.User, out var userIds) || userIds?.citrixTenantId is null)
                return null;

            var strUrl = string.Format(constCitrixSessionLogoff, session?.Id);

            var citrixCommunication = await GetCitrixCommunicationForSystemAsync(userIds?.citrixTenantId, true);
            if (citrixCommunication == null)
            {
                return null;
            }

            var Result = await citrixCommunication.CVAD.Request.RequestAsync(strUrl, httpMethod: eHttpMethod.post, null);
            if (Result != null)
            {

                var ResultJson = Result.Result as JObject;
                if (ResultJson != null)
                {
                    Collector.DoProcessUiMessage(3, $"  properties of selected '{ResultJson}' with {ResultJson.Count} properties could be read.");
                }

                return ResultJson;

            }
            return null;

        }

        public async Task<dynamic> SessionHidden(List<cF4sdIdentityEntry> Identities, cF4sdWebRequestInfo requestInfo, CancellationToken Token)
        {
            var Ids = await Collector.getConntectorIds(Identities, Token, requestInfo, 1);

            if (!Ids.TryGetValue(enumFasdInformationClass.VirtualSession, out var session) || session?.Id is null)
                return null;

            if (!Ids.TryGetValue(enumFasdInformationClass.User, out var userIds) || userIds?.citrixTenantId is null)
                return null;

            var strUrl = string.Format(constCitrixSessionHide, session?.Id);

            var citrixCommunication = await GetCitrixCommunicationForSystemAsync(userIds?.citrixTenantId, true);
            if (citrixCommunication == null)
            {
                return null;
            }

            var Result = await citrixCommunication.CVAD.Request.RequestAsync(strUrl, httpMethod: eHttpMethod.post, null);
            if (Result != null)
            {

                var ResultJson = Result.Result as JObject;
                if (ResultJson != null)
                {
                    Collector.DoProcessUiMessage(3, $"  properties of selected '{ResultJson}' with {ResultJson.Count} properties could be read.");
                }

                return ResultJson;

            }
            return null;

        }

        public async Task<dynamic> SendMessageToSession(List<cF4sdIdentityEntry> Identities, cF4sdWebRequestInfo requestInfo, CancellationToken Token)
        {
            var Ids = await Collector.getConntectorIds(Identities, Token, requestInfo, 1);

            if (!Ids.TryGetValue(enumFasdInformationClass.VirtualSession, out var session) || session?.Id is null)
                return null;

            if (!Ids.TryGetValue(enumFasdInformationClass.User, out var userIds) || userIds?.citrixTenantId is null)
                return null;



            var strUrl = string.Format(constCitrixSessionSendMessage, session?.Id);

            var citrixCommunication = await GetCitrixCommunicationForSystemAsync(userIds?.citrixTenantId, true);
            if (citrixCommunication == null)
            {
                return null;
            }

            var message = new SessionMessage
            {
                Style = enumMessageSytle.Critical.ToString(),
                Title = cMultiLanguageSupport.GetItem("Quickaction.Title.LogOff"),
                Text = cMultiLanguageSupport.GetItem("Quickaction.Text.LogOff")
            };


            var Result = await citrixCommunication.CVAD.Request.RequestAsync(strUrl, httpMethod: eHttpMethod.post, message);
            if (Result != null)
            {

                var ResultJson = Result.Result as JObject;
                if (ResultJson != null)
                {
                    Collector.DoProcessUiMessage(3, $"  properties of selected '{ResultJson}' with {ResultJson.Count} properties could be read.");
                }

                return Result.Context;

            }
            return null;

        }

        private class RequestFieldList
        {
            internal Dictionary<string, string> fields = new Dictionary<string, string>();
            internal Dictionary<string, RequestFieldList> SubNodes = new Dictionary<string, RequestFieldList>();
        }

        private void addRequestField(RequestFieldList list, string fieldPath, string fieldName)
        {
            var arrItems = fieldPath.Split('.');
            if (arrItems.Length == 1)
                list.fields[arrItems[0]] = fieldName;
            else if (arrItems.Length > 1)
            {
                var _node = arrItems[0];
                var _newPath = fieldPath.Remove(0, _node.Length + 1);
                if (!list.SubNodes.TryGetValue(_node, out var _newList))
                {
                    _newList = new RequestFieldList();
                    list.SubNodes.Add(_node, _newList);
                }

                addRequestField(_newList, _newPath, fieldName);
            }
        }

        private RequestFieldList getRequestFieldList(cDataHistoryConfigTable table)
        {
            try
            {
                var _result = new RequestFieldList();

                foreach (var _colum in table.Columns.Values)
                    addRequestField(_result, _colum.SourceName, _colum.Name);

                return _result;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            return null;
        }

        private string getUrlSelectionSubFields(string name, RequestFieldList list)
        {
            var _select = string.Empty;
            foreach (var _entry in list.fields)
            {
                if (_select != string.Empty)
                    _select += ",";
                _select += _entry.Key;
            }

            var _expand = string.Empty;
            foreach (var _entry in list.SubNodes)
            {
                var _sub = getUrlSelectionSubFields(_entry.Key, _entry.Value);
                if (!string.IsNullOrEmpty(_sub))
                {
                    if (_expand != string.Empty)
                        _expand += ",";
                    _expand += _sub;
                }
            }

            var _retVal = string.Empty;
            if (_select != string.Empty)
                _retVal = name + "($select=" + _select + ")";
            
            if (_expand != string.Empty)
            {
                if (_retVal != string.Empty)
                    _retVal += ",";
                _retVal += name + "($expand=" + _expand + ")";
            }

            return _retVal;
        }

        private string getUrlSelection(RequestFieldList list)
        {
            try
            {
                var _select = string.Empty;
                foreach (var _entry in list.fields)
                {
                    if (_entry.Key == "SiteId" || _entry.Key == "SiteName")
                        continue;
                    if (_select != string.Empty)
                        _select += ",";
                    _select += _entry.Key;
                }

                var _expand = string.Empty;
                foreach (var _entry in list.SubNodes)
                {
                    var _sub = getUrlSelectionSubFields(_entry.Key, _entry.Value);
                    if (!string.IsNullOrEmpty(_sub))
                    {
                        if (_expand != string.Empty)
                            _expand += ",";
                        _expand += _sub;
                    }
                }

                var _result = string.Empty;
                if (_select != string.Empty)
                    _result = "&$select=" + _select;

                if (_expand != string.Empty)
                    _result += "&$expand=" + _expand;

                return _result;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            return null;
        }

        public override 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)
        {
            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 (!Identities.TryGetValue(enumFasdInformationClass.VirtualSession, out var session) || session?.Id is null)
                    return null;

                var _instance = session.Instance;
                if (_instance == null)
                    return null;

                var _detailTable = Tables.Where(_t => _t.Name == constTableNameSessionDetails).First();
                if (_detailTable == null)
                {
                    // ToDo: create an empty table
                }
                else
                {
                    var _fieldList = getRequestFieldList(_detailTable);
                    var _select = getUrlSelection(_fieldList);

                    var _url = string.Format(constCitrixSession, session.Id.ToString("D")) + _select;

                    await ValidateConnectionsAsync(Token);
                    if (Token.IsCancellationRequested) return null;

                    if (!cachedConnections.TryGetValue(_instance, out var _conn))
                        return null;

                    var _sessionData = await _conn.Monitor.Request.RequestAsync(_url, eHttpMethod.get, null, token: Token);
                    if (Token.IsCancellationRequested) return null;

                }


                return null;

                //var citrixTableNames = new HashSet<string>(Tables.Where(t => t?.ParentCluster?.Origin == enumDataHistoryOrigin.Citrix).Select(t => t.Name), StringComparer.Ordinal);
                //bool Has(string tableName) => tableName != null && citrixTableNames.Contains(tableName);

                //var _userExists = Has(constTableNameSessionUser);
                //var _sessionExists = Has(constTableNameSession);
                //var _machineExists = Has(constTableNameSessionMachine);
                //var _connectionExists = Has(constTableNameSessionConnection);
                //var _currentConnectionExists = Has(constTableNameSessionCurrentConnection);
                //var _sessionMetricsExists = Has(constTableNameSessionMetrics);

                //var retVal = new List<cF4SDHealthCardRawData.cHealthCardTable>();
                //var listTasks = new List<Task<List<cF4SDHealthCardRawData.cHealthCardTable>>>(2);

                //if (_sessionExists)
                //    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                //if (_sessionMetricsExists)
                //    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionMetricsAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, LogDeep + 1, Token); }));

                //if (_machineExists)
                //    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionMachineAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                //if (_connectionExists)
                //    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionConnectionsAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                //if (_currentConnectionExists)
                //    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionCurrentConnectionAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                //if (_userExists)
                //    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionUserAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));


                //var arrRes = await Task.WhenAll(listTasks.ToArray());
                //foreach (var Entry in arrRes)
                //    if (Entry != null)
                //        retVal.AddRange(Entry);


                //if (retVal.Count == 0)
                //    return null;

                //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 override Task<bool> DoScanAsync(bool Always, bool Rescan, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool EnhancedDebug = 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 (!Always)
                {
                    var scanInfo = await Collector.CitrixCollector.GetScanTimeInfoAsync(requestInfo, LogDeep + 1, Token);
                    if (scanInfo == null)
                    {
                        Collector.DoProcessUiMessage(0, "could not get valid scan times info for Citrix connector");
                        return false;
                    }
                    if (scanInfo.NextScan == null)
                    {
                        Collector.DoProcessUiMessage(0, "not valid next scan time info for Citrix connector");
                        return false;
                    }

                    var nextScan = (DateTime)scanInfo.NextScan;
                    if (!(scanInfo.LastScan is DateTime lastScan) || (lastScan >= nextScan))
                    {
                        Collector.DoProcessUiMessage(0, $"currently no scan needed for Citrix connector, lastScan={scanInfo.LastScan} (UTC), nextScan={scanInfo.NextScan} (UTC)");
                        return false;
                    }
                }

                Collector.DoProcessUiMessage(0, "");

                if (Collector.InfrastructureConfig.Citrix.Systems != null)
                    await DoCitrixScanAsync(requestInfo, LogDeep + 1, Token);

                await Collector.SetLastScanTime("CitrixScan", DateTime.UtcNow, requestInfo, LogDeep + 1, Token);

                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 class UserScanProcessor : cCitrixCommunicationMonitor.ScanEntryProcessor
        {
            private cDataHistoryCollector Collector;
            private cDbConnection dbConnection;
            private cDbQuery GetUserQuery;
            private cDbQuery UpdateUserQuer;
            private cF4sdWebRequestInfo requestInfo;
            private int LogDeep;
            private string SiteId;

            private CancellationToken token;

            public int UpdatedCount { get; private set; } = 0;
            public int UnknownCount { get; private set; } = 0;
            public int TotalCount { get; private set; } = 0;

            public UserScanProcessor(cDataHistoryCollector Collector, string SiteId, cDbConnection dbConnection, cDbQuery GetUserQuery, cDbQuery UpdateUserQuery, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken token)
            {
                this.Collector = Collector;
                this.dbConnection = dbConnection;
                this.GetUserQuery = GetUserQuery;
                this.UpdateUserQuer = UpdateUserQuery;
                this.requestInfo = requestInfo;
                this.LogDeep = LogDeep;
                this.token = token;
                this.SiteId = SiteId;
            }

            public async override Task<bool> ProcessScannedItemAsync(dynamic _node, CancellationToken token)
            {
                if (token.IsCancellationRequested)
                    return false; ;

                try
                {
                    TotalCount++;

                    int Id = _node?.Id;
                    string strSid = _node?.Sid;
                    string Upn = _node?.Upn;
                    Upn = Upn?.ToLowerInvariant();
                    string Domain = _node?.Domain;

                    // check if we have a substitution for the upn for testing purposes
                    if (UserTestReplacement != null)
                    {
                        var _search = SiteId + ":" + Upn;
                        if (UserTestReplacement.TryGetValue(_search, out var _newUpn))
                            Upn = _newUpn;
                    }

                    // translate the sid to binary format
                    byte[] arrSid = null;
                    if (!string.IsNullOrEmpty(strSid))
                    {
                        try
                        {
                            var sid = new SecurityIdentifier(strSid);
                            arrSid = new byte[sid.BinaryLength];
                            sid.GetBinaryForm(arrSid, 0);
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                    }

                    // identify the f4sd user id by SID or UPN
                    var SqlParams = new Dictionary<string, object>(2)
                                        {
                                            { "SID", arrSid },
                                            { "UPN", Upn },
                                        };

                    var _res = await DataHistorySqlHelper.GetScalarResultAsync<Guid?>(dbConnection, GetUserQuery, SqlParams, requestInfo, token);
                    if (token.IsCancellationRequested)
                        return false;

                    if (_res != null && _res != Guid.Empty)
                    {
                        // update the foreign ids table
                        var _mainId = (Guid)_res;
                        SqlParams = new Dictionary<string, object>(5)
                        {
                            { "id", _mainId },
                            { "origin", (int)enumDataHistoryOrigin.Citrix },
                            { "instance", SiteId },
                            { "id_foreign", Id.ToString() },
                        };

                        var _insRes = await DataHistorySqlHelper.ExecuteAsync(dbConnection, UpdateUserQuer, SqlParams, requestInfo, token);
                        if (_insRes > 0)
                            UpdatedCount++;
                        else
                            UnknownCount++;
                    }
                    else
                    {
                        // we could not identify a known f4sd user
                        UnknownCount++;
                    }

                    return true;
                }
                catch (Exception E)
                {
                    UnknownCount++;
                    LogException(E);
                }
                finally
                {
                    if ((TotalCount ^ 100) == 0)
                        Collector.DoProcessUiMessage(LogDeep, $"{TotalCount} users scanned, {UpdatedCount} inserted or updated. ");
                }

                return false;
            }

        }

        public async Task<bool> DoCitrixScanSystemAsync(cCitrixCommunicationSystem CitrixSystem, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

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

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateForeignUsernfo", out var InsertUpdateUser))
                    return false;

                try
                {
                    Collector.DoProcessUiMessage(LogDeep, $"Start scanning Citrix system: {CitrixSystem.Name}");

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

                        var _processor = new UserScanProcessor(Collector, CitrixSystem.CVAD.CitrixInfo.SiteId, Conn, QueryGetUser, InsertUpdateUser, requestInfo, LogDeep + 1, token);
                        var _n = await CitrixSystem.Monitor.ScanUsersAsync(_processor, token);
                        if (_n >= 0)
                        {
                            if ((_processor.TotalCount ^ 100) != 0)
                                Collector.DoProcessUiMessage(LogDeep +1, $"{_processor.TotalCount} users scanned, {_processor.UpdatedCount} inserted or updated. ");
                            return true;
                        }
                    }
                }
                catch (Exception E)
                {
                    LogException(E);
                }
                finally
                {
                    Collector.DoProcessUiMessage(LogDeep, $"Scanning Citrix system finished: {CitrixSystem.Name}");
                }

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

            return false;
        }

        public async Task<bool> DoCitrixScanAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var _doSequential = this.DoScanSequential;

                await ValidateConnectionsAsync(token);
                if (token.IsCancellationRequested)
                    return false;

                var scanErrors = false;
                var lstTasks = new List<Task<bool>>();
                foreach (var _comm in cachedConnections.Values)
                {
                    if (_doSequential)
                    {
                        var _res = await DoCitrixScanSystemAsync(_comm, requestInfo, LogDeep + 1, token);
                        if (!_res)
                            scanErrors = true;
                    }
                    else
                        lstTasks.Add(Task.Run(async () => { return await DoCitrixScanSystemAsync(_comm, requestInfo, LogDeep + 1, token); }));
                }

                if (!_doSequential)
                {
                    var _res =  await Task.WhenAll<bool>(lstTasks);
                    foreach (var _resEntry in _res)
                        if (!_resEntry)
                            scanErrors = true;
                }

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

            return false;
        }

        public override async Task<cScanTimeInfo> GetScanTimeInfoAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            return await GetScanTimeInfoAsync(Collector.InfrastructureConfig.Citrix.ScanTiming, "CitrixScan", requestInfo, LogDeep, Token);
        }

        public HashSet<enumFasdInformationClass> GetSupportedInformationClasses() => new HashSet<enumFasdInformationClass>() { enumFasdInformationClass.VirtualSession };

        public async Task<cF4sdStagedSearchResultRelations> GetRelationsAsync(IEnumerable<cF4sdConnectorIds> ids, enumFasdInformationClass informationClass, int age, CancellationToken token = default)
        {
            try
            {
                List<cF4sdApiSearchResultRelation> relations = await GetUserSessionsAsync(ids, age, token, null);
                return new cF4sdStagedSearchResultRelations() { Relations = relations };
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            return null;
        }

        private object GetAggregationValue(IEnumerable<object> objVal, eDataHistoryAggregationType AggregationType, enumFasdValueType ValueType)
        {
            double aggregatedValue;
            switch (ValueType)
            {
                case enumFasdValueType.INT:
                case enumFasdValueType.BIGINT:
                case enumFasdValueType.FLOAT:
                    switch (AggregationType)
                    {
                        case eDataHistoryAggregationType.average:
                            return aggregatedValue = objVal.Where(v => v != null)
                                            .Select(v => Convert.ToDouble(v))
                                            .DefaultIfEmpty(0)
                                            .Average();
                        case eDataHistoryAggregationType.sum:
                            return aggregatedValue = objVal.Where(v => v != null)
                                             .Select(v => Convert.ToDouble(v))
                                             .DefaultIfEmpty(0)
                                             .Sum();
                        case eDataHistoryAggregationType.max:
                            return aggregatedValue = objVal.Where(v => v != null)
                                             .Select(v => v == DBNull.Value ? 0.0 : Convert.ToDouble(v))
                                             .DefaultIfEmpty(0)
                                             .Max();
                        case eDataHistoryAggregationType.min:
                            return aggregatedValue = objVal.Where(v => v != null)
                                             .Select(v => v == DBNull.Value ? 0.0 : Convert.ToDouble(v))
                                             .DefaultIfEmpty(0)
                                             .Min();
                        case eDataHistoryAggregationType.Unknown:
                            return null;
                    }
                    break;
            }
            return null;
        }

        private object ConvertToF4sdType(object objVal, enumFasdValueType ValueType)
        {
            try
            {
                if (objVal != null)
                {
                    switch (ValueType)
                    {
                        case enumFasdValueType.INT:
                            return cF4SDHealthCardRawData.GetInteger(objVal);
                        case enumFasdValueType.GUID:
                            return cXmlParser.GetGuidFromString(objVal.ToString());
                        case enumFasdValueType.BOOLEAN:

                            if (objVal == null) return false;
                            if (objVal is bool) return (bool)objVal;
                            if (objVal is int) return (int)objVal != 0;
                            if (objVal is string) return bool.TryParse((string)objVal, out bool result) ? result : ((string)objVal == "1");
                            return false; // Standardrückgabewert, falls keine Bedingung zutrifft
                        case enumFasdValueType.DATETIME:
                            DateTime? dt = null;
                            if (objVal is DateTime _dt)
                                dt = _dt;
                            else
                            {
                                if (DateTime.TryParse(objVal.ToString(), out _dt))
                                    dt = _dt;
                            }
                            if (dt is DateTime _dt2)
                            {
                                if (_dt2 == new DateTime(1, 1, 1, 0, 0, 0))
                                    dt = null;
                            }
                            return dt;
                        default:
                            return objVal.ToString();
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }

            return null;
        }

        public class SessionMessage
        {
            public string Style { get; set; } = null;
            public string Title { get; set; } = null;
            public string Text { get; set; } = null;
        }
    }
}
