using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlTypes;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using C4IT.FASD.Base;
using C4IT.FASD.Communication.Citrix;
using C4IT.HTTP;
using C4IT.Logging;
using C4IT_DataHistoryProvider_Base.DataSources;
using Newtonsoft.Json.Linq;
using static C4IT.Logging.cLogManager;
using static C4IT_DataHistoryProvider.cDataHistoryConfigCitrix;

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

        public const string constCitrixUserList = "monitorodata/Users?$top={0}&$select=Id,Sid,Upn,Domain&$filter=Domain ne ''";
        public const string constCitrixSessionList = "monitorodata/Sessions?$expand=Machine($select=Id,DnsName,Name)&$select=SessionKey,StartDate,EndDate,ConnectionState";
        public const string constCitrixSessions = "monitorodata/Sessions?$filter=UserId in ({0})&$expand=Machine($select=Id,DnsName,Name)&$select=SessionKey,StartDate,EndDate,ConnectionState";
        public const string constCitrixSession = "monitorodata/Sessions?$filter=SessionKey eq {0}";
        public const string constCitrixSessionMetrics = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics";
        public const string constCitrixSessionMetricsIcaRTT = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,IcaRttMs)";
        public const string constCitrixSessionMetricsIcaLatency = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,IcaLatency)";
        public const string constCitrixSessionMetricsClientL7Latency = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,ClientL7Latency)";
        public const string constCitrixSessionMetricsServerL7Latency = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,ServerL7Latency)";
        public const string constCitrixSessionMetricsInputBandwidthUsed = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,InputBandwidthUsed)";
        public const string constCitrixSessionMetricsOutputBandwidthUsed = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,OutputBandwidthUsed)";
        public const string constCitrixSessionMetricsOutputBandwidthAvailable = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,OutputBandwidthAvailable)";
        public const string constCitrixSessionMetricsFps = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,Fps)";
        public const string constCitrixSessionMetricsInputFps = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,InputFps)";
        public const string constCitrixSessionMetricsOutputFps = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,OutputFps)";
        public const string constCitrixSessionMetricsWanLatency = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,WanLatency)"; 
        public const string constCitrixSessionMetricsDcLatency = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=SessionMetrics($select=Id,CollectedDate,DcLatency)";
        public const string constCitrixSessionMachine = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=Machine($expand=CurrentLoadIndex,Catalog,DesktopGroup($select=Name)),Machine";
        public const string constCitrixSessionCurrentConnection = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=CurrentConnection,LogOnMetrics";
        public const string constCitrixSessionConnections = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=Connection";
        public const string constCitrixSessionLogInSummaries = "monitorodata/Sessions?$filter=UserId in ({0})$select=Id,StartDate&$expand=User($select=UserName),LogOnSummaries($select=LogOnDuration,AuthenticationDuration,BrokingDuration,ProfileLoadDuration,GpoProcessingDuration,InteractiveSessionDuration)";
        public const string constCitrixSessionLogInSummaries2 = "monitorodata/LogOnSummaries?$expand=Session($expand=User)&$filter=UserId eq {0}";
        public const string constCitrixUserConnections = "monitorodata/Sessions?$filter=UserId in ({0})&$expand=Connection";
        public const string constCitrixSessionUser = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=User";
        public const string constCitrixSessionLogoff = "cvad/manage/Sessions/{0}/$logoff";
        public const string constCitrixSessionHide = "cvad/manage/Sessions/{0}/$hide";
        public const string constCitrixSessionSendMessage = "cvad/manage/Sessions/{0}/$sendMessage";
        public const string constTableNameSession = "citrix-session";
        public const string constTableNameSessionMachine = "citrix-session-machine";
        public const string constTableNameSessionConnection = "citrix-session-connection";
        public const string constTableNameSessionCurrentConnection = "citrix-session-currentConnection";
        public const string constTableNameSessionMetrics = "citrix-session-metrics";
        public const string constTableNameSessionStateMetrics = "citrix-connectionState-metrics";
        public const string constTableNameSessionUser = "citrix-session-user";
        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 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"},
        };
        private Dictionary<string, cCitrixCommunication> tenantCitrixCache = new Dictionary<string, cCitrixCommunication>();
        private readonly cDataHistoryCollector _collector;
        public cDataHistoryCollectorCitrix(cDataHistoryCollector Collector) : base(Collector, enumDataHistoryOrigin.Citrix, constConnectorName, constLicenseId)
        {
            _collector = Collector;
        }
        public async Task<cCitrixCommunication> GetCitrixCommunicationForTenantAsync(string tenant, bool force = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (tenant == null)
                    return null;

                cDataHistoryCitrixTenant _CitrixTenant = null;
                var tenants = Collector.InfrastructureConfig.CitrixTenants?.Values;
                if (tenants is null)
                {
                    return null;
                }
                foreach (var Tenant in tenants)
                {
                    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;
        }
        public async Task<cCitrixCommunication> LogonCitrixTenantAsync(cDataHistoryCitrixTenant Tenant, bool force = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var CitrixConn = new cCitrixCommunication();
                if (Tenant == null)
                {
                    return null;
                }
                var LI = new cOAuthLogonInfo()
                {
                    Tenant = Tenant?.TenantID.ToString(),
                    ClientID = Tenant.Credential?.User,
                    ClientSecret = Tenant.Credential?.NwCredential.Password,
                    InstanceId = Tenant?.InstanceID.ToString()
                };
                var RetVal = await CitrixConn.LogonAsync(LI);
                if (RetVal)
                    return CitrixConn;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public async Task<cCitrixCommunication> GetCitrixUsersAsync()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var strUrl = string.Format(constCitrixUserList, constCitrixUserPaging);

                var tenants = Collector.InfrastructureConfig.CitrixTenants?.Values;
                if (tenants is null)
                {
                    Collector.DoProcessUiMessage(2, $" Could not logon to tenant - Tenant is null", LogLevels.Warning);
                    return null;
                }
                foreach (var Tenant in tenants)
                {

                    var CitrixConn = await GetCitrixCommunicationForTenantAsync(Tenant.TenantID, true);
                    if (CitrixConn == null)
                    {
                        Collector.DoProcessUiMessage(2, $"error: could not logon to tenat {Tenant.Domain}", LogLevels.Error);
                        continue;
                    }

                    var Result = await CitrixConn.RequestAsync(strUrl, retryForbidden: false);
                    if (Result != null)
                    {


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

                        }


                    }

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

            return null;
        }

        public async Task<List<cF4sdApiSearchResultRelation>> GetUserSessionsAsync(List<cF4sdConnectorIds> UserId, CancellationToken Token, cF4sdWebRequestInfo requestInfo)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                string userIds = string.Join(",", UserId.Where(u => !string.IsNullOrWhiteSpace(u?.citrixUserId.ToString())).Select(u => u.citrixUserId));


                var strUrl = string.Format(constCitrixSessions, userIds);

                var tenantId = UserId?.FirstOrDefault(u => !string.IsNullOrWhiteSpace(u?.citrixTenantId))?.citrixTenantId;
                if (tenantId is null)
                {
                    Collector.DoProcessUiMessage(2, $" Could not logon to tenant - Tenant is null", LogLevels.Warning);
                    return null;
                }

                var CitrixConn = await GetCitrixCommunicationForTenantAsync(tenantId, true);

                if (CitrixConn == null)
                {
                    Collector.DoProcessUiMessage(2, $"error: could not logon to tenat {tenantId}", LogLevels.Error);
                    return null;
                }

                // maybe we should think about a concrete implementation of the results instead of a dynamic, what do you think?
                var Result = await CitrixConn.RequestListAsync(strUrl, retryForbidden: false);
                
                if (Result != null == Result.Count > 0)
                {
                    var sessionResult = new List<cF4sdApiSearchResultRelation>(Result.Count);
                    double _sumDuration = 0;
                    foreach (var Entry in Result)
                    {
                        var endDate = DateTime.MinValue;
                        if (!DateTime.TryParse(Entry.Result.EndDate?.ToString(), out endDate))
                            endDate = DateTime.UtcNow;

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

                        TimeSpan _duration = endDate - startDate;
                        _sumDuration += _duration.TotalSeconds;

                        Enum.TryParse(Entry.Result.ConnectionState.ToString(), out enumCitrixSessionStatus connectionState);

                        var Infos = new Dictionary<string, string>()
                            {

                                { "Status",connectionState.ToString() },
                                { "StatusId", Entry.Result.ConnectionState.ToString() },
                                { "Domain", Regex.Replace(Entry.Result.Machine.Name?.ToString(), @"\\.*$", "") }
                            };
                        var sessionInfo = new cF4sdApiSearchResultRelation()
                        {
                            Type = enumF4sdSearchResultClass.VirtualSession,
                            Name = Regex.Replace(Entry.Result.Machine.Name.ToString(), @"^.*\\", ""),
                            DisplayName = Regex.Replace(Entry.Result.Machine.Name.ToString(), @"^.*\\", ""),
                            LastUsed = endDate,
                            // minor thing due to the fact we did it like that in the Agent before, but:
                            // Instead of setting the UsingLevel to a temporary value and calculate the actual value later, 
                            // we should maybe calculate the sumvalue beforehand and apply only the calculation here, what do you think?
                            UsingLevel = _duration.TotalSeconds == 0 ? 1 : _duration.TotalSeconds,
                            id = Entry.Result.SessionKey,
                            Status = enumF4sdSearchResultStatus.Active,
                            Infos = Infos,
                            Identities = new cF4sdIdentityList()
                            {
                                new cF4sdIdentityEntry(){ Class = enumFasdInformationClass.User, Id = UserId[0].Id},
                                new cF4sdIdentityEntry(){ Class = enumFasdInformationClass.VirtualSession, Id = Entry.Result.SessionKey}
                            }
                        };
                        sessionResult.Add(sessionInfo);
                    }
                    if (sessionResult != null && sessionResult.Count > 0)
                    {
                        foreach (var Entry in sessionResult)
                        {
                            Entry.UsingLevel /= _sumDuration;
                        }
                    }
                    return sessionResult;
                }

            }
            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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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.cHealthCardTable>> GetSessionStateMetricsAsync(cF4sdConnectorIds UserIds, 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
            {

                var strUrl = string.Format(constCitrixSessions, UserIds.citrixUserId.ToString());

                if (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionStateMetrics, out var SessionTableDetails))
                    return null;

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

                var res = await citrixCommunication.RequestAsync(strUrl, retryForbidden: false);
                if (res != null)
                {
                    JObject sessionInfos = res.Result;
                    var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
                    {
                        Name = constTableNameSessionStateMetrics,
                        InformationClass = enumFasdInformationClass.VirtualSession,
                        IsStatic = true,
                        TableType = eDataHistoryTableType.Static
                    };

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

                    if (metricsObj != null)
                    {
                        dicVals = metricsObj
                            .OfType<JObject>()
                            .Where(o =>
                                o["StartDate"] != null &&
                                o["ConnectionState"] != null &&
                                DateTime.TryParse(o["StartDate"].ToString(), out _)
                            )
                            .GroupBy(o => new
                            {
                                Date = DateTime.Parse(o["StartDate"].ToString()).Date,
                                ConnectionState = (int)o["ConnectionState"]
                            })
                            .OrderBy(g => g.Key.Date)
                            .Select(g => new Dictionary<string, object>
                            {
        { "StartDate", g.Key.Date },          // ✅ DateTime
        { "ConnectionState", g.Key.ConnectionState },
        { "ConnectionCount", g.Count() }
                            })
                            .ToList();




                        const string startDateColumnName = "StartDate";
                        const string connectionStatusColumnName = "ConnectionState";

                        var groupedValues = dicVals
        .Where(v =>
            v.TryGetValue(startDateColumnName, out var collectDate) &&
            collectDate is DateTime &&
            v.TryGetValue(connectionStatusColumnName, out _))
        .GroupBy(v => new
        {
            StartDate = ((DateTime)v[startDateColumnName]).Date,
            ConnectionStatus = v[connectionStatusColumnName]
        })
        .Select(g => new Dictionary<string, object>
        {
        { startDateColumnName, g.Key.StartDate },
        { connectionStatusColumnName, g.Key.ConnectionStatus },
        { "ConnectionCount", g.Count() }
        })
        .ToLookup(d => new
        {
            StartDate = (DateTime)d[startDateColumnName],
            ConnectionStatus = d[connectionStatusColumnName],
            ConnectionCount = d["ConnectionCount"]
        });




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

                            //var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key.Date == referenceDate);
                            var valuesOfDay = groupedValues.Where(g => g.Key.StartDate == referenceDate).SelectMany(g => g).ToList();

                            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));
                                    }
                                    else
                                    {
                                        var value = valuesOfColumn.FirstOrDefault();
                                        AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(value, 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 {UserIds}. 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(TenantId, true);
                if (citrixCommunication == null)
                    return null;

                var res = await citrixCommunication.RequestListAsync(strUrl, 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 GetCitrixCommunicationForTenantAsync(userIds?.citrixTenantId, true);
            if (citrixCommunication == null)
            {
                return null;
            }

            var Result = await citrixCommunication.RequestAsync(strUrl, httpMethod: eHttpMethod.post, retryForbidden: false);
            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 GetCitrixCommunicationForTenantAsync(userIds?.citrixTenantId, true);
            if (citrixCommunication == null)
            {
                return null;
            }

            var Result = await citrixCommunication.RequestAsync(strUrl, httpMethod: eHttpMethod.post, retryForbidden: false);
            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 GetCitrixCommunicationForTenantAsync(userIds?.citrixTenantId, true);
            if (citrixCommunication == null)
            {
                return null;
            }

            var message = new SessionMessage
            {
                Style = "Critical",
                Title = "Abmeldung bevorstehend!",
                Text = "Eine dringende Wartung muss durchgeführt werden. Sie werden in Kürze abgemeldet. Bitte speichern Sie Ihre Daten."
            };


            var Result = await citrixCommunication.RequestAsync(strUrl, httpMethod: eHttpMethod.post, message, retryForbidden: false);
            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 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;

                if (!Identities.TryGetValue(enumFasdInformationClass.User, out var userIds) || userIds?.citrixTenantId is 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)
                    //if (CacheId == null && !instantly)
                    //{
                    //    retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                    //    {
                    //        Name = constTableNameSessionMetrics,
                    //        InformationClass = enumFasdInformationClass.VirtualSession,
                    //        IsIncomplete = true
                    //    });

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

                if (_sessionMetricsExists) { 
                    //if (CacheId == null && !instantly)
                    //{
                    //    retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                    //    {
                    //        Name = constTableNameSessionMetrics,
                    //        InformationClass = enumFasdInformationClass.VirtualSession,
                    //        IsIncomplete = true
                    //    });

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


                if (_machineExists)
                    //if (CacheId == null && !instantly)
                    //{
                    //    retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                    //    {
                    //        Name = constTableNameMachineMetrics,
                    //        InformationClass = enumFasdInformationClass.VirtualSession,
                    //        IsIncomplete = true
                    //    });
                    //}
                    //else
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionMachineAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                if (_connectionExists)
                    //if (CacheId == null && !instantly)
                    //{
                    //    retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                    //    {
                    //        Name = constTableNameMachineMetrics,
                    //        InformationClass = enumFasdInformationClass.VirtualSession,
                    //        IsIncomplete = true
                    //    });
                    //}
                    //else
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionConnectionsAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                if (_currentConnectionExists)
                    //if (CacheId == null && !instantly)
                    //{
                    //    retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                    //    {
                    //        Name = constTableNameMachineMetrics,
                    //        InformationClass = enumFasdInformationClass.VirtualSession,
                    //        IsIncomplete = true
                    //    });
                    //}
                    //else
                    listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionCurrentConnectionAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));

                if (_userExists)
                    //if (CacheId == null && !instantly)
                    //{
                    //    retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                    //    {
                    //        Name = constTableNameMachineMetrics,
                    //        InformationClass = enumFasdInformationClass.VirtualSession,
                    //        IsIncomplete = true
                    //    });
                    //}
                    //else
                    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 Task<cCitrixCommunication> GetCitrixSessionsAsync()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var strUrl = string.Format(constCitrixSessionList, constCitrixUserPaging);

                var tenants = Collector.InfrastructureConfig.CitrixTenants?.Values;
                if (tenants is null)
                {
                    Collector.DoProcessUiMessage(2, $" Could not logon to tenant - Tenant is null", LogLevels.Warning);
                    return null;
                }
                foreach (var Tenant in tenants)
                {

                    var CitrixConn = await GetCitrixCommunicationForTenantAsync(Tenant.TenantID, true);
                    if (CitrixConn == null)
                    {
                        Collector.DoProcessUiMessage(2, $"error: could not logon to tenat {Tenant.Domain}", LogLevels.Error);
                        continue;
                    }

                    var Result = await CitrixConn.RequestAsync(strUrl, retryForbidden: false);
                    if (Result != null)
                    {


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

                        }


                    }

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

            return null;
        }

        public override async 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.CitrixTenants != null)
                    await DoCitrixScanAsync(requestInfo, LogDeep + 1, Token);

                await Collector.SetLastScanTime("CitrixScan-all", 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;
        }

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

            try
            {
                var tenants = Collector.InfrastructureConfig.CitrixTenants?.Values;
                if (tenants is null)
                {
                    Collector.DoProcessUiMessage(2, $" could not logon to tenant - Tenant is null ", LogLevels.Error);
                    return true;
                }

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateUserCitrix", out var QueryInsert))
                    return false;

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


                    Collector.DoProcessUiMessage(0, string.Format("doing the Citrix cloud scans:"));
                    foreach (var Tenant in tenants)
                    {
                        try
                        {
                            Collector.DoProcessUiMessage(1, $"scanning tenat {Tenant.Domain} for users ...");
                            var CitrixConn = await GetCitrixCommunicationForTenantAsync(Tenant.TenantID, true);
                            if (CitrixConn == null)
                            {
                                Collector.DoProcessUiMessage(2, $"error: could not logon to tenat {Tenant.Domain}", LogLevels.Error);
                                continue;
                            }

                            var strUrl = string.Format(constCitrixUserList, constCitrixUserPaging);
                            var Result = await CitrixConn.RequestListAsync(strUrl, retryForbidden: false);

                            var Count = 0;
                            var Updated = 0;
                            while (Result != null == Result.Count > 0)
                            {
                                foreach (var Entry in Result)
                                {

                                    Count++;
                                    try
                                    {
                                        var _strId = Entry.Result.Id.ToString();


                                        var _sid = Entry.Result.Sid.ToString();
                                        var sid = new SecurityIdentifier(_sid);
                                        var arrSid = new byte[sid.BinaryLength];
                                        sid.GetBinaryForm(arrSid, 0);

                                        var SqlParams = new Dictionary<string, object>(2)
                                        {
                                            { "sid_bin", arrSid },
                                            { "id", _strId },
                                            { "tenant", Tenant.TenantID }
                                        };

                                        var _res = await DataHistorySqlHelper.ExecuteAsync(Conn, QueryInsert, SqlParams, requestInfo, Token);
                                        if (_res > 0)
                                            Updated++;
                                        else
                                        {
                                            if (DataHistorySqlHelper.GetWellKnownSqlStatement("UpdateUserCitrix2", out var QueryInsert2))
                                            {
                                                string upn = Entry.Result.Upn.ToString();
                                                var SqlParams2 = new Dictionary<string, object>(2)
                                                                     {
                                                                         { "id", _strId },
                                                                         { "tenant", Tenant.TenantID },
                                                                         { "upn_internal", upn }
                                                                     };
                                                var _res2 = await DataHistorySqlHelper.ExecuteAsync(Conn, QueryInsert2, SqlParams2, requestInfo, Token);
                                                if (_res2 > 0)
                                                    Updated++;
                                            }
                                        }
                                    }
                                    catch (Exception E)
                                    {
                                        LogException(E);
                                    }
                                }
                                Collector.DoProcessUiMessage(2, $"{Count} accounts processed, {Updated} updated.");

                                if (Result.Count < constCitrixUserPaging)
                                    break;

                                Result.Clear();
                                if (!await CitrixConn.RequestNextAsync(Result))
                                    break;
                            }
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }

                        Collector.DoProcessUiMessage(1, $"...scanning tenat {Tenant.Domain} finished");
                    }

                    Collector.DoProcessUiMessage(0, "... Citrix cloud scans finished.");

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

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

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

        private static void AddToLdapPropertyList(List<cLdapPropertyInfo> list, ref Dictionary<string, cLdapPropertyInfo> dicLdap)
        {
            foreach (var Entry in list)
            {
                if (!dicLdap.ContainsKey(Entry.Name))
                    dicLdap.Add(Entry.Name.ToLowerInvariant(), Entry);
            }
        }

        private Dictionary<string, cLdapPropertyInfo> GetPropertyList(enumFasdInformationClass InformationClass)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var dicLdap = new Dictionary<string, cLdapPropertyInfo>();

            try
            {
                switch (InformationClass)
                {
                    case enumFasdInformationClass.User:
                        AddToLdapPropertyList(UserMainProperties, ref dicLdap);
                        break;
                }

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

            return dicLdap;

        }

        public HashSet<enumFasdInformationClass> GetSupportedInformationClasses() => new HashSet<enumFasdInformationClass>() { enumFasdInformationClass.VirtualSession };
        
        public async Task<cF4sdStagedSearchResultRelations> GetRelationsAsync(IEnumerable<cF4sdIdentityEntry> ids, enumFasdInformationClass informationClass, int age, CancellationToken token = default)
        {
            try
            {
                List<cF4sdConnectorIds> sessionsIds = await _collector.getConnectorIdList(ids.ToList(), token, null, 0);
                List<cF4sdApiSearchResultRelation> relations = await GetUserSessionsAsync(sessionsIds, token, null);
                return new cF4sdStagedSearchResultRelations() { Relations = relations };
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            return null;
        }
        
        private enumCitrixSessionStatus ConvertSessionStatusCode(int sessionStatus)
        {
            try
            {
                switch (sessionStatus)
                {
                    case 0:
                        return enumCitrixSessionStatus.Unknown;
                    case 1:
                        return enumCitrixSessionStatus.Connected;
                    case 2:
                        return enumCitrixSessionStatus.Disconnected;
                    case 3:
                        return enumCitrixSessionStatus.Terminated;
                    case 4:
                        return enumCitrixSessionStatus.PreparingSession;
                    case 5:
                        return enumCitrixSessionStatus.Active;
                    case 6:
                        return enumCitrixSessionStatus.Reconnecting;
                    case 7:
                        return enumCitrixSessionStatus.NonBrokeredSession;
                    case 8:
                        return enumCitrixSessionStatus.Other;
                    case 9:
                        return enumCitrixSessionStatus.Pending;

                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            return enumCitrixSessionStatus.Unknown;
        }        

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

    public class cF4SDVirtualSession : cF4sdApiSearchResultRelation
    {
        public Guid SessionId { get; set; }
        public string ConnectionState { get; set; }
        public string FailureState { get; set; }
        public string Startdate { get; set; }
        public string Enddate { get; set; }
        public string MachineId { get; set; }
        public string MachineName { get; set; }
        public Guid UserId { get; set; }
    }

    
}
