using Insight.Agent.Interfaces;
using Insight.Agent.Messages;
using Microsoft.Win32;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.AccessControl;

namespace Insight.Agent.Network.Handlers
{
    [SupportedOSPlatform("windows")]
    internal class SoftwareHandler : IAgentMessageHandler<AgentSession>
    {
        public async ValueTask HandleAsync<TMessage>(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IAgentMessage
        {
            if (message is GetInventory)
            {
                var x64 = Task.Run(() => ApplicationRegistryQuery(RegistryView.Registry64), cancellationToken);
                var x86 = Task.Run(() => ApplicationRegistryQuery(RegistryView.Registry32), cancellationToken);

                await Task.WhenAll(x64, x86).ConfigureAwait(false);

                var result = new ApplicationList();
                result.AddRange(x64.Result);
                result.AddRange(x86.Result.Where(p => result.All(app => p.Name != app.Name && p.Version != app.Version)));

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

        private static IEnumerable<Application> ApplicationRegistryQuery(RegistryView registryView)
        {
            using var registry = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView);

            using var key = registry.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\", RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey);
            if (key is null) throw new NullReferenceException(nameof(key));

            var apps = new List<Application>();

            var architecture = registryView switch
            {
                RegistryView.Registry32 => Architecture.X86,
                _ => Architecture.X64
            };

            foreach (string name in key.GetSubKeyNames())
            {
                using var query = key.OpenSubKey(name);
                if (query is null) continue;

                var app = new Application
                {
                    Architecture = architecture
                };

                if (query.GetValue("DisplayName")?.ToString()?.Trim() is string displayName && string.IsNullOrWhiteSpace(displayName) is false)
                {
                    app.Name = displayName;
                }

                if (query.GetValue("Publisher")?.ToString()?.Trim() is string publisher && string.IsNullOrWhiteSpace(publisher) is false)
                {
                    app.Publisher = publisher;
                }

                if (query.GetValue("DisplayVersion")?.ToString()?.Trim() is string version && string.IsNullOrWhiteSpace(version) is false)
                {
                    app.Version = version;
                }

                if (query.GetValue("InstallLocation")?.ToString()?.Trim() is string location && string.IsNullOrWhiteSpace(location) is false)
                {
                    app.Location = location;
                }

                if (query.GetValue("InstallSource")?.ToString()?.Trim() is string source && string.IsNullOrWhiteSpace(source) is false)
                {
                    app.Source = source;
                }

                if (query.GetValue("UninstallString")?.ToString()?.Trim() is string uninstall && string.IsNullOrWhiteSpace(uninstall) is false)
                {
                    app.Uninstall = uninstall;
                }

                if (app.Uninstall is null)
                {
                    if (query.GetValue("UninstallString_Hidden")?.ToString()?.Trim() is string uninstall2 && string.IsNullOrWhiteSpace(uninstall2) is false)
                    {
                        app.Uninstall = uninstall2;
                    }
                }

                if (query.GetValue("InstallDate")?.ToString()?.Trim() is string installDate)
                {
                    if (DateTime.TryParseExact(installDate, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime valid))
                    {
                        app.InstallDate = valid;
                    }

                    if (app.InstallDate is null)
                    {
                        if (DateTime.TryParseExact(installDate, "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime valid2))
                        {
                            app.InstallDate = valid2;
                        }
                    }
                }

                if (app.Name is not null)
                {
                    apps.Add(app);
                }
            }

            return apps;
        }
    }
}