using Insight.Agent.Interfaces;
using Insight.Agent.Messages;
using System.Management;
using System.Runtime.Versioning;

namespace Insight.Agent.Network.Handlers
{
    [SupportedOSPlatform("windows")]
    public class UserHandler : IAgentMessageHandler<AgentSession>
    {
        public async ValueTask HandleAsync<TMessage>(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IAgentMessage
        {
            if (message is GetInventory)
            {
                var result = new UserList();
                result.AddRange(GetUsers());

                await sender.SendAsync(result, cancellationToken);
            }
        }

        private static List<User> GetUsers()
        {
            var users = QueryUsers();
            var groups = GetGroups();
            var usergrouping = QueryUserGroupMaps();

            foreach (var u in users)
            {
                u.Groups = new List<Group>();

                foreach (var ug in usergrouping.Where(ug => ug.UserDomain == u.Domain && ug.UserName == u.Name))
                {
                    var grps = groups.Where(g => g.Domain == ug.GroupDomain && g.Name == ug.GroupName);

                    if (grps is not null)
                    {
                        u.Groups.AddRange(grps);
                    }
                }
            }

            return users;
        }

        private static List<Group> GetGroups()
        {
            using var searcher = new ManagementObjectSearcher
            {
                Scope = new ManagementScope(@"root\cimv2"),
                Query = new ObjectQuery("select sid, domain, name, description, localaccount from win32_group")
            };

            if (searcher.TryGet(out var collection) is false)
            {
                searcher.Query = new ObjectQuery("select * from win32_group");

                if (searcher.TryGet(out collection) is false) throw new InvalidOperationException("WMI Collection NULL");
            }

            var groups = new List<Group>();

            using (collection)
            {
                foreach (ManagementObject @object in collection)
                {
                    var group = new Group();

                    var properties = @object.GetPropertyHashes();

                    group.Sid = @object.GetValue<string>(properties, "sid")?.Trim();
                    group.Domain = @object.GetValue<string>(properties, "domain")?.Trim();
                    group.Name = @object.GetValue<string>(properties, "name")?.Trim();
                    group.Description = @object.GetValue<string>(properties, "description")?.Trim();
                    group.LocalAccount = @object.GetValue<bool>(properties, "localaccount");

                    groups.Add(group);
                }
            }

            return groups.OrderBy(x => x.Name)?.ToList();
        }

        private static List<User> QueryUsers()
        {
            using var searcher = new ManagementObjectSearcher
            {
                Scope = new ManagementScope(@"root\cimv2"),
                Query = new ObjectQuery("select sid, name, fullname, description, domain, localaccount, disabled, lockout, status, passwordchangeable, passwordexpires, passwordrequired from win32_useraccount")
            };

            if (searcher.TryGet(out var collection) is false)
            {
                searcher.Query = new ObjectQuery("select * from win32_useraccount");

                if (searcher.TryGet(out collection) is false) throw new InvalidOperationException("WMI Collection NULL");
            }

            var users = new List<User>();

            using (collection)
            {
                foreach (ManagementObject @object in collection)
                {
                    var user = new User();

                    var properties = @object.GetPropertyHashes();

                    user.Sid = @object.GetValue<string>(properties, "sid")?.Trim();
                    user.Name = @object.GetValue<string>(properties, "name")?.Trim();
                    user.FullName = @object.GetValue<string>(properties, "fullname")?.Trim();
                    user.Description = @object.GetValue<string>(properties, "description")?.Trim();
                    user.Domain = @object.GetValue<string>(properties, "domain")?.Trim();
                    user.LocalAccount = @object.GetValue<bool>(properties, "localaccount");
                    user.Disabled = @object.GetValue<bool>(properties, "disabled");
                    user.Lockout = @object.GetValue<bool>(properties, "lockout");
                    user.Status = @object.GetValue<string>(properties, "status")?.Trim();
                    user.PasswordChangeable = @object.GetValue<bool>(properties, "passwordchangeable");
                    user.PasswordExpires = @object.GetValue<bool>(properties, "passwordexpires");
                    user.PasswordRequired = @object.GetValue<bool>(properties, "passwordrequired");

                    users.Add(user);
                }
            }

            return users;
        }

        private static List<UserGroupMap> QueryUserGroupMaps()
        {
            using var searcher = new ManagementObjectSearcher
            {
                Scope = new ManagementScope(@"root\cimv2"),
                Query = new ObjectQuery("select groupcomponent, partcomponent from win32_groupuser")
            };

            if (searcher.TryGet(out var collection) is false)
            {
                searcher.Query = new ObjectQuery("select * from win32_groupuser");

                if (searcher.TryGet(out collection) is false) throw new InvalidOperationException("WMI Collection NULL");
            }

            var usergroups = new List<UserGroupMap>();

            using (collection)
            {
                foreach (ManagementObject @object in collection)
                {
                    var usergroup = new UserGroupMap();

                    var properties = @object.GetPropertyHashes();

                    var raw = @object.GetValue<string>(properties, "groupcomponent");
                    var split = raw?.Split(".Domain=")[1]?.Split(",Name=");

                    if (split is not null && split.Length > 1)
                    {
                        usergroup.GroupDomain = split[0].TrimStart('"').TrimEnd('"');
                        usergroup.GroupName = split[1].TrimStart('"').TrimEnd('"');
                    }

                    raw = @object.GetValue<string>(properties, "partcomponent");
                    split = raw?.Split(".Domain=")[1]?.Split(",Name=");

                    if (split is not null && split.Length > 1)
                    {
                        usergroup.UserDomain = split[0].TrimStart('"').TrimEnd('"');
                        usergroup.UserName = split[1].TrimStart('"').TrimEnd('"');
                    }

                    usergroups.Add(usergroup);
                }
            }

            return usergroups;
        }

        private class UserGroupMap
        {
            public string? GroupDomain { get; set; }
            public string? GroupName { get; set; }

            public string? UserDomain { get; set; }
            public string? UserName { get; set; }
        }
    }
}