using Insight.Agent.Interfaces;
using Insight.Agent.Messages;
using System.Management;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Route = Insight.Agent.Messages.Route;

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

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

        private static List<Interface> GetInterfaces()
        {
            if (NetworkInterface.GetIsNetworkAvailable() is false) return null;
            if (NetworkInterface.GetAllNetworkInterfaces().Any() is false) return null;

            var interfaces = new List<Interface>();

            foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
            {
                var ipProperties = ni.GetIPProperties();
                var ipStatistics = ni.GetIPStatistics();

                var @interface = new Interface
                {
                    Mac = ni.GetPhysicalAddress().ToString(),
                    Name = ni.Name,
                    Description = ni.Description,
                    Type = ni.NetworkInterfaceType,
                    Speed = ni.Speed,
                    Status = ni.OperationalStatus,
                    Suffix = ipProperties.DnsSuffix,
                    Sent = ipStatistics.BytesSent,
                    Received = ipStatistics.BytesReceived,
                    IncomingPacketsDiscarded = ipStatistics.IncomingPacketsDiscarded,
                    IncomingPacketsWithErrors = ipStatistics.IncomingPacketsWithErrors,
                    IncomingUnknownProtocolPackets = ipStatistics.IncomingUnknownProtocolPackets,
                    OutgoingPacketsDiscarded = ipStatistics.OutgoingPacketsDiscarded,
                    OutgoingPacketsWithErrors = ipStatistics.OutgoingPacketsWithErrors
                };

                try
                {
                    var propertiesV4 = ipProperties.GetIPv4Properties();
                    @interface.Index = uint.Parse(propertiesV4.Index.ToString());
                    @interface.Ipv4Mtu = propertiesV4.Mtu;
                    @interface.Ipv4Dhcp = propertiesV4.IsDhcpEnabled;
                    @interface.Ipv4Forwarding = propertiesV4.IsForwardingEnabled;
                }
                catch (Exception) { }

                try
                {
                    var propertiesV6 = ipProperties.GetIPv6Properties();
                    @interface.Index = uint.Parse(propertiesV6.Index.ToString());
                    @interface.Ipv6Mtu = propertiesV6.Mtu;
                }
                catch (Exception) { }

                @interface.Gateways = GetAddresses(ipProperties.GatewayAddresses);
                @interface.Addresses = GetAddresses(ipProperties.UnicastAddresses);
                @interface.Dns = GetAddresses(ipProperties.DnsAddresses);
                @interface.Dhcp = GetAddresses(ipProperties.DhcpServerAddresses);

                if (@interface.Index.HasValue)
                {
                    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        using var searcher = new ManagementObjectSearcher
                        {
                            Scope = new ManagementScope(@"root\cimv2"),
                            Query = new ObjectQuery($"select interfaceindex, guid, physicaladapter, manufacturer from win32_networkadapter where interfaceindex = {@interface.Index}")
                        };

                        if (searcher.TryGet(out var collection) is false)
                        {
                            searcher.Query = new ObjectQuery($"select * from win32_networkadapter where interfaceindex = {@interface.Index}");

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

                        using (collection)
                        {
                            foreach (ManagementObject @object in collection)
                            {
                                var properties = @object.GetPropertyHashes();

                                @interface.Manufacturer = @object.GetValue<string>(properties, "manufacturer")?.Trim();
                                @interface.Guid = @object.GetValue<Guid>(properties, "guid");
                                @interface.Physical = @object.GetValue<bool>(properties, "physicaladapter");

                                break;
                            }
                        }

                        @interface.Routes = QueryInterfaceRoutes(@interface.Index.Value);
                    }
                }

                interfaces.Add(@interface);
            }

            return interfaces;
        }

        private static List<Route> QueryInterfaceRoutes(uint interfaceIndex)
        {
            using var searcher = new ManagementObjectSearcher
            {
                Scope = new ManagementScope(@"root\standardcimv2"),
                Query = new ObjectQuery($"select addressFamily, state, interfaceindex, routemetric, nexthop, destinationprefix from msft_netroute where interfaceindex = {interfaceIndex}")
            };

            if (searcher.TryGet(out var collection) is false)
            {
                searcher.Query = new ObjectQuery($"select * from msft_netroute where interfaceindex = {interfaceIndex}");

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

            var routes = new List<Route>();

            using (collection)
            {
                foreach (var @object in collection)
                {
                    var route = new Route
                    {
                        InterfaceIndex = interfaceIndex
                    };

                    var properties = @object.GetPropertyHashes();

                    if (@object.TryGetValue<object>(properties, "routemetric", out var routemetric))
                    {
                        if (int.TryParse(routemetric?.ToString(), out var metric)) route.Metric = metric;
                    }

                    if (@object.TryGetValue<object>(properties, "nexthop", out var nexthop))
                    {
                        if (IPAddress.TryParse(nexthop?.ToString(), out var gateway)) route.Gateway = new IPAddress2(gateway);
                    }

                    if (@object.TryGetValue<object>(properties, "destinationprefix", out var destinationprefix))
                    {
                        var split = destinationprefix?.ToString()?.Split('/');
                        var cidrData = split?[1];

                        if (IPAddress.TryParse(split?[0], out var destination))
                            route.Destination = new IPAddress2(destination);

                        if (int.TryParse(cidrData, out var cidr))
                        {
                            var mask = ConvertCidr(cidr);
                            route.Mask = mask;
                        }
                    }

                    routes.Add(route);
                }
            }

            return routes;
        }

        private static List<Unicast> GetAddresses(UnicastIPAddressInformationCollection unicastCollection)
        {
            var addresses = new List<Unicast>();

            if (unicastCollection.Any() is false) return addresses;

            foreach (var unicast in unicastCollection)
            {
                addresses.Add(new Unicast
                {
                    IpAddress = new IPAddress2(unicast.Address),
                    AddressPreferredLifetime = unicast.AddressPreferredLifetime,
                    AddressValidLifetime = unicast.AddressValidLifetime,
                    DuplicateAddressDetectionState = unicast.DuplicateAddressDetectionState,
                    Ipv4Mask = new IPAddress2(unicast.IPv4Mask),
                    PrefixLength = unicast.PrefixLength,
                    PrefixOrigin = unicast.PrefixOrigin,
                    SuffixOrigin = unicast.SuffixOrigin,
                    DhcpLeaseLifetime = unicast.DhcpLeaseLifetime,
                });
            }

            return addresses;
        }

        private static List<IPAddress2> GetAddresses(IPAddressCollection addressCollection)
        {
            var addresses = new List<IPAddress2>();

            if (addressCollection.Any() is false) return addresses;

            foreach (var address in addressCollection)
            {
                addresses.Add(new IPAddress2(address));
            }

            return addresses;
        }

        private static List<IPAddress2> GetAddresses(GatewayIPAddressInformationCollection addressCollection)
        {
            var addresses = new List<IPAddress2>();

            if (addressCollection.Any() is false) return addresses;

            foreach (var address in addressCollection)
            {
                addresses.Add(new IPAddress2(address.Address));
            }

            return addresses;
        }

        private static string? ConvertCidr(int cidr)
        {
            return cidr switch
            {
                0 => "0.0.0.0",
                1 => "128.0.0.0",
                2 => "192.0.0.0",
                3 => "224.0.0.0",
                4 => "240.0.0.0",
                5 => "248.0.0.0",
                6 => "252.0.0.0",
                7 => "254.0.0.0",
                8 => "255.0.0.0",
                9 => "255.128.0.0",
                10 => "255.192.0.0",
                11 => "255.224.0.0",
                12 => "255.240.0.0",
                13 => "255.248.0.0",
                14 => "255.252.0.0",
                15 => "255.254.0.0",
                16 => "255.255.0.0",
                17 => "255.255.128.0",
                18 => "255.255.192.0",
                19 => "255.255.224.0",
                20 => "255.255.240.0",
                21 => "255.255.248.0",
                22 => "255.255.252.0",
                23 => "255.255.254.0",
                24 => "255.255.255.0",
                25 => "255.255.255.128",
                26 => "255.255.255.192",
                27 => "255.255.255.224",
                28 => "255.255.255.240",
                29 => "255.255.255.248",
                30 => "255.255.255.252",
                31 => "255.255.255.254",
                32 => "255.255.255.255",
                _ => null,
            };
        }
    }
}