using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using C4IT.HTTP;
using C4IT.Logging;

using Newtonsoft.Json.Linq;

using static C4IT.Logging.cLogManager;

namespace C4IT.FASD.Communication.Citrix
{
    public class cCitrixApiResultList : List<cCitrixApiResultBase>
    {
        public string NextResultUrl { get; private set; } = null;
        public bool retryForbidden { get; private set; } = false;

        public cCitrixApiResultList(bool retryForbidden)
        {
            this.retryForbidden = retryForbidden;
        }

        public bool hasRemaining
        {
            get
            {
                return !string.IsNullOrEmpty(NextResultUrl);
            }
        }

        public void AddResult(dynamic Result)
        {
            try
            {
                NextResultUrl = null;

                if (Result.TryGetValue("@odata.nextLink", out JToken JT))
                {
                    var JO = (JValue)JT;

                    NextResultUrl = Result["@odata.nextLink"];
                }

                if (Result.TryGetValue("value", out JToken JT2))
                {
                    foreach (dynamic Entry in Result.value)
                        try
                        {
                            var val = new cCitrixApiResultBase(Entry);
                            this.Add(val);
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }
                }
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
            }
        }

    }

    public class cCitrixApiResultBase
    {
        public string ID { get; private set; } = null;
        public string ODataId { get; private set; } = null;
        public string DisplayName { get; private set; } = null;

        public string Context { get; private set; } = null;

        public dynamic Result { get; private set; } = null;

        public cCitrixApiResultBase(dynamic Result, string namePropery = null)
        {
            this.Result = Result;

            try { ID = Result.Id; } catch { }
            ;
            try
            {
                if (namePropery == null)
                    namePropery = "displayName";
                if (Result.TryGetValue(namePropery, out JToken JT1))
                {
                    var _ty = JT1.GetType().ToString();
                    if (JT1 is JValue _jVal)
                        DisplayName = _jVal.Value?.ToString();
                }
                else if (Result.TryGetValue("name", out JToken JT2))
                    DisplayName = Result.name;
            }
            catch { }
            try { Context = Result["@odata.context"]; } catch { }
            ODataId = GetStringFromDynamic(Result, "@odata.id");
            if (string.IsNullOrEmpty(ODataId))
                ODataId = @"https://api.cloud.com/" + ID;
        }

        public cCitrixApiResultBase(cCitrixApiResultBase Result)
        {
            if (Result == null)
                return;

            this.Result = Result.Result;
            ID = Result.ID;
            ODataId = Result.ODataId;
            DisplayName = Result.DisplayName;
            Context = Result.Context;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string GetStringFromDynamic(dynamic O, string ProperyName)
        {
            try
            {
                return (string)O[ProperyName];
            }
            catch { }
            return null;
        }
    }

    public class cCitrixCustomerInfo
    {
        public string Id { get; set; }
        public string Name { get; set; } = string.Empty;

        public cCitrixSiteInfo PrimarySite { get; set; }

        public Dictionary<Guid, cCitrixSiteInfo> Sites { get; private set; } = new Dictionary<Guid, cCitrixSiteInfo>();
    }

    public class cCitrixSiteInfo
    {
        public Guid Id { get; set; }
        public string Name { get; set; } = string.Empty;

        public bool? IsValid { get; set; } = null;
        public bool IsPrimarySite { get; set; } = false;
    }

    public class cCitrixCommunicationSystem
    {
        public const string constCitrixCloudMonitorUrl = "monitorodata/";

        public const string constCitrixOnPremMonitorUrl = "Citrix/Monitor/OData/v4/Data/";

        public cCitrixCommunicationMonitor Monitor { get; private set; }
        public cCitrixCommunicationCvad CVAD { get; private set; }

        public bool IsCloud { get; private set; }

        public string Name { get; private set; } = string.Empty;

