using C4IT.F4SDM;
using C4IT.FASD.Base;
using C4IT.Logging;
using Matrix42.Common;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using update4u.SPS.DataLayer;
using update4u.SPS.DataLayer.Transaction;
using static C4IT.FASD.Base.cF4SDTicket;
using static C4IT.Logging.cLogManager;

namespace C4IT.F4SD
{
    public class F4SDHelperService
    {
        private static Guid SPSUserClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSUserClassBase");
        private static Guid SPSAccountClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassBase");
        private static Guid SPSCommonClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSCommonClassBase");
        private static Guid SPSAccountClassADID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassAD");
        private static Guid SPSActivityClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassBase");
        private static Guid SPSActivityClassIncidentID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassIncident");
        private static Guid SPSActivityClassUnitOfWorkID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassUnitOfWork");
        private static Guid SPSScCategoryClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSScCategoryClassBase");
        private static Guid SPSAssetClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAssetClassBase");
        private static Guid SPSSecurityClassRole = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSSecurityClassRole");

        public static readonly Guid Administrators = new Guid("{A5D7B682-B211-4D94-A96D-8C57EEDAFDEA}");

        private const string TicketCloseActionId = "51bb3283-7bd1-e511-9a82-60e327035d31";
        private const string TicketDirectLinkCreateTemplate = @"{0}/wm/app-ServiceDesk/notSet/create-object/{1}?view-options=%7B%22embedded%22:false%7D&presetParams={2}";
        private const string IncidentDirectLinkCloseTemplate = @"{0}/wm/app-ServiceDesk/notSet/preview-object/SPSActivityTypeIncident/{1}/0/?view-options={2}";
        private const string TicketDirectLinkPreviewTemplate = @"{0}/wm/app-ServiceDesk/notSet/preview-object/{1}/{2}/0/";
        private const string TicketDirectLinkEditTemplate = @"{0}/wm/app-ServiceDesk/notSet/edit-object/{1}/{2}";

        private const string placeHolderSubject = "PARAM_SUBJECT";
        private const string placeHolderDescription = "PARAM_DESCRIPTION";
        private const string c4itf4sdmonLinkBase = "f4sdsend://localhost";

        private const string F4SDTicketTableName = "M42WPM-TICKETS-INFOS";
        private const string F4SDTicketStatusColumnName = "STATUS";


