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

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

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

        private static List<Service> GetServices()
        {
            var services = new List<Service>();

            var serviceControllers = ServiceController.GetServices()?.OrderBy(s => s.DisplayName)?.ToList();
            if (serviceControllers is null || serviceControllers.Any() is false) throw new InvalidOperationException("SERVICE Collection NULL");

            foreach (var sc in serviceControllers)
            {
                var status = sc.Status switch
                {
                    ServiceControllerStatus.Stopped => Service.ServiceStatus.Stopped,
                    ServiceControllerStatus.StartPending => Service.ServiceStatus.StartPending,
                    ServiceControllerStatus.StopPending => Service.ServiceStatus.StopPending,
                    ServiceControllerStatus.Running => Service.ServiceStatus.Running,
                    ServiceControllerStatus.ContinuePending => Service.ServiceStatus.ContinuePending,
                    ServiceControllerStatus.PausePending => Service.ServiceStatus.PausePending,
                    ServiceControllerStatus.Paused => Service.ServiceStatus.Paused,
                    _ => Service.ServiceStatus.Unknown
                };

                var mode = sc.StartType switch
                {
                    ServiceStartMode.Boot => Service.ServiceMode.Boot,
                    ServiceStartMode.System => Service.ServiceMode.System,
                    ServiceStartMode.Automatic => Service.ServiceMode.Automatic,
                    ServiceStartMode.Manual => Service.ServiceMode.Manual,
                    ServiceStartMode.Disabled => Service.ServiceMode.Disabled,
                    _ => Service.ServiceMode.Unknown,
                };

                var service = new Service
                {
                    Name = sc.ServiceName?.Trim(),
                    Display = sc.DisplayName?.Trim(),
                    Status = status,
                    StartMode = mode
                };

                services.Add(service);
            }

            // additional infos
            using var searcher = new ManagementObjectSearcher
            {
                Scope = new ManagementScope(@"root\cimv2"),
                Query = new ObjectQuery("SELECT processid, name, description, pathname, startname, delayedautostart from win32_service")
            };

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

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

            var services2 = new List<Service>();

            using (collection)
            {
                foreach (ManagementObject @object in collection.Cast<ManagementObject>())
                {
                    var service2 = new Service();

                    var properties = @object.GetPropertyHashes();

                    service2.Name = @object.GetValue<string>(properties, "name")?.Trim();
                    service2.ProcessId = @object.GetValue<uint>(properties, "processid");
                    service2.Description = @object.GetValue<string>(properties, "description")?.Trim();
                    service2.PathName = @object.GetValue<string>(properties, "pathname")?.Trim();
                    service2.Account = @object.GetValue<string>(properties, "startname")?.Trim();
                    service2.Delay = @object.GetValue<bool>(properties, "delayedautostart");

                    services2.Add(service2);
                }
            }

            if (services2.Any() is false) return services;

            foreach (var svc in services)
            {
                var map = services2.Where(p => p.Name == svc.Name).FirstOrDefault();

                if (map is null) continue;

                svc.ProcessId = map.ProcessId;
                svc.Description = map.Description;
                svc.PathName = map.PathName;
                svc.Account = map.Account;
                svc.Delay = map.Delay;
            }

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