        public cCitrixCommunicationSystem(cCitrixCommunicationCloudBase CitrixCloud, string Name)
        {
            IsCloud = true;
            this.Name = Name;
            this.Monitor = new cCitrixCommunicationMonitor(CitrixCloud);
            this.CVAD = new cCitrixCommunicationCvad(CitrixCloud);
        }

        public cCitrixCommunicationSystem(cCitrixCommunicationOnPremCvad Cvad, cCitrixCommunicationOnPremMonitor Monitor, string Name)
        {
            IsCloud = false;
            this.Name = Name;
            this.Monitor = new cCitrixCommunicationMonitor(Monitor);
            this.CVAD = new cCitrixCommunicationCvad(Cvad);
        }

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

            try
            {
                var _retVal = new cCitrixCustomerInfo();
                var CustomerInfo = await CVAD.GetCustomerInfoAsync(token);
                if (token.IsCancellationRequested)
                    return null;

                if (CustomerInfo == null)
                    return _retVal;

                _retVal.Id = CustomerInfo.Id;
                _retVal.Name = CustomerInfo.Name;

                cCitrixSiteInfo PrimarySite = null;
                int maxConsitency = 0;
                int countC0 = 0;
                int countC1 = 0;
                int countC2 = 0;
                foreach (var site in CustomerInfo.Sites.Values)
                {
                    var _newSite = new cCitrixSiteInfo()
                    {
                        Name = site.Name,
                        Id = site.Id,
                        IsValid = false
                    };

                    // validate the site consitency
                    var _resultConsitency = await CheckSiteConsitencyAsync(site.Id.ToString("D"), token);
                    if (token.IsCancellationRequested)
                        return null;
                    switch (_resultConsitency)
                    {
                        case 0: _newSite.IsValid = null; countC0++; break;
                        case 1: _newSite.IsValid = true; countC1++; break;
                        case 2: _newSite.IsValid = true; countC2++; break;
                    }

                    if (maxConsitency < _resultConsitency)
                        PrimarySite = _newSite;

                    maxConsitency = Math.Max(maxConsitency, _resultConsitency);

                    _retVal.Sites.Add(_newSite.Id, _newSite);
                }

                if (PrimarySite != null)
                {
                    if (countC2 == 1)
                        PrimarySite.IsPrimarySite = true;
                    else if (countC1 == 1 && countC2 == 0)
                        PrimarySite.IsPrimarySite = true;
                }

                if (PrimarySite?.IsPrimarySite == true)
                    _retVal.PrimarySite = PrimarySite;

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


            return null;
        }

        public async Task<int> CheckSiteConsitencyAsync(string SiteId, CancellationToken token)
        {
            // check if the machines queried from the monitor component could be found in the cvad component
            // return values:
            //   2: all machines could be found
            //   1: al least one machine could be found
            //   0: no machines in monitor component
            //  -1: none of the machines could be found
            //  -2: internal error occured

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

            var _lastSiteId = CVAD.CitrixInfo.SiteId;
            try
            {
                CVAD.CitrixInfo.SiteId = SiteId;

                // get the machine list from cvad
                var _machinesCvad = await CVAD.GetMachineNamesAsync(token);
                if (token.IsCancellationRequested)
                    return -2;

                // get the machine list from monitor
                var _machinesMonitor = await Monitor.GetMachineNamesAsync(token);
                if (token.IsCancellationRequested)
                    return -2;

                if (_machinesMonitor == null || _machinesMonitor.Count == 0)
                    return 0;

                var atLeastOne = false;
                var all = true;
                foreach ( var machineName in _machinesMonitor )
                {
                    if (_machinesCvad.Contains(machineName))
                        atLeastOne = true;
                    else
                        all = false;
                }

                if (atLeastOne && all)
                    return 2;
                if (atLeastOne)
                    return 1;

                return -1;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                CVAD.CitrixInfo.SiteId = _lastSiteId;
                if (CM != null) LogMethodEnd(CM);
            }

            return -2;
        }
    }

    public interface iCitrixCvadInfo
    {
        string CustomerId { get; }

        string SiteId { get; set; }

    }

    public class cCitrixCommunicationCloudBase : cHttpApiBase, iCitrixCvadInfo
    {
        public const int constMaxPageSize = 1000;

        private string InstanceId = null;

        public cCitrixCommunicationCloudBase(string baseUrlCloud) : base("Citrix customer tenant", $"{baseUrlCloud}/cctrustoauth2/{{Tenant}}/tokens/clients", "Citrix API", baseUrlCloud)
        {
            MaxConcurrentRequests = 1;
            KnownAutoRetryErrors.Add((HttpStatusCode)429);
            autoRetryCount = 50;
            autoRetryDelay = 200;
            autoRetryOnKnownErrors = true;
        }

        private protected override List<KeyValuePair<string, string>> GetRequestHeaders(cOAuthLogonInfo privLogonInfo)
        {

            return new List<KeyValuePair<string, string>>()
            {
                new KeyValuePair<string, string>("Authorization", $"CwsAuth Bearer={AccessToken}"),
                new KeyValuePair<string, string>("Citrix-CustomerId", privLogonInfo.Tenant),
                new KeyValuePair<string, string>("Citrix-InstanceId", InstanceId)
            };
        }

        public string CustomerId { get {
                return this.privLogonInfo?.Tenant;
            } }

        public string SiteId
        {
            get { return InstanceId; }
            set { InstanceId = value; }
        }
    }

    public class cCitrixCommunicationOnPremCvad : cHttpApiBase, iCitrixCvadInfo
    {
        private const string constLogonUrl = "manage/Tokens";
        private string privCustomerId = null;
        private string InstanceId = null;

        public cCitrixCommunicationOnPremCvad(string baseUrl) : base("Citrix on-prem system", baseUrl, "Citrix API", baseUrl + "/cvad/")
        {
            // MaxConcurrentRequests = 1;
            //KnownAutoRetryErrors.Add((HttpStatusCode)429);
            //autoRetryCount = 50;
            //autoRetryDelay = 200;
            //autoRetryOnKnownErrors = true;
        }

        public string CustomerId { get { return privCustomerId; } }

        public string SiteId
        {
            get { return InstanceId; }
            set { InstanceId = value; }
        }