        public static IEnumerable<string> ReadLines(Func<Stream> streamProvider,
                                     Encoding encoding)
        {
            using (var stream = streamProvider())
            using (var reader = new StreamReader(stream, encoding))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    yield return line;
                }
            }
        }

        internal async Task<string> getDirectLinkF4SD(Guid EOID, string type)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                LogEntry($"Generating F4SD URI for type: '{type}', GUID: '{EOID}'", LogLevels.Debug);
                var builder = new UriBuilder(c4itf4sdmonLinkBase);
                var queryString = HttpUtility.ParseQueryString(builder.Query);

                switch (type.ToLowerInvariant().Split('.').Last())
                {
                    case "person":
                    case "user":
                        var user = getUsersByAsql($"[Expression-ObjectId]='{EOID}'")?.FirstOrDefault();
                        string Sids = string.Empty;

                        if (user != null)
                        {
                            var accounts = getAccountsByAsql($"Owner='{user.Id}'");
                            Sids = string.Join(",", accounts?.Where(x => !string.IsNullOrEmpty(x.Sid)).Select(x => x.Sid) ?? Array.Empty<string>());
                        }

                        if (!string.IsNullOrEmpty(user?.Name))
                        {
                            queryString.Add("command", "UserSidSearch");
                            queryString.Add("name", user.Name);
                            queryString.Add("sids", Sids);
                        }
                        break;

                    case "asset":
                        var asset = getAssetsByAsql($"UsedInTypeSPSComputerType is not null AND [Expression-ObjectId]='{EOID}'")?.FirstOrDefault();
                        if (asset != null)
                        {
                            queryString.Add("command", "ComputerDomainSearch");
                            queryString.Add("name", asset.Name);
                            queryString.Add("domain", asset.DomainName);
                        }
                        break;

                    case "incident":
                    case "servicerequest":
                    case "ticket":
                        var ticket = (await getTicketDetails(new List<Guid> { EOID }))?.FirstOrDefault();
                        if (ticket != null)
                        {
                            queryString.Add("command", "TicketSearch");
                            queryString.Add("tname", ticket.Name);
                            queryString.Add("tid", ticket.TicketObjectId.ToString());

                            if (ticket.AffectedUserId != Guid.Empty)
                            {
                                var initiator = getUsersByAsql($"ID = '{ticket.AffectedUserId}'")?.FirstOrDefault();
                                string userName = initiator?.Name;
                                Sids = string.Join(",", getAccountsByAsql($"Owner='{ticket.AffectedUserId}'")?.Where(x => !string.IsNullOrEmpty(x.Sid)).Select(x => x.Sid) ?? Array.Empty<string>());

                                queryString.Add("uname", userName);
                                queryString.Add("sids", Sids);
                            }
                        }
                        break;

                    default:
                        break;
                }

                builder.Query = queryString.ToString();
                return string.IsNullOrEmpty(builder.Query) ? null : builder.ToString();
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }


        internal async Task<List<cF4SDTicketSummary>> getTicketListByUser(string userSid, int hours, int queueoption, List<cApiM42TicketQueueInfo> queues)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                LogEntry($"Generating ticket list for userSid: '{userSid}', {hours} hours", LogLevels.Debug);
                await Task.Delay(0);

                var tickets = new List<cF4SDTicketSummary>();
                var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
                LogEntry($"ASql Filter: {activityFilter}");

                var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
                    string.Format("[Expression-ObjectID] as EOID, TicketNumber, Subject, Service.ID as ServiceId, Service.Name as ServiceName, SUBQUERY(BasicSchemaObjectType as bso, bso.Name, bso.id = base.T(SPSCommonClassBase).TypeID) as ActivityType" +
                    ", COALESCE(T(SPSActivityClassIncident).Asset.T(SPSComputerClassBase).Name, T(SPSActivityClassIncident).Asset.T(SPSAssetClassSIMCard).PhoneNumber, T(SPSActivityClassIncident).Asset.Name, T(SPSActivityClassIncident).Asset.objectid) as AssetName" +
                    ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSActivityClassIncident).Asset.T(SPSCommonClassBase).TypeID) as AssetCIName" +
                    ", CASE WHEN Initiator.PrimaryAccount.T(SPSAccountClassAd).Sid = '{0}' THEN 1 ELSE 0 END AS IsPrimaryAccount",
                    userSid),
                    activityFilter);

                if (activityTable?.Rows == null || activityTable.Rows.Count == 0)
                {
                    LogEntry($"No activity entries found for userSid: '{userSid}'", LogLevels.Warning);
                    return null;
                }

                for (int i = 0; i < activityTable.Rows.Count; i++)
                {
                    DataRow entry = activityTable.Rows[i];
                    var activityEOId = getGuidFromObject(entry["EOID"]);
                    var ticketNumber = getStringFromObject(entry["TicketNumber"]);
                    var activityType = getStringFromObject(entry["ActivityType"]);
                    var assetName = getStringFromObject(entry["AssetName"]);
                    var ServiceName = getStringFromObject(entry["ServiceName"]);
                    var assetCIName = getStringFromObject(entry["AssetCIName"]);
                    var subject = getStringFromObject(entry["Subject"]);

                    var stateData = GetActivityState(activityEOId);
                    var state = stateData.Item1;
                    var stateDisp = stateData.Item2;

                    LogEntry($"Activity found {i + 1}/{activityTable.Rows.Count}: ObjectID={activityEOId}, TicketNumber={ticketNumber}, Subject={subject}, State={state}", LogLevels.Debug);

                    if (string.IsNullOrEmpty(ticketNumber))
                    {
                        LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    if (string.IsNullOrEmpty(assetName))
                    {
                        LogEntry($"No AssetName found for activity entry", LogLevels.Debug);
                    }

                    if (string.IsNullOrEmpty(assetCIName))
                    {
                        LogEntry($"No AssetCIName found for activity entry", LogLevels.Debug);
                    }
                    var ServiceId = getGuidFromObject(entry["ServiceId"]);
                    if (ServiceId == Guid.Empty)
                    {
                        LogEntry($"no ServiceId found for activity entry", LogLevels.Debug);
                    }
                    var AssetCIName = getStringFromObject(entry["AssetCIName"]);
                    if (AssetCIName == string.Empty)
                    {
                        LogEntry($"no AssetCIName found for activity entry", LogLevels.Debug);
                    }
                    var Subject = getStringFromObject(entry["Subject"]);
                    if (Subject == string.Empty)
                    {
                        LogEntry($"no Subject found for activity entry", LogLevels.Warning);
                        continue;
                    }
                    var IsPrimaryAccount = getIntFromObject(entry["IsPrimaryAccount"]);

                    if (string.IsNullOrEmpty(subject))
                    {
                        LogEntry($"No Subject found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    tickets.Add(new cF4SDTicketSummary()
                    {
                        TicketObjectId = activityEOId,
                        Name = ticketNumber,
                        ActivityType = activityType,
                        AssetCIName = assetCIName,
                        AssetName = assetName,
                        ServiceId = ServiceId,
                        ServiceName = ServiceName,
                        StatusId = state,
                        Status = stateDisp,
                        Summary = subject,
                        IsPrimaryAccount = IsPrimaryAccount == 1
                    });
                }

                tickets = tickets.OrderByDescending(x => x.Name).ToList();
                return tickets;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private static readonly string[] TicketOverviewKeys = new[]
        {
            "TicketsNew",
            "TicketsActive",
            "TicketsCritical",
            "TicketsNewInfo",
            "IncidentNew",
            "IncidentActive",
            "IncidentCritical",
            "IncidentNewInfo",
            "UnassignedTickets",
            "UnassignedTicketsCritical"
        };

        public class TicketOverviewCountsResult
        {
            [JsonProperty("counts")]
            public Dictionary<string, int> Counts { get; set; } = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        }

        public class TicketOverviewCountsByRoleResult
        {
            [JsonProperty("generatedAtUtc")]
            public DateTime GeneratedAtUtc { get; set; } = DateTime.UtcNow;

            [JsonProperty("countsByRole")]
            public Dictionary<string, Dictionary<string, int>> CountsByRole { get; set; }
                = new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);
        }

        public class TicketOverviewRelationDto
        {
            public enumF4sdSearchResultClass Type { get; set; }
            public string Name { get; set; }
            public string DisplayName { get; set; }
            public Guid id { get; set; }
            public enumF4sdSearchResultStatus Status { get; set; } = enumF4sdSearchResultStatus.Unknown;
            public Dictionary<string, string> Infos { get; set; } = null;
            public List<cF4sdIdentityEntry> Identities { get; set; } = null;
        }

        private sealed class TicketOverviewEntry
        {
            public Guid TicketId { get; set; }
            public string TicketNumber { get; set; }
            public string Summary { get; set; }
            public Guid InitiatorId { get; set; }
            public string InitiatorDisplayName { get; set; }
            public string InitiatorAccount { get; set; }
            public string InitiatorDomain { get; set; }
            public string InitiatorSid { get; set; }
            public Guid RecipientId { get; set; }
            public Guid RecipientRoleId { get; set; }
            public int State { get; set; }
            public DateTime CreatedDate { get; set; }
            public bool NewInformationReceived { get; set; }
            public bool ReactionTimeEscalated { get; set; }
            public bool SolutionTimeEscalated { get; set; }
            public bool IsIncident { get; set; }
            public string ActivityType { get; set; }
        }

        internal async Task<TicketOverviewCountsResult> getTicketOverviewCounts(
            string sid,
            string scope,
            IEnumerable<string> keys,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase);
                var normalizedKeys = (keys ?? Array.Empty<string>())
                    .Where(k => !string.IsNullOrWhiteSpace(k))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();

                if (normalizedKeys.Count == 0)
                {
                    normalizedKeys.AddRange(TicketOverviewKeys);
                }

                var entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues);
                var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

                foreach (var key in normalizedKeys)
                {
                    counts[key] = entries.Count(entry => MatchesTicketOverviewKey(entry, key));
                }

                return new TicketOverviewCountsResult { Counts = counts };
            }
            catch (Exception E)
            {
                LogException(E);
                return new TicketOverviewCountsResult();
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        internal async Task<TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles(
            string sid,
            IEnumerable<Guid> roleGuids,
            IEnumerable<string> keys,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var normalizedKeys = (keys ?? Array.Empty<string>())
                    .Where(k => !string.IsNullOrWhiteSpace(k))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();

                if (normalizedKeys.Count == 0)
                {
                    normalizedKeys.AddRange(TicketOverviewKeys);
                }

                var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, roleGuids);
                if (roleIds.Count == 0)
                    return new TicketOverviewCountsByRoleResult();

                var entries = await LoadTicketOverviewEntriesByRoleIds(roleIds, queueoption, queues);
                var entriesByRole = entries
                    .GroupBy(entry => entry.RecipientRoleId)
                    .ToDictionary(group => group.Key, group => group.ToList());

                var countsByRole = new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);
                foreach (var roleId in roleIds)
                {
                    var roleEntries = entriesByRole.TryGetValue(roleId, out var list)
                        ? list
                        : new List<TicketOverviewEntry>();

                    var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
                    foreach (var key in normalizedKeys)
                    {
                        counts[key] = roleEntries.Count(entry => MatchesTicketOverviewKey(entry, key));
                    }

                    countsByRole[roleId.ToString()] = counts;
                }

                return new TicketOverviewCountsByRoleResult
                {
                    GeneratedAtUtc = DateTime.UtcNow,
                    CountsByRole = countsByRole
                };
            }
            catch (Exception E)
            {
                LogException(E);
                return new TicketOverviewCountsByRoleResult();
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        internal async Task<List<TicketOverviewRelationDto>> getTicketOverviewRelations(
            string sid,
            string scope,
            string key,
            int count,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (string.IsNullOrWhiteSpace(key))
                    return new List<TicketOverviewRelationDto>();

                var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase);
                var entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues);

                var filtered = entries
                    .Where(entry => MatchesTicketOverviewKey(entry, key))
                    .OrderByDescending(entry => entry.CreatedDate)
                    .ToList();

                if (count > 0)
                    filtered = filtered.Take(count).ToList();

                var relations = new List<TicketOverviewRelationDto>(filtered.Count);
                foreach (var entry in filtered)
                {
                    var relation = new TicketOverviewRelationDto
                    {
                        Type = enumF4sdSearchResultClass.Ticket,
                        DisplayName = entry.TicketNumber ?? string.Empty,
                        Name = entry.TicketNumber ?? string.Empty,
                        id = entry.TicketId,
                        Status = enumF4sdSearchResultStatus.Active,
                        Infos = new Dictionary<string, string>
                        {
                            ["Summary"] = entry.Summary ?? string.Empty,
                            ["StatusId"] = ConvertM42State(entry.State),
                            ["ActivityType"] = entry.ActivityType ?? string.Empty,
                            ["UserDisplayName"] = entry.InitiatorDisplayName ?? string.Empty,
                            ["UserAccount"] = entry.InitiatorAccount ?? string.Empty,
                            ["UserDomain"] = entry.InitiatorDomain ?? string.Empty,
                            ["UserSid"] = entry.InitiatorSid ?? string.Empty,
                            ["Sids"] = entry.InitiatorSid ?? string.Empty,
                            ["UserId"] = entry.InitiatorId != Guid.Empty ? entry.InitiatorId.ToString() : string.Empty,
                            ["UserGuid"] = entry.InitiatorId != Guid.Empty ? entry.InitiatorId.ToString() : string.Empty
                        },
                        Identities = new List<cF4sdIdentityEntry>
                        {
                            new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = entry.TicketId },
                            new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = entry.InitiatorId }
                        }
                    };
                    relations.Add(relation);
                }

                return relations;
            }
            catch (Exception E)
            {
                LogException(E);
                return new List<TicketOverviewRelationDto>();
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private async Task<List<TicketOverviewEntry>> LoadTicketOverviewEntries(
            string sid,
            bool useRoleScope,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);

                if (string.IsNullOrWhiteSpace(sid))
                    return new List<TicketOverviewEntry>();

                var filter = await BuildTicketOverviewFilterAsync(sid, useRoleScope, queueoption, queues);
                return await LoadTicketOverviewEntriesByFilter(filter);
            }
            catch (Exception E)
            {
                LogException(E);
                return new List<TicketOverviewEntry>();
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private async Task<List<TicketOverviewEntry>> LoadTicketOverviewEntriesByRoleIds(
            IEnumerable<Guid> roleIds,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);

                var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
                return await LoadTicketOverviewEntriesByFilter(filter);
            }
            catch (Exception E)
            {
                LogException(E);
                return new List<TicketOverviewEntry>();
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private async Task<List<TicketOverviewEntry>> LoadTicketOverviewEntriesByFilter(string filter)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);

                if (string.IsNullOrWhiteSpace(filter))
                    return new List<TicketOverviewEntry>();

                var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
                    "[Expression-ObjectID] as EOID" +
                    ", TicketNumber" +
                    ", Subject" +
                    ", Initiator as InitiatorId" +
                    ", Initiator.LastName + ISNULL(', ' + Initiator.FirstName, '') as Initiator" +
                    ", Initiator.PrimaryAccount.T(SPSAccountClassAD).NBAccountName as InitiatorAccount" +
                    ", Initiator.PrimaryAccount.T(SPSAccountClassAD).Domain.NT4Name as InitiatorDomain" +
                    ", Initiator.PrimaryAccount.T(SPSAccountClassAD).Sid as InitiatorSid" +
                    ", Recipient as RecipientId" +
                    ", RecipientRole.T(SPSSecurityClassRole).ID as RecipientRoleId" +
                    ", T(SPSCommonClassBase).State as State" +
                    ", CreatedDate" +
                    ", NewInformationReceived" +
                    ", ReactionTimeEscalated" +
                    ", SolutionTimeEscalated" +
                    ", UsedInTypeSPSActivityTypeTicket as TicketEoid" +
                    ", UsedInTypeSPSActivityTypeIncident as IncidentEoid" +
                    ", UsedInTypeSPSActivityTypeAlert as AlertEoid" +
                    ", UsedInTypeSPSActivityTypeGroupTicket as ProblemEoid" +
                    ", UsedInTypeSPSActivityTypeServiceRequest as ServiceRequestEoid" +
                    ", UsedInTypeSPSActivityTypeBase as TaskEoid" +
                    ", UsedInTypeSVMChangeRequestType as ChangeEoid",
                    filter);

                if (tbl?.Rows == null || tbl.Rows.Count == 0)
                    return new List<TicketOverviewEntry>();

                var entries = new List<TicketOverviewEntry>(tbl.Rows.Count);
                foreach (DataRow row in tbl.Rows)
                {
                    var ticketId = getGuidFromObject(row["EOID"]);
                    if (ticketId == Guid.Empty)
                        continue;

                    var ticketEoid = getGuidFromObject(row["TicketEoid"]);
                    var incidentEoid = getGuidFromObject(row["IncidentEoid"]);
                    var alertEoid = getGuidFromObject(row["AlertEoid"]);
                    var problemEoid = getGuidFromObject(row["ProblemEoid"]);
                    var serviceRequestEoid = getGuidFromObject(row["ServiceRequestEoid"]);
                    var taskEoid = getGuidFromObject(row["TaskEoid"]);
                    var changeEoid = getGuidFromObject(row["ChangeEoid"]);
                    var isIncident = incidentEoid != Guid.Empty;
                    var activityType = ResolveTicketOverviewActivityType(
                        ticketEoid,
                        incidentEoid,
                        alertEoid,
                        problemEoid,
                        serviceRequestEoid,
                        taskEoid,
                        changeEoid);

                    entries.Add(new TicketOverviewEntry
                    {
                        TicketId = ticketId,
                        TicketNumber = getStringFromObject(row["TicketNumber"]),
                        Summary = getStringFromObject(row["Subject"]),
                        InitiatorId = getGuidFromObject(row["InitiatorId"]),
                        InitiatorDisplayName = getStringFromObject(row["Initiator"]),
                        InitiatorAccount = getStringFromObject(row["InitiatorAccount"]),
                        InitiatorDomain = getStringFromObject(row["InitiatorDomain"]),
                        InitiatorSid = getStringFromObject(row["InitiatorSid"]),
                        RecipientId = getGuidFromObject(row["RecipientId"]),
                        RecipientRoleId = getGuidFromObject(row["RecipientRoleId"]),
                        State = getIntFromObject(row["State"]),
                        CreatedDate = getDateTimeFromObject(row["CreatedDate"]),
                        NewInformationReceived = GetBoolValue(row["NewInformationReceived"]),
                        ReactionTimeEscalated = GetBoolValue(row["ReactionTimeEscalated"]),
                        SolutionTimeEscalated = GetBoolValue(row["SolutionTimeEscalated"]),
                        IsIncident = isIncident,
                        ActivityType = activityType
                    });
                }

                return entries;
            }
            catch (Exception E)
            {
                LogException(E);
                return new List<TicketOverviewEntry>();
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private static string ResolveTicketOverviewActivityType(
            Guid ticketEoid,
            Guid incidentEoid,
            Guid alertEoid,
            Guid problemEoid,
            Guid serviceRequestEoid,
            Guid taskEoid,
            Guid changeEoid)
        {
            if (incidentEoid != Guid.Empty)
                return "SPSActivityTypeIncident";
            if (ticketEoid != Guid.Empty)
                return "SPSActivityTypeTicket";
            if (alertEoid != Guid.Empty)
                return "SPSActivityTypeAlert";
            if (problemEoid != Guid.Empty)
                return "SPSActivityTypeGroupTicket";
            if (serviceRequestEoid != Guid.Empty)
                return "SPSActivityTypeServiceRequest";
            if (taskEoid != Guid.Empty)
                return "SPSActivityTypeBase";
            if (changeEoid != Guid.Empty)
                return "SVMChangeRequestType";

            return null;
        }

        private async Task<string> BuildTicketOverviewFilterAsync(
            string sid,
            bool useRoleScope,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues)
        {
            var filter = BuildTicketOverviewBaseFilter(queueoption, queues);

            if (!useRoleScope)
            {
                filter += $" AND Initiator.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'";
                return filter;
            }

            var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
            return BuildTicketOverviewFilterForRoleIds(roleIds, filter);
        }

        private string BuildTicketOverviewBaseFilter(int queueoption, List<cApiM42TicketQueueInfo> queues)
        {
            var filter = "T(SPSCommonClassBase).State <> 204";

            if (ticketAndServiceRequestEnabled())
            {
                filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" +
                          " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" +
                          " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)";
            }
            else
            {
                filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)";
            }

            filter = AppendQueueFilter(filter, queueoption, queues);
            return filter;
        }

        private string BuildTicketOverviewFilterForRoleIds(
            IEnumerable<Guid> roleIds,
            string baseFilter = null,
            int queueoption = 0,
            List<cApiM42TicketQueueInfo> queues = null)
        {
            var filter = string.IsNullOrWhiteSpace(baseFilter)
                ? BuildTicketOverviewBaseFilter(queueoption, queues)
                : baseFilter;

            var roleIdList = (roleIds ?? Enumerable.Empty<Guid>())
                .Where(id => id != Guid.Empty)
                .Distinct()
                .Select(id => $"'{id}'")
                .ToList();

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

            filter += $" AND RecipientRole.T(SPSSecurityClassRole).ID IN ({string.Join(", ", roleIdList)})";
            return filter;
        }

        private string AppendQueueFilter(string filter, int queueoption, List<cApiM42TicketQueueInfo> queues)
        {
            if (queues != null && queues.Count > 0)
            {
                var escapedNames = queues
                    .Select(q => $"'{Escape(q.QueueName)}'")
                    .ToList();
                var escapedIds = queues
                    .Select(q => $"'{Escape(q.QueueID.ToString())}'")
                    .ToList();

                string nameList = string.Join(", ", escapedNames);
                string idList = string.Join(", ", escapedIds);

                switch (queueoption)
                {
                    case 1:
                        filter +=
                            $" AND (" +
                                "Queue IS NULL" +
                                $" OR Queue.Name IN ({nameList})" +
                                $" OR Queue.ID   IN ({idList})" +
                            ")";
                        break;
                    case 2:
                        filter +=
                            $" AND (" +
                                "Queue IS NOT NULL" +
                                $" AND (Queue.Name IN ({nameList})" +
                                $"      OR Queue.ID   IN ({idList}))" +
                            ")";
                        break;
                    case 3:
                        filter += " AND Queue IS NULL";
                        break;
                    default:
                        break;
                }
            }
            else if (queueoption == 3)
            {
                filter += " AND Queue IS NULL";
            }

            return filter;
        }

        private async Task<List<Guid>> ResolveTicketOverviewRoleIdsAsync(string sid, IEnumerable<Guid> roleGuids)
        {
            var roleIds = (roleGuids ?? Enumerable.Empty<Guid>())
                .Where(id => id != Guid.Empty)
                .Distinct()
                .ToList();

            if (roleIds.Count > 0)
                return roleIds;

            if (string.IsNullOrWhiteSpace(sid))
                return new List<Guid>();

            var userId = getUserBySid(sid);
            if (userId == Guid.Empty)
                return new List<Guid>();

            var roles = await getRoleMembershipById(userId) ?? new List<M42Role>();
            return roles
                .Where(role => role != null && role.Id != Guid.Empty)
                .Select(role => role.Id)
                .Distinct()
                .ToList();
        }

        private static bool MatchesTicketOverviewKey(TicketOverviewEntry entry, string key)
        {
            if (entry == null || string.IsNullOrWhiteSpace(key))
                return false;

            var isTicket = !entry.IsIncident;
            var hasPerson = entry.RecipientId != Guid.Empty;
            var isAssigned = hasPerson || entry.RecipientRoleId != Guid.Empty;
            var isCritical = entry.ReactionTimeEscalated || entry.SolutionTimeEscalated;
            var isNew = entry.State == 200;
            var isActive = entry.State == 201 || entry.State == 202 || entry.State == 203;

            switch (key.Trim())
            {
                case "TicketsNew":
                    return isTicket && isAssigned && isNew;
                case "TicketsActive":
                    return isTicket && isAssigned && isActive;
                case "TicketsCritical":
                    return isTicket && isAssigned && isCritical;
                case "TicketsNewInfo":
                    return isTicket && isAssigned && entry.NewInformationReceived;
                case "IncidentNew":
                    return entry.IsIncident && isNew;
                case "IncidentActive":
                    return entry.IsIncident && isActive;
                case "IncidentCritical":
                    return entry.IsIncident && isCritical;
                case "IncidentNewInfo":
                    return entry.IsIncident && entry.NewInformationReceived;
                case "UnassignedTickets":
                    return !hasPerson && isNew;
                case "UnassignedTicketsCritical":
                    return !hasPerson && isCritical;
                default:
                    return false;
            }
        }

        private static bool GetBoolValue(object value)
        {
            if (value == null || value is DBNull)
                return false;
            if (value is bool boolValue)
                return boolValue;
            if (value is int intValue)
                return intValue != 0;
            if (value is long longValue)
                return longValue != 0;

            if (bool.TryParse(value.ToString(), out var parsedBool))
                return parsedBool;
            if (int.TryParse(value.ToString(), out var parsedInt))
                return parsedInt != 0;

            return false;
        }

        private static string ConvertM42State(int state)
        {
            switch (state)
            {
                case 200:
                    return "New";
                case 201:
                case 202:
                    return "InProgress";
                case 203:
                    return "OnHold";
                case 204:
                    return "Closed";
                default:
                    return "Unknown";
            }
        }
        private (int, string) GetActivityState(Guid activityEOId)
        {
            var tbl3 = FragmentRequestBase.SimpleLoad(SPSCommonClassBaseID,
                "State.Value as State, State.DisplayString as StateDisp", $"[Expression-ObjectID] = '{activityEOId}'");
            if (tbl3?.Rows == null || tbl3.Rows.Count <= 0)
            {
                LogEntry($"SPSCommonClassBase fragment not found: ClassId={SPSCommonClassBaseID}, ObjectId={activityEOId}", LogLevels.Debug);
                return (0, "");
            }
            var state = getIntFromObject(tbl3.Rows[0]["State"]);
            var stateDisp = getStringFromObject(tbl3.Rows[0]["StateDisp"]);
            return (state, stateDisp);
        }
        private string Escape(string input)
        {
            return input?.Replace("'", "''");
        }
        private string GetActivityFilter(
            string userSid,
            int hours,
            bool ticketAndServiceRequestEnabled,
            int queueoption,
            List<cApiM42TicketQueueInfo> queues
        )
        {
            // Baseline-Filter auf User und Datum
            var filter = $"Initiator.Accounts.T(SPSAccountClassAd).Sid = '{Escape(userSid)}'";

            string fStartDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow.AddHours(-hours), true);
            string fEndDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow, true);

            // Incident vs. Ticket/ServiceRequest
            if (ticketAndServiceRequestEnabled)
            {
                filter +=
                    " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" +
                    " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" +
                    " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)";
            }
            else
            {
                filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)";
            }

            // Offene bzw. kürzlich geschlossene Objekte
            filter +=
                " AND (T(SPSCommonClassBase).State <> 204" +
                $" OR (ClosedDate > {fStartDate} AND ClosedDate < {fEndDate}))";

            // Queue-Filter nur, wenn tatsächlich Queues übergeben wurden
            if (queues != null && queues.Count > 0)
            {
                // URL-escaping für Name und ID
                var escapedNames = queues
                    .Select(q => $"'{Escape(q.QueueName)}'")
                    .ToList();
                var escapedIds = queues
                    .Select(q => $"'{Escape(q.QueueID.ToString())}'")
                    .ToList();

                string nameList = string.Join(", ", escapedNames);
                string idList = string.Join(", ", escapedIds);

                switch (queueoption)
                {
                    // 1 = entweder keine Queue oder eine der übergebenen Queues (Name oder ID)
                    case 1:
                        filter +=
                            $" AND (" +
                                "Queue IS NULL" +
                                $" OR Queue.Name IN ({nameList})" +
                                $" OR Queue.ID   IN ({idList})" +
                            ")";
                        break;

                    // 2 = nur die übergebenen Queues (Name oder ID)
                    case 2:
                        filter +=
                            $" AND (" +
                                "Queue IS NOT NULL" +
                                $" AND (Queue.Name IN ({nameList})" +
                                $"      OR Queue.ID   IN ({idList}))" +
                            ")";
                        break;

                    // 3 = nur Objekte ohne Queue
                    case 3:
                        filter += " AND Queue IS NULL";
                        break;

                    // 0 oder andere = keine zusätzliche Einschränkung
                    default:
                        break;
                }
            }
            else if (queueoption == 3)
            {
                // Ausnahme: wenn keine Queues übergeben, aber Option 3 = nur ohne Queue
                filter += " AND Queue IS NULL";
            }

            return filter;
        }


        internal async Task<DirectLink> getDirectLinkCreateTicket(string sid, string assetname)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);

                // get global settings
                bool TicketAndServiceRequestEnabled = ticketAndServiceRequestEnabled();

                var user = getUserBySid(sid);
                var asset = getAssetByName(assetname);
                dynamic presetParamsDyn = new ExpandoObject();

                presetParamsDyn.SPSActivityClassBase = new ExpandoObject();
                presetParamsDyn.SPSActivityClassBase.Subject = placeHolderSubject;
                presetParamsDyn.SPSActivityClassBase.DescriptionHTML = placeHolderDescription;
                if (user != Guid.Empty)
                    presetParamsDyn.SPSActivityClassBase.Initiator = user;
                if (asset != null)
                {
                    presetParamsDyn.SPSActivityClassIncident = new ExpandoObject();
                    presetParamsDyn.SPSActivityClassIncident.Asset = asset.Id;
                }
                var type = TicketAndServiceRequestEnabled ? "SPSActivityTypeTicket" : "SPSActivityTypeIncident";
                var presetParams = HttpUtility.UrlEncode(JsonConvert.SerializeObject(presetParamsDyn));
                var directLink = string.Format(TicketDirectLinkCreateTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, type, presetParams);
                return new DirectLink()
                {
                    Link = directLink,
                    DescriptionParameter = placeHolderDescription,
                    SubjectParameter = placeHolderSubject
                };
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private static bool ticketAndServiceRequestEnabled()
        {
            var configProvider = F4SDM42WebApiController.defaultInstance._globalConfigurationProvider;

            // using reflection because signature of "ReloadSettings" changed in ESM v12
            var method = configProvider.GetType().GetMethod("ReloadSettings");

            if (method != null)
            {
                var defaultParameters = method.GetParameters().Select(p => p.HasDefaultValue ? p.DefaultValue : null).ToArray();
                method.Invoke(configProvider, defaultParameters);
            }

            return configProvider.ServiceDeskConfiguration.TicketAndServiceRequestEnabled;
        }

        internal async Task<List<cF4SDTicket>> getTicketDetails(List<Guid> ticketObjectIds)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);

                var Filter = AsqlHelper.BuildInCondition("[Expression-ObjectId]", ticketObjectIds);
                LogEntry($"ASql Filter: {Filter}");

                var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
                    "[Expression-ObjectId] as EOID" +
                    ", TicketNumber" +
                    ", Subject" +
                    ", Category.Name as Category" +
                    ", Category as CategoryId" +
                    ", CreatedDate, ClosedDate" +
                    ", Initiator as InitiatorId" +
                    ", Initiator.LastName + ISNULL(', ' + Initiator.FirstName, '') as Initiator" +
                    ", DescriptionHTML" +
                    ", Impact" +
                    ", Impact.DisplayString as ImpactDisp" +
                    ", Urgency" +
                    ", Urgency.DisplayString as UrgencyDisp" +
                    ", Priority" +
                    ", Priority.Description as PrioDisp" +
                    ", SolutionHTML", Filter);

                if (tbl == null || tbl.Rows == null)
                {
                    LogEntry($"No activity entry list found with [Expression-ObjectId]='{string.Join(", ", ticketObjectIds)}'", LogLevels.Warning);
                    return null;
                }

                var tickets = new List<cF4SDTicket>();

                foreach (DataRow Entry in tbl.Rows)
                {
                    var ActivityEOID = getGuidFromObject(Entry["EOID"]);
                    if (ActivityEOID == Guid.Empty)
                    {
                        LogEntry($"No expression object id found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    var InitiatorId = getGuidFromObject(Entry["InitiatorId"]);
                    var Initiator = getStringFromObject(Entry["Initiator"]);
                    if (InitiatorId == Guid.Empty)
                    {
                        LogEntry($"No Initiator id found for activity entry", LogLevels.Warning);
                    }

                    var TicketNumber = getStringFromObject(Entry["TicketNumber"]);
                    if (string.IsNullOrEmpty(TicketNumber))
                    {
                        LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    var CategoryId = getGuidFromObject(Entry["CategoryId"]);
                    if (CategoryId == Guid.Empty)
                    {
                        LogEntry($"No Initiator object id found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    var Category = getStringFromObject(Entry["Category"]);
                    if (string.IsNullOrEmpty(Category))
                    {
                        LogEntry($"No Category found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    var CreatedDate = getDateTimeFromObject(Entry["CreatedDate"]);
                    if (CreatedDate == DateTime.MinValue)
                    {
                        LogEntry($"No CreationDate found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    var ClosedDate = getDateTimeFromObject(Entry["ClosedDate"]);
                    var DescriptionHtml = getStringFromObject(Entry["DescriptionHTML"]);
                    var Description = DescriptionHtml != string.Empty ? Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(DescriptionHtml) : string.Empty;

                    var SolutionHtml = getStringFromObject(Entry["SolutionHTML"]);
                    var Solution = SolutionHtml != string.Empty ? Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(SolutionHtml) : string.Empty;

                    var Subject = getStringFromObject(Entry["Subject"]);
                    if (string.IsNullOrEmpty(Subject))
                    {
                        LogEntry($"No Subject found for activity entry", LogLevels.Warning);
                        continue;
                    }

                    var PriorityId = getIntFromObject(Entry["Priority"]);
                    var Priority = getStringFromObject(Entry["PrioDisp"]);

                    var UrgencyId = getIntFromObject(Entry["Urgency"]);
                    var Urgency = getStringFromObject(Entry["UrgencyDisp"]);

                    var ImpactId = getIntFromObject(Entry["Impact"]);
                    var Impact = getStringFromObject(Entry["ImpactDisp"]);

                    LogEntry($"get category information: {Filter}");
                    Filter = string.Format("Recursive(Children).T(SPSScCategoryClassBase).Id='{0}'", CategoryId);
                    var tbl1 = FragmentRequestBase.SimpleLoad(SPSScCategoryClassBaseID, "Name", Filter);
                    if (tbl1?.Rows == null || tbl1.Rows.Count <= 0)
                    {
                        LogEntry($"No Category entry list found with Initiator='{CategoryId}'", LogLevels.Warning);
                        return null;
                    }

                    var CategoryHierarchical = string.Join(" > ", tbl1.Rows.Cast<DataRow>().Reverse().Select(row => getStringFromObject(row["Name"])));

                    var tbl2 = FragmentRequestBase.SimpleLoad(SPSActivityClassIncidentID, "EntryBy as EntryBy, EntryBy.DisplayString as EntryByDisp, COALESCE(AssetAffected, Asset) as Asset, COALESCE(AssetAffected.T(SPSComputerClassAD).Domain.NT4Name, Asset.T(SPSComputerClassAD).Domain.NT4Name) as AssetDomain, COALESCE(COALESCE(AssetAffected.T(SPSComputerClassBase).Name, AssetAffected.T(SPSAssetClassSIMCard).PhoneNumber, AssetAffected.Name, AssetAffected.objectid), COALESCE(Asset.T(SPSComputerClassBase).Name, Asset.T(SPSAssetClassSIMCard).PhoneNumber, Asset.Name, Asset.objectid)) as AssetName", $"[Expression-ObjectID] = '{ActivityEOID}'");

                    if (tbl2?.Rows == null || tbl2.Rows.Count <= 0)
                    {
                        LogEntry($"SPSActivityClassIncident fragment not found: ClassId={SPSActivityClassIncidentID}, ObjectId={ActivityEOID}", LogLevels.Debug);
                        continue;
                    }

                    var AssetId = getGuidFromObject(tbl2.Rows[0]["Asset"]);
                    var Asset = getStringFromObject(tbl2.Rows[0]["AssetName"]);
                    var AssetDomain = getStringFromObject(tbl2.Rows[0]["AssetDomain"]);
                    var EntryBy = getIntFromObject(tbl2.Rows[0]["EntryBy"]);
                    var EntryByDisp = getStringFromObject(tbl2.Rows[0]["EntryByDisp"]);

                    M42Asset AssetObject = null;
                    if (AssetId != Guid.Empty)
                        AssetObject = getAssetsByAsql($"ID = '{AssetId}'").FirstOrDefault();

                    var tbl3 = FragmentRequestBase.SimpleLoad(SPSCommonClassBaseID, "State, State.DisplayString as StateDisp, SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.TypeID) as CIName", $"[Expression-ObjectID] = '{ActivityEOID}'");
                    if (tbl3?.Rows == null || tbl3.Rows.Count <= 0)
                    {
                        LogEntry($"SPSCommonClassBase fragment not found: ClassId={SPSCommonClassBaseID}, ObjectId={ActivityEOID}", LogLevels.Debug);
                        continue;
                    }

                    var State = getIntFromObject(tbl3.Rows[0]["State"]);
                    var StateDisp = getStringFromObject(tbl3.Rows[0]["StateDisp"]);
                    var CIName = getStringFromObject(tbl3.Rows[0]["CIName"]);

                    dynamic viewOptionsDyn = new ExpandoObject();
                    viewOptionsDyn.objectId = ActivityEOID;
                    viewOptionsDyn.type = CIName;
                    viewOptionsDyn.viewType = "action";
                    viewOptionsDyn.actionId = TicketCloseActionId;
                    var viewOptions = HttpUtility.UrlEncode(JsonConvert.SerializeObject(viewOptionsDyn));

                    var ticketDirectLinkPreview = string.Format(TicketDirectLinkPreviewTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID);
                    var ticketDirectLinkEdit = string.Format(TicketDirectLinkEditTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID);
                    var ticketDirectLinkClose = string.Format(IncidentDirectLinkCloseTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, ActivityEOID, viewOptions);

                    LogEntry($"Activity found: ObjectID={ActivityEOID}, TicketNumber={TicketNumber}, Subject={Subject}, State={State}", LogLevels.Debug);
                    var ticket = new cF4SDTicket()
                    {
                        TicketObjectId = ActivityEOID,
                        Name = TicketNumber,
                        CIName = CIName,
                        StatusId = State,
                        Status = StateDisp,
                        Summary = Subject,
                        AffectedUserId = InitiatorId,
                        AffectedUser = Initiator,
                        CreationDate = CreatedDate,
                        ClosingDate = ClosedDate,
                        DescriptionHtml = DescriptionHtml,
                        Description = Description,
                        SolutionHtml = SolutionHtml,
                        Solution = Solution,
                        Priority = Priority,
                        PriorityId = PriorityId,
                        CreationSourceId = EntryBy,
                        CreationSource = EntryByDisp,
                        CategoryHierarchical = CategoryHierarchical,
                        CategoryId = CategoryId,
                        Category = Category,
                        DirectLinkPreview = ticketDirectLinkPreview,
                        Urgency = Urgency,
                        UrgencyId = UrgencyId,
                        Impact = Impact,
                        ImpactId = ImpactId
                        //JournalItems = GetJournalEntriesByEOID(ActivityEOID)
                    };

                    if (AssetObject != null)
                    {
                        ticket.AssetId = AssetId;
                        ticket.AssetName = Asset;
                        ticket.AssetDomain = AssetDomain;
                        ticket.AssetCIId = AssetObject.CIId;
                        ticket.AssetCIName = AssetObject.CIName;
                        ticket.AssetSKUAssetGroupId = AssetObject.SKUAssetGroupId;
                        ticket.AssetSKUAssetGroup = AssetObject.SKUAssetGroup;
                        ticket.AssetSKUTypeId = AssetObject.SKUTypeId;
                        ticket.AssetSKUType = AssetObject.SKUType;
                    }

                    if (ticket.StatusId != 204)
                        ticket.DirectLinkEdit = ticketDirectLinkEdit;

                    if ((CIName.ToUpper() == "SPSACTIVITYTYPEINCIDENT" || CIName.ToUpper() == "SPSACTIVITYTYPESERVICEREQUEST") && ticket.StatusId != 204)
                        ticket.DirectLinkClose = ticketDirectLinkClose;

                    tickets.Add(ticket);
                }

                tickets = tickets.OrderBy(x => x.CreationDate).ToList();
                tickets.Reverse();
                return tickets;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        internal async Task<List<cTicketJournalItem>> GetJournalEntries(Guid activityEOID)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                List<cTicketJournalItem> journalEntries = new List<cTicketJournalItem>();
                var entries = F4SDM42WebApiController.defaultInstance._journalService.GetJournalList(activityEOID, false, 0, 50, "<a href=\"#\" class=\"mx-object-journal--link {0} {1}\">{2}</a>", 120);
                LogEntry($"{entries.Length} Journal entries found for ObjectID={activityEOID}", LogLevels.Debug);
                for (int i = 0; i < entries.Length; i++)
                {
                    Matrix42.Contracts.Platform.Data.JournalEntryInfo item = entries[i];
                    LogEntry($"Journal entry {i + 1}/{entries.Length}: ID={item.Id}, CreatedDate={item.CreatedDate}, CreatedBy={item.Creator}, Header={item.Header}", LogLevels.Debug);
                    journalEntries.Add(new cTicketJournalItem()
                    {
                        JournalId = item.Id,
                        ActivityObjectId = activityEOID,
                        CreatedBy = item.Creator,
                        CreationDate = (DateTime)item.CreatedDate,
                        DescriptionHtml = item.Text,
                        Description = Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(item.Text),
                        Header = item.Header,
                        IsVisibleForUser = item.VisibleInPortal
                    });
                }
                return journalEntries;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        internal async Task<bool> updateActivitySolution(Guid objectId, string solutionHtml)
        {

            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                if (objectId == Guid.Empty)
                    return false;

                var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, "ID", $"[Expression-ObjectID] = '{objectId}'");
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                {
                    LogEntry($"SPSActivityClassBase fragment not found: ClassId={SPSActivityClassBaseID}, ObjectId={objectId}", LogLevels.Debug);
                    return false;
                }

                var fragmentId = getGuidFromObject(tbl.Rows[0]["ID"]);
                SPSFragment activityFragment = ObjectBroker.GetFragment(SPSActivityClassBaseID, fragmentId);
                activityFragment["SolutionHTML"] = solutionHtml;
                ObjectBroker.UpdateFragment(SPSActivityClassBaseID, activityFragment);
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private List<M42Asset> getAssetsByAsql(string Filter)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // get SPSAssetClassBase
                if (string.IsNullOrEmpty(Filter))
                    return null;
                List<M42Asset> assets = new List<M42Asset>();
                LogEntry($"ASql Filter: {Filter}");
                var tbl = FragmentRequestBase.SimpleLoad(SPSAssetClassBaseID
                    , "Id" +
                    ", COALESCE(T(SPSComputerClassBase).Name, T(SPSAssetClassSIMCard).PhoneNumber, Name) as AssetName" +
                    ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSCommonClassBase).TypeID) as CIName" +
                    ", SUBQUERY(BasicSchemaObjectType AS t, t.Id, t.ID=base.T(SPSCommonClassBase).TypeID) as CIId" +
                    ", SKU.Type as SKUTypeId" +
                    ", SKU.Type.DisplayString as SKUType" +
                    ", SKU.Type.AssetGroup as SKUAssetGroupId" +
                    ", SUBQUERY(SPSAssetPickupTypeCategory AS ag, ag.DisplayString, ag.Value=base.SKU.Type.AssetGroup) as SKUAssetGroup" +
                    ", T(SPSComputerClassAD).Domain.NT4Name as DomainName" +
                    ", T(SPSComputerClassAD).Sid as Sid"
                    , Filter);
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                {
                    LogEntry($"SPSAssetClassBase fragment not found: ClassId={SPSActivityClassIncidentID}, Filter={Filter}", LogLevels.Debug);
                    return null;
                }
                else
                {
                    foreach (DataRow row in tbl.Rows)
                    {
                        var asset = new M42Asset()
                        {
                            Id = getGuidFromObject(row["Id"]),
                            Name = getStringFromObject(row["AssetName"]),
                            CIName = getStringFromObject(row["CIName"]),
                            CIId = getGuidFromObject(row["CIId"]),
                            SKUTypeId = getIntFromObject(row["SKUTypeId"]),
                            SKUType = getStringFromObject(row["SKUType"]),
                            SKUAssetGroupId = getIntFromObject(row["SKUAssetGroupId"]),
                            SKUAssetGroup = getStringFromObject(row["SKUAssetGroup"]),
                            DomainName = getStringFromObject(row["DomainName"]),
                            Sid = getStringFromObject(row["Sid"]),
                        };
                        assets.Add(asset);
                        LogEntry($"Asset found: Id={asset.Id}, Name={asset.Name}, CIName={asset.CIName}", LogLevels.Debug);
                    }
                }
                return assets;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private M42Asset getAssetByName(string assetname)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // get SPSAssetClassBase
                if (string.IsNullOrEmpty(assetname))
                    return null;
                var Filter = string.Format("T(SPSComputerClassBase).Name = '{0}' OR T(SPSAssetClassSIMCard).PhoneNumber = '{0}' OR Name = '{0}'", assetname);
                return getAssetsByAsql(Filter)?.First();
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }
        private Guid getUserBySid(string sid)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                string sidPattern = @"^S-\d-\d+-(\d+-){1,14}\d+$";
                if (string.IsNullOrEmpty(sid) || !Regex.IsMatch(sid, sidPattern))
                {
                    LogEntry($"Invalid or empty SID: '{sid}'", LogLevels.Warning);
                    return Guid.Empty;
                }

                var users = getUsersByAsql(string.Format("Accounts.T(SPSAccountClassAD).Sid = '{0}' OR PrimaryAccount.T(SPSAccountClassAD).Sid = '{0}'", sid));
                return users != null ? users.First().Id : Guid.Empty;
            }
            catch (Exception E)
            {
                LogException(E);
                return Guid.Empty;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }


        private List<M42User> getUsersByAsql(string asqlFilter)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var Persons = new List<M42User>();


                LogEntry($"ASql Filter: {asqlFilter}");

                var tbl = FragmentRequestBase.SimpleLoad(SPSUserClassBaseID,
                    "Id as Id" +
                    ", [Expression-ObjectID] as UserEOID" +
                    ", T(SPSCommonClassBase).State as State" +
                    ", LastName + ISNULL(', ' + FirstName,'') as Name" +
                    ", PreferredMailCulture.Locale as Locale" +
                    ", T(SPSCommonClassBase).State.DisplayString as StateDisp",
                    asqlFilter);
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                {
                    LogEntry($"no user entry list found with asql='{asqlFilter}'", LogLevels.Warning);
                    return null;
                }
                foreach (DataRow Entry in tbl.Rows)
                {
                    // get the id
                    var UserId = getGuidFromObject(Entry["ID"]);
                    if (UserId == Guid.Empty)
                    {
                        LogEntry($"no id found for user entry", LogLevels.Warning);
                        continue;
                    }
                    var UserEOID = getGuidFromObject(Entry["UserEOID"]);
                    if (UserEOID == Guid.Empty)
                    {
                        LogEntry($"no expression object id found for user entry", LogLevels.Warning);
                        continue;
                    }
                    var Name = getStringFromObject(Entry["Name"]);
                    if (string.IsNullOrEmpty(Name))
                    {
                        LogEntry($"no Name found for entry", LogLevels.Debug);
                    }
                    var Locale = getStringFromObject(Entry["Locale"]);
                    if (string.IsNullOrEmpty(Locale))
                    {
                        LogEntry($"no locale found for entry", LogLevels.Debug);
                    }
                    var StateDisp = getStringFromObject(Entry["StateDisp"]);
                    if (string.IsNullOrEmpty(StateDisp))
                    {
                        LogEntry($"no statedisp found for entry", LogLevels.Debug);
                    }



                    var State = getIntFromObject(Entry["State"]);
                    LogEntry($"User found: ID={UserId}, ObjectID={UserEOID}, State={State}", LogLevels.Debug);
                    if (State != 2023)
                        LogEntry($"User is not active", LogLevels.Debug);
                    Persons.Add(new M42User()
                    {
                        Id = UserId,
                        ObjectId = UserEOID,
                        Name = Name,
                        State = State,
                        Locale = Locale,
                        LanguageId = new CultureInfo(Locale).LCID % 1024,
                        StateDisp = StateDisp
                    });
                }
                return Persons;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }
        private List<M42Account> getAccountsByAsql(string asqlFilter)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var result = new List<M42Account>();


                LogEntry($"ASql Filter: {asqlFilter}");

                var tbl = FragmentRequestBase.SimpleLoad(SPSAccountClassBaseID,
                    "Id as Id" +
                    ", [Expression-ObjectID] as EOID" +
                    ", T(SPSCommonClassBase).State as State" +
                    ", T(SPSAccountClassAD).Sid as Sid" +
                    ", T(SPSAccountClassAD).UserPrincipalName as UserPrincipalName" +
                    ", Owner ",
                    asqlFilter);
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                {
                    LogEntry($"no account entry list found with asql='{asqlFilter}'", LogLevels.Warning);
                    return null;
                }
                foreach (DataRow Entry in tbl.Rows)
                {
                    // get the id
                    var Id = getGuidFromObject(Entry["ID"]);
                    if (Id == Guid.Empty)
                    {
                        LogEntry($"no id found for entry", LogLevels.Warning);
                        continue;
                    }
                    var EOID = getGuidFromObject(Entry["EOID"]);
                    if (EOID == Guid.Empty)
                    {
                        LogEntry($"no expression object id found for entry", LogLevels.Warning);
                        continue;
                    }
                    var Owner = getGuidFromObject(Entry["Owner"]);
                    if (Owner == Guid.Empty)
                    {
                        LogEntry($"no owner found for entry", LogLevels.Warning);
                    }
                    var Sid = getStringFromObject(Entry["Sid"]);
                    if (string.IsNullOrEmpty(Sid))
                    {
                        LogEntry($"no Sid found for entry", LogLevels.Debug);
                    }
                    var UserPrincipalName = getStringFromObject(Entry["UserPrincipalName"]);
                    if (string.IsNullOrEmpty(UserPrincipalName))
                    {
                        LogEntry($"no UserPrincipalName found for entry", LogLevels.Debug);
                    }


                    var State = getIntFromObject(Entry["State"]);
                    LogEntry($"Account found: ID={Id}, ObjectID={EOID}, State={State}", LogLevels.Debug);
                    result.Add(new M42Account()
                    {
                        Id = Id,
                        EOID = EOID,
                        State = State,
                        UserPrincipalName = UserPrincipalName,
                        Sid = Sid,
                        Owner = Owner
                    });
                }
                return result;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }
        internal HttpResponseMessage privGetLog(string download, int maxLines, HttpRequestMessage request, string filter)
        {
            var response = new HttpResponseMessage();



            if (download == "1")
            {
                var CM = MethodBase.GetCurrentMethod();
                LogMethodBegin(CM);
                try
                {
                    var logger = (DefaultLogger.Manager as cLogManagerFile);
                    var logFileName = logger.GetLogFileName();
                    MemoryStream memoryStream = new MemoryStream();

                    ZipArchive zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true);
                    string[] files = Directory.GetFiles(Path.GetDirectoryName(logFileName), $"*.log");
                    foreach (string fileName in files)
                    {
                        using (var tmpInStream = new FileStream(logFileName, FileMode.Open,
                            FileAccess.Read, FileShare.ReadWrite))
                        {
                            ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(Path.GetFileName(fileName));
                            using (Stream destination = zipArchiveEntry.Open())
                            {
                                tmpInStream.CopyTo(destination);
                            }
                        }
                    }
                    zipArchive.Dispose();
                    memoryStream.Position = 0L;
                    HttpResponseMessage httpResponseMessage = request.CreateResponse(HttpStatusCode.OK);
                    httpResponseMessage.Content = new StreamContent(memoryStream);
                    httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = Path.GetFileNameWithoutExtension(logFileName) + ".zip"
                    };
                    httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

                    return httpResponseMessage;
                }
                catch (Exception E)
                {
                    LogException(E);
                    return null;
                }
                finally
                {
                    LogMethodEnd(CM);
                }

            }
            else
            {
                var logger = (DefaultLogger.Manager as cLogManagerFile);
                var logFileName = logger.GetLogFileName();
                using (var inStream = new FileStream(logFileName, FileMode.Open,
                  FileAccess.Read, FileShare.ReadWrite))
                {
                    var lines = ReadLines(() => inStream,
                              Encoding.UTF8)
                        .ToList();
                    var sb = new StringBuilder();
                    sb.AppendLine("<html><body><table>");
                    var htmlLines = new List<string>();
                    foreach (var line in lines)
                    {
                        var lineSplit = line.Split('\t');
                        if (string.IsNullOrEmpty(filter) || !string.IsNullOrEmpty(filter) && lineSplit.Length > 4 && filter.ToLowerInvariant().Split(',').Contains(lineSplit[3].ToLowerInvariant()))
                            htmlLines.Add("<tr><td><nobr>" + string.Join("</nobr></td><td><nobr>", lineSplit) + "</nobr></td></tr>");
                    }
                    if (maxLines > 0)
                        htmlLines = htmlLines.Skip(Math.Max(0, htmlLines.Count() - maxLines)).ToList();
                    foreach (var line in htmlLines)
                    {
                        sb.AppendLine(line);
                    }
                    sb.AppendLine("</table></body></html>");

                    response.Content = new StringContent(sb.ToString());
                    response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html")
                    {
                        CharSet = Encoding.UTF8.HeaderName
                    };
                    response.Content.Headers.Add("CodePage", Encoding.UTF8.CodePage.ToString());
                    return response;
                }
            }
        }

        internal List<cM42LogEntry> privGetLog2()
        {
            var response = new HttpResponseMessage();
            {
                var logger = (DefaultLogger.Manager as cLogManagerFile);
                var logFileName = logger.GetLogFileName();
                using (var inStream = new FileStream(logFileName, FileMode.Open,
                  FileAccess.Read, FileShare.ReadWrite))
                {
                    var lines = ReadLines(() => inStream,
                              Encoding.UTF8)
                        .ToList();

                    var entries = new List<cM42LogEntry>();
                    DateTime dateValue;
                    for (int i = 0; i < lines.Count; i++)
                    {
                        string line = lines[i];
                        try
                        {
                            var lineSplit = line.Split('\t');
                            if (lineSplit.Length >= 5)
                            {
                                if (string.IsNullOrEmpty(lineSplit[0]))
                                {
                                    entries.Last().Message += Environment.NewLine + lineSplit[4];
                                    continue;
                                }
                                DateTime.TryParseExact(lineSplit[0], "yyyy-MM-dd HH:mm:ss:fffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateValue);
                                entries.Add(new cM42LogEntry()
                                {
                                    LineNumber = i + 1,
                                    date = dateValue,
                                    ProcessId = lineSplit[1],
                                    logLvl = lineSplit[3],
                                    Theme = lineSplit[2],
                                    Message = lineSplit[4],
                                });
                            }
                        }
                        catch (Exception)
                        {

                        }
                    }
                    return entries;
                }
            }
        }

        private int getIntFromObject(object o, int Default = 0)
        {
            try
            {
                if (o == null)
                    return Default;
                if (o is DBNull)
                    return Default;
                if (o is int @int)
                    return @int;
                if (o is long int1)
                    return (int)int1;
                if (int.TryParse(o.ToString(), out var r))
                    return r;
            }
            catch { }
            return Default;
        }

        private DateTime getDateTimeFromObject(object o)
        {
            try
            {
                if (o == null)
                    return DateTime.MinValue;
                if (o is DBNull)
                    return DateTime.MinValue;
                if (o is DateTime @dateTime)
                    return @dateTime;
                if (o is string dstr && DateTime.TryParse(o.ToString(), out var r))
                    return r;
            }
            catch { }
            return DateTime.MinValue;
        }

        private string getStringFromObject(object o, string Default = "")
        {
            try
            {
                if (o == null)
                    return Default;
                if (o is DBNull)
                    return Default;
                return o.ToString();
            }
            catch { }
            return Default;
        }

        private Guid getGuidFromObject(object o)
        {
            try
            {
                if (o == null)
                    return Guid.Empty;
                if (o is DBNull)
                    return Guid.Empty;
                if (o is Guid G)
                    return G;
                if (Guid.TryParse(o.ToString(), out var r))
                    return r;
            }
            catch { }
            return Guid.Empty;
        }

        internal cM42LogEntry privGetLog2(int id)
        {
            return privGetLog2().Find(x => x.LineNumber == id);
        }

        internal async Task<List<M42Role>> getRoleMembershipById(Guid UserId)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            var RoleMemberships = new List<M42Role>();
            try
            {
                await Task.Delay(0);
                var asqlFilter = string.Format("Members.Id = '{0}'", UserId.ToString());
                LogEntry($"ASql Filter: {asqlFilter}");

                FragmentRequestBase fragmentRequestBase = new FragmentRequestBase(SPSSecurityClassRole, ColumnSelectOption.List, "Id as Id" +
                    ", Name as Name");
                fragmentRequestBase.Where = asqlFilter;
                var langExt = new FragmentRequestExtensionLanguage();
                langExt.AddCultureRequest(new CultureInfo("en"));
                fragmentRequestBase.AddExtension(langExt);

                using (SPSTransactionScope sPSTransactionScope = new SPSTransactionScope(SPSTransactionScopeOption.Required, new SPSTransactionOptions(IsolationLevel.ReadUncommitted)))
                {
                    fragmentRequestBase.Load();
                    sPSTransactionScope.Complete();
                }

                // if requested language is already "en" use the english table
                var tbl = fragmentRequestBase.DataSet.Tables[1];
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                    tbl = fragmentRequestBase.DataSet.Tables[0];
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                {
                    LogEntry($"no role membership found with asql='{asqlFilter}'", LogLevels.Debug);
                    return RoleMemberships;
                }
                foreach (DataRow Entry in tbl.Rows)
                {
                    if (Entry.Table.Columns.Contains("LCID") && (int)Entry["LCID"] != 9)
                        continue;
                    // get the id
                    var IDColumn = Entry.Table.Columns.Contains("Owner") ? "Owner" : "ID";
                    var RoleId = getGuidFromObject(Entry[IDColumn]);
                    if (RoleId == Guid.Empty)
                    {
                        LogEntry($"no id found for role entry", LogLevels.Warning);
                        continue;
                    }
                    var Name = getStringFromObject(Entry["Name"]);
                    if (string.IsNullOrEmpty(Name))
                    {
                        LogEntry($"no Name found for entry", LogLevels.Debug);
                    }
                    LogEntry($"Role Membership found: ID={RoleId}, Name={Name}", LogLevels.Debug);
                    RoleMemberships.Add(new M42Role() { Id = RoleId, Name = Name });
                }
                return RoleMemberships;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /*
        internal async Task<List<M42Role>> getRoleMembershipById(Guid UserId)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            var RoleMemberships = new List<M42Role>();
            try
            {
                await Task.Delay(0);
                var asqlFilter = string.Format("Members.Id = '{0}'", UserId.ToString());
                LogEntry($"ASql Filter: {asqlFilter}");
                var tbl = FragmentRequestBase.SimpleLoad(SPSSecurityClassRole,
                    "Id as Id" +
                    ", Name as Name",
                    asqlFilter);
                if (tbl?.Rows == null || tbl.Rows.Count <= 0)
                {
                    LogEntry($"no role membership found with asql='{asqlFilter}'", LogLevels.Debug);
                    return RoleMemberships;
                }
                foreach (DataRow Entry in tbl.Rows)
                {
                    // get the id
                    var RoleId = getGuidFromObject(Entry["ID"]);
                    if (RoleId == Guid.Empty)
                    {
                        LogEntry($"no id found for role entry", LogLevels.Warning);
                        continue;
                    }
                    var Name = getStringFromObject(Entry["Name"]);
                    if (string.IsNullOrEmpty(Name))
                    {
                        LogEntry($"no Name found for entry", LogLevels.Debug);
                    }
                    LogEntry($"Role Membership found: ID={RoleId}, Name={Name}", LogLevels.Debug);
                    RoleMemberships.Add(new M42Role() { Id = RoleId, Name = Name });
                }
                return RoleMemberships;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }
        */
        internal async Task<object> UserPermissionsInfo(string filter)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            var RoleMemberships = new List<M42Role>();
            try
            {
                await Task.Delay(0);

                var retVal = new UserPermissionsInfo();
                List<M42User> m42Users = getUsersByAsql(filter);
                if (m42Users != null && m42Users.Count == 1)
                {
                    retVal.User = m42Users[0];


                    var user = F4SDM42WebApiController.defaultInstance._userProfile.GetInteractiveUserInfo(retVal.User.Id);
                    if (user != null)
                    {
                        retVal.User.Id = user.Id;
                        retVal.User.Currency = user.Currency;
                        retVal.User.Email = user.Email;
                        retVal.User.FirstName = user.FirstName;
                        retVal.User.LastName = user.LastName;
                        retVal.User.Phone = user.Phone;
                        retVal.User.Photo = user.Photo;
                    }

                    retVal.Roles = await getRoleMembershipById(retVal.User.Id);
                    retVal.User.IsAdmin = retVal.Roles?.Where(i => i.Id == Administrators).FirstOrDefault() != null;
                    return retVal;
                }
                return null;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private enum eM42EntryBy
        {
            Phone = 0,
            Email = 1,
            Portal = 2,
            CatalogOrder = 3,
            EventTrigger = 4,
            Feedback = 5,
            VisualSupportAgent = 6,
            ComplianceAlert = 7,
            F4SD = 20010,
        }

        public class DirectLink
        {
            public string Link { get; set; }
            public string SubjectParameter { get; set; }
            public string DescriptionParameter { get; set; }
        }

        public class M42Asset
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public string CIName { get; set; }
            public string SKUType { get; set; }
            public string SKUAssetGroup { get; set; }
            public Guid CIId { get; set; }
            public int SKUTypeId { get; internal set; }
            public int SKUAssetGroupId { get; internal set; }
            public string DomainName { get; internal set; }
            public string Sid { get; internal set; }
        }
    }

    public class M42User
    {
        public Guid Id { get; internal set; }
        public Guid ObjectId { get; internal set; }
        public string Name { get; internal set; }
        public string FirstName { get; internal set; }
        public string LastName { get; internal set; }
        public string Email { get; internal set; }
        public string Photo { get; internal set; }
        public string Phone { get; internal set; }
        public string Currency { get; internal set; }
        public string Locale { get; internal set; }
        public int LanguageId { get; internal set; }
        public bool IsAdmin { get; internal set; }
        public int State { get; internal set; }
        public string StateDisp { get; internal set; }
    }

    internal class M42Account
    {
        public Guid Id { get; internal set; }
        public Guid EOID { get; internal set; }
        public Guid Owner { get; internal set; }
        public int State { get; internal set; }
        public string UserPrincipalName { get; internal set; }
        public string Sid { get; internal set; }
    }

    public class M42Role
    {
        public Guid Id { get; internal set; }
        public string Name { get; internal set; }

    }

    public class UserPermissionsInfo
    {
        public M42User User { get; internal set; }
        public List<M42Role> Roles { get; internal set; }
        public UserPermissionsInfo()
        {
            User = new M42User();
            Roles = new List<M42Role>();
        }
    }
}