        private protected override async Task<cCredentialLoginInfo> privLogonWithCredentialsAsync(NetworkCredential credentials)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var _usr = credentials.UserName;
                if (!string.IsNullOrEmpty(credentials.Domain))
                    _usr = $"{credentials.Domain}\\{_usr}";
                var _auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_usr}:{credentials.Password}"));
                using (var httpClient = new HttpClient())
                {
                    httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", _auth);
                    httpClient.BaseAddress = new Uri(RequestBaseUrl);
                    var _result = await httpClient.PostAsync(constLogonUrl, null);
                    if (_result != null)
                    {
                        var content = await _result.Content.ReadAsStringAsync();
                        if (_result.IsSuccessStatusCode)
                        {
                            var JO = JObject.Parse(content);
                            var token = JO["Token"]?.ToString();
                            var CustomerId = JO["CustomerId"]?.ToString();
                            var strExpires = JO["ExpiresAt"]?.ToString();
                            if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(CustomerId) || string.IsNullOrEmpty(strExpires))
                            {
                                LastErrorMessage = $"CVAD Logon failed because no valid token information could be found in the logon result.";
                                return new cCredentialLoginInfo();
                            }


                            if (!DateTime.TryParse(strExpires, out var expires))
                            {
                                LastErrorMessage = $"CVAD Logon failed because no valid expiration information could be found in the logon result.";
                                return new cCredentialLoginInfo();
                            }

                            expires = DateTime.SpecifyKind(expires, DateTimeKind.Utc);
                            var _now = DateTime.UtcNow;

                            var _expieresInteral = expires - _now;
                            var _delta = _expieresInteral.TotalSeconds * RelogonFactor;
                            var expires2 = _now + TimeSpan.FromSeconds(_delta);

                            this.privCustomerId = CustomerId;

                            return new cCredentialLoginInfo() { IsValid = true, Token = token, TokenExpiresIn = expires2 };
                        }
                        else
                        {
                            LastErrorMessage = $"CVAD Logon failed with status code {(int)_result.StatusCode}: {_result.ReasonPhrase}";
                        }
                    }
                    else
                        LastErrorMessage = $"CVAD Logon failed due to unknown reason.";
                }

            }
            catch (Exception E)
            {
                LastErrorMessage = $"CVAD Logon failed with exception: {E.Message}";
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }


            return new cCredentialLoginInfo();
        }

        private protected override List<KeyValuePair<string, string>> GetRequestHeaders(cOAuthLogonInfo privLogonInfo)
        {
            var _retVal = new List<KeyValuePair<string, string>>()
            {
                new KeyValuePair<string, string>("Authorization", $"CwsAuth Bearer={AccessToken}")
            };

            if (privCustomerId != null)
                _retVal.Add(new KeyValuePair<string, string>("Citrix-CustomerId", privCustomerId));

            if (InstanceId != null)
                _retVal.Add(new KeyValuePair<string, string>("Citrix-InstanceId", InstanceId));

            return _retVal;
        }

    }

    public class cCitrixCommunicationOnPremMonitor : cHttpApiBase
    {
        private const string constLogonUrl = "Users?$top=1&$select=Id";

        public cCitrixCommunicationOnPremMonitor(string baseUrl) : base("Citrix on-prem system", baseUrl, "Citrix API", baseUrl + string.Format("/Citrix/Monitor/OData/v4/Data/"))
        {
        }

        private protected override async Task<cCredentialLoginInfo> privLogonWithCredentialsAsync(NetworkCredential credentials)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var _auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{credentials.Domain}\\{credentials.UserName}:{credentials.Password}"));

                using (var _handler = new HttpClientHandler())
                {
                    _handler.Credentials = credentials;
                    _handler.PreAuthenticate = true;
                    using (var httpClient = new HttpClient(_handler))
                    {
                        httpClient.BaseAddress = new Uri(RequestBaseUrl);
                        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
                        var _result = await httpClient.GetAsync(constLogonUrl);
                        if (_result != null)
                        {
                            var content = await _result.Content.ReadAsStringAsync();
                            if (_result.IsSuccessStatusCode)
                                return new cCredentialLoginInfo() { IsValid = true, Token = null, TokenExpiresIn = DateTime.MaxValue, useAlwaysCredentials = true };
                            else
                                LastErrorMessage = $"Monitor Logon failed with status code {(int)_result.StatusCode}: {_result.ReasonPhrase}";
                        }
                        else
                            LastErrorMessage = $"Monitor Logon failed due to unknown reason.";

                    }
                }
            }
            catch (Exception E)
            {
                LastErrorMessage = $"Monitor Logon failed with exception: {E.Message}";
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }


            return new cCredentialLoginInfo();
        }

        private protected override List<KeyValuePair<string, string>> GetRequestHeaders(cOAuthLogonInfo privLogonInfo)
        {
            var _retVal = new List<KeyValuePair<string, string>>();
            {
                new KeyValuePair<string, string>("Accept", "application/json");
            }
            ;

            return _retVal;
        }
    }

    public class cCitrixCommunicationBase
    {
        private cHttpApiBase HttpApi;

        public Uri baseUri { get; private set; }

        public string LastErrorMessage { get { return HttpApi.LastErrorMessage; } }

        public cCitrixCommunicationBase(cHttpApiBase HttpApi, string baseUrl)
        {
            this.HttpApi = HttpApi;
            this.baseUri = new Uri(new Uri(HttpApi.RequestBaseUrl), baseUrl);
        }

        public async Task<cCitrixApiResultBase> RequestAsync(string Request, eHttpMethod httpMethod, object JsonData, string nameProperty = null, bool retryForbidden = false, bool noAutoRelogon = false, CancellationToken token = default)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var uriRequest = new Uri(baseUri, Request);

                var data = await HttpApi.privRequestAsync(uriRequest.OriginalString, httpMethod, JsonData, retryForbidden, noAutoRelogon: noAutoRelogon, token: token);
                if (data == null)
                    return null;

                if (data is bool isValid)
                {
                    if (isValid)
                        return new cCitrixApiResultBase(null);
                    else
                        return null;
                }

                var RetVal = new cCitrixApiResultBase(data, nameProperty);
                return RetVal;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
            return null;
        }

        public async Task<cCitrixApiResultList> RequestListAsync(string Request, bool loadPaged, bool retryForbidden, CancellationToken token = default)
        {
            try
            {
                var uriRequest = new Uri(baseUri, Request);
                var res = await HttpApi.privRequestAsync(uriRequest.OriginalString, autoRetry: retryForbidden, token: token);
                if (res != null)
                {
                    var RetVal = new cCitrixApiResultList(retryForbidden);
                    RetVal.AddResult(res);
                    if (!RetVal.hasRemaining || loadPaged)
                        return RetVal;
                    while (RetVal.hasRemaining)
                    {
                        if (!await RequestNextAsync(RetVal))
                            return RetVal;
                    }
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
            }
            return null;
        }

        public async Task<bool> RequestNextAsync(cCitrixApiResultList List, CancellationToken token = default)
        {
            try
            {
                var res = await HttpApi.privRequestAsync(List.NextResultUrl, autoRetry: List.retryForbidden, token: token);
                if (res != null)
                {
                    List.AddResult(res);
                    return true;
                }
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
            }

            return false;
        }

    }

    public class cCitrixCommunicationCvad
    {
        public const string constCitrixCvadUrl = "cvad/";

        public const string constCitrixApiMe = "manage/Me";
        public const string constCitrixApiMachineNames = "manage/Machines?fields=DnsName";
        public const string constCitrixApiSessions = "manage/Sessions?fields=Id,Uid,State,StartTime";

        public cCitrixCommunicationBase Request { get; private set; }

        public iCitrixCvadInfo CitrixInfo { get; private set; }

        public cCitrixCommunicationCvad(cCitrixCommunicationCloudBase CitrixCloud)
        {
            this.Request = new cCitrixCommunicationBase(CitrixCloud, constCitrixCvadUrl);
            this.CitrixInfo = CitrixCloud as iCitrixCvadInfo;
        }

        public cCitrixCommunicationCvad(cCitrixCommunicationOnPremCvad OnPremCvad)
        {
            this.Request = new cCitrixCommunicationBase(OnPremCvad, null);
            this.CitrixInfo = OnPremCvad as iCitrixCvadInfo;
        }


        public string GetServer()
        {
            return Request?.baseUri?.Host;
        }

        public async Task<cCitrixCustomerInfo> GetCustomerInfoAsync(CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                var _resultMe = await Request.RequestAsync(constCitrixApiMe, eHttpMethod.get, null);

                if (Token.IsCancellationRequested)
                    return null;

                if (_resultMe?.Result == null)
                    return null;

                var _obj = _resultMe.Result.Customers;
                var _customers = _obj as IEnumerable<dynamic>;

                if (_customers == null)
                    return null;

                cCitrixCustomerInfo _retVal = null;

                foreach (var _customer in _customers) {
                    var id = _customer.Id;
                    if (id != CitrixInfo?.CustomerId)
                        continue;

                    _retVal = new cCitrixCustomerInfo()
                    {
                        Id = _customer.Id,
                        Name = _customer?.Name
                    };

                    _obj = _customer.Sites;
                    var _sites = _obj as IEnumerable<dynamic>;
                    if (_sites != null)
                        foreach (var _site in _sites)
                        {
                            try
                            {
                                if (Guid.TryParse(_site.Id.ToString(), out Guid _id))
                                {
                                    var siteInfo = new cCitrixSiteInfo()
                                    {
                                        Id = _id,
                                        Name = _site.Name
                                    };
                                    _retVal.Sites[_id] = siteInfo;
                                }
                            }
                            catch (Exception E)
                            {
                                cLogManager.DefaultLogger.LogException(E);
                            }
                        }

                }

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

            return null;
        }

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

            try
            {
                var _result = await Request.RequestAsync(constCitrixApiMachineNames, eHttpMethod.get, null, token: token);

                if (_result?.Result == null)
                    return null;

                var _items = _result.Result?.Items as IEnumerable<dynamic>;

                if (_items == null)
                    return null;

                var _retVal = new List<string>();
                foreach ( var item in _items )
                {
                    try
                    {
                        var _dnsName = item.DnsName?.ToString()?.ToLowerInvariant();
                        if (!string.IsNullOrEmpty( _dnsName ))
                            _retVal.Add( _dnsName );
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                }

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

            return null;
        }

        public class SessionInfo
        {
            public string Id { get; set; }
            public string Uid { get; set; }
            public string State { get; set; }
            public DateTime? StartTime { get; set; }
        }

        public async Task<List<SessionInfo>> GetSessionsAsync()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var _result = await Request.RequestAsync(constCitrixApiSessions, eHttpMethod.get, null);
                if (_result?.Result == null)
                    return null;
                var _items = _result.Result?.Items as IEnumerable<dynamic>;
                if (_items == null)
                    return null;
                var _retVal = new List<SessionInfo>();
                foreach (var item in _items)
                {
                    try
                    {
                        var _sessionInfo = new SessionInfo()
                        {
                            Id = item.Id?.ToString(),
                            Uid = item.Uid?.ToString(),
                            State = item.State?.ToString(),
                            StartTime = item.StartTime != null ? (DateTime?)item.StartTime : null
                        };
                        _retVal.Add(_sessionInfo);
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                }
                return _retVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
            return null;
        }
    }

    public class cCitrixCommunicationMonitor
    {
        public const string constCitrixCloudUrl = "monitorodata/";

        public const string constCitrixMachineNames = "Machines/?$select=DnsName&$filter=DesktopGroupId+ne+null+and+Name+ne+null";
        public const string constCitrixUserList = "Users?$select=Id,Sid,Upn,Domain&$filter=Domain ne ''";
        public const string constCitrixMachineList = "Machines?$select=Id,Sid,DnsName&$filter=DesktopGroupId ne null and Name ne null";
        public const string constCitrixSessionListDetailed = "Sessions?$select=SessionKey,StartDate,EndDate,ConnectionState, UserId, MachineId&$expand=Machine($select=Id,DnsName,Name)&$filter=EndDate eq null or EndDate gt {0}";

        public int MaxPageSize { get; set; } = 100;

        public cCitrixCommunicationBase Request { get; private set; }

        public cCitrixCommunicationMonitor(cCitrixCommunicationCloudBase CitrixCloud)
        {
            this.Request = new cCitrixCommunicationBase(CitrixCloud, constCitrixCloudUrl);
        }

        public cCitrixCommunicationMonitor(cCitrixCommunicationOnPremMonitor OnPremMonitor)
        {
            this.Request = new cCitrixCommunicationBase(OnPremMonitor, null);
        }

        private string getFormattedDateTime(int DayOffset = 0)
        {
            var _dateTime = DateTime.UtcNow.AddDays(DayOffset);
            var _strDateTime = _dateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
            return _strDateTime;
        }

        private string getFormattedGuid(Guid id)
        {
            return id.ToString("D");
        }

        public async Task<List<string>> GetMachineNamesAsync(CancellationToken token)
        {
            var _processor = new ScanEntryStringListProcessor("DnsName", true);
            var _count = await RequestListAsync(constCitrixMachineList, _processor.ProcessScannedItemAsync, token: token);
            return _processor.Result;
        }

        public delegate Task<bool> ProcessScanEntryDelegate(dynamic ScanEntry, CancellationToken token);

        public abstract class ScanEntryProcessor
        {
            public abstract Task<bool> ProcessScannedItemAsync(dynamic _node, CancellationToken token);
        }

        public class ScanEntryCountProcessor : ScanEntryProcessor
        {
            private string nameProperty = null;

            public ScanEntryCountProcessor(string nameProperty)
            {
                this.nameProperty = nameProperty;
            }

            public async override Task<bool> ProcessScannedItemAsync(dynamic _node, CancellationToken token)
            {
                try
                {
                    string _prop = cCitrixApiResultBase.GetStringFromDynamic(_node, nameProperty);
                    if (!string.IsNullOrEmpty(_prop))
                        return true;
                }
                catch (Exception E)
                {
                    LogException(E);
                }
                return false;
            }

        }

        public class ScanEntryStringListProcessor : ScanEntryProcessor
        {
            private string nameProperty = null;
            private bool toLowercase = false;

            public List<string> Result = new List<string>();

            public ScanEntryStringListProcessor(string nameProperty, bool toLowercase = false)
            {
                this.nameProperty = nameProperty;
                this.toLowercase = toLowercase;
            }

            public async override Task<bool> ProcessScannedItemAsync(dynamic _node, CancellationToken token)
            {
                try
                {
                    string _prop = cCitrixApiResultBase.GetStringFromDynamic(_node, nameProperty);
                    if (!string.IsNullOrEmpty(_prop))
                    {
                        if (toLowercase)
                            _prop = _prop.ToLowerInvariant();
                        Result.Add(_prop);
                        return true;
                    }
                }
                catch (Exception E)
                {
                    LogException(E);
                }
                return false;
            }

        }

        private async Task<int> RequestListAsync(string Url, ProcessScanEntryDelegate ProcessScanEntry, CancellationToken token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            try
            {
                int countTotal = 0;
                int skipping = 0;
                int lastCount = 0;
                do
                {
                    lastCount = 0;
                    var _url = Url + $"&$top={MaxPageSize}&$skip={skipping}";
                    var _result = await Request.RequestAsync(_url, eHttpMethod.get, null, token: token);
                    if (_result == null || _result.Result == null)
                        return -1;

                    var _items = _result.Result?.value as IEnumerable<dynamic>;

                    if (_items == null)
                        return -1;

                    foreach (var item in _items)
                    {
                        try
                        {
                            var _item = item as dynamic;
                            var _ok = await ProcessScanEntry(_item, token);
                            if (token.IsCancellationRequested)
                                return 0;
                            if (_ok)
                            {
                                lastCount++;
                                countTotal++;
                            }
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                    }

                    skipping += lastCount;
                } while (lastCount >= MaxPageSize);

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

            return -1;
        }

        public async Task<int> ScanUsersAsync(ScanEntryProcessor _processor, CancellationToken token)
        {
            return await RequestListAsync(constCitrixUserList, _processor.ProcessScannedItemAsync, token);
        }

        public async Task<int> ScanMachinesAsync(ScanEntryProcessor _processor, CancellationToken token)
        {
                return await RequestListAsync(constCitrixMachineList, _processor.ProcessScannedItemAsync, token);
        }

        public async Task<int> ScanSessionsAsync(ScanEntryProcessor _processor, int DayOffset, CancellationToken token)
        {
            var _url = string.Format(constCitrixSessionListDetailed, getFormattedDateTime(DayOffset));
            return await RequestListAsync(_url, _processor.ProcessScannedItemAsync, token);
        }
    }
}   