using Insight.Agent.Interfaces;
using Insight.Agent.Messages;
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using MongoDB.Bson;
using MongoDB.Driver;

namespace Insight.Server.Network.Handlers.Agent
{
    public class InterfaceHandler : IAgentMessageHandler<AgentSession>
    {
        private readonly IMongoDatabase _database;

        public InterfaceHandler(IMongoDatabase database)
        {
            _database = database;
        }

        public async ValueTask HandleAsync<TMessage>(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IAgentMessage
        {
            if (message is InterfaceList interfaces)
            {
                await OnInterfacesAsync(sender, interfaces, cancellationToken);
            }
        }

        private async ValueTask OnInterfacesAsync(AgentSession session, List<Interface> interfaces, CancellationToken cancellationToken)
        {
            var agentEntity = await _database.Agent().Find(Builders<AgentEntity>.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken);
            if (agentEntity is null) return;

            var hostEntity = await _database.Host().Find(Builders<HostEntity>.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken);
            if (hostEntity is null) return;

            var batch = ObjectId.GenerateNewId().ToString();
            var date = DateTime.Now;

            // interfaces

            if (interfaces is not null && interfaces.Any())
            {
                var interfaceBulk = new List<WriteModel<HostInterfaceEntity>>();

                foreach (var @interface in interfaces)
                {
                    var interfaceFilter = Builders<HostInterfaceEntity>.Filter.And(new List<FilterDefinition<HostInterfaceEntity>>
                        {
                            Builders<HostInterfaceEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                            Builders<HostInterfaceEntity>.Filter.Eq(x => x.Index, @interface.Index)
                        });

                    var interfaceUpdate = Builders<HostInterfaceEntity>.Update
                        .SetOnInsert(p => p.Insert, date)
                        .SetOnInsert(p => p.Host, hostEntity.Id)
                        .SetOnInsert(p => p.Index, @interface.Index)
                        .Set(p => p.Update, date)
                        .Set(p => p.Batch, batch)

                        .Set(p => p.Mac, @interface?.Mac)
                        .Set(p => p.Name, @interface?.Name)
                        .Set(p => p.Description, @interface?.Description)
                        .Set(p => p.Physical, @interface?.Physical)
                        .Set(p => p.Status, @interface?.Status?.ToString())
                        .Set(p => p.Suffix, @interface?.Suffix)
                        .Set(p => p.Speed, @interface?.Speed)
                        .Set(p => p.Ipv4Mtu, @interface?.Ipv4Mtu)
                        .Set(p => p.Ipv4Dhcp, @interface?.Ipv4Dhcp)

                        .Set(p => p.Ipv4Forwarding, @interface?.Ipv4Forwarding)
                        .Set(p => p.Ipv6Mtu, @interface?.Ipv6Mtu)
                        .Set(p => p.Sent, @interface?.Sent)
                        .Set(p => p.Received, @interface?.Received)
                        .Set(p => p.IncomingPacketsDiscarded, @interface?.IncomingPacketsDiscarded)
                        .Set(p => p.IncomingPacketsWithErrors, @interface?.IncomingPacketsWithErrors)
                        .Set(p => p.IncomingUnknownProtocolPackets, @interface?.IncomingUnknownProtocolPackets)
                        .Set(p => p.OutgoingPacketsDiscarded, @interface?.OutgoingPacketsDiscarded)
                        .Set(p => p.OutgoingPacketsWithErrors, @interface?.OutgoingPacketsWithErrors);

                    interfaceBulk.Add(new UpdateOneModel<HostInterfaceEntity>(interfaceFilter, interfaceUpdate)
                    {
                        IsUpsert = true
                    });
                }

                interfaceBulk.Add(new DeleteManyModel<HostInterfaceEntity>(Builders<HostInterfaceEntity>.Filter.And(new List<FilterDefinition<HostInterfaceEntity>>
                    {
                        Builders<HostInterfaceEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                        Builders<HostInterfaceEntity>.Filter.Ne(x => x.Batch, batch)
                    })));

                var interfaceResult = await _database.HostInterface().BulkWriteAsync(interfaceBulk, cancellationToken: cancellationToken);
            }

            // addresses

            if (interfaces is not null && interfaces.Any())
            {
                var addressBulk = new List<WriteModel<HostInterfaceAddressEntity>>();

                foreach (var @interface in interfaces)
                {
                    var interfaceId = await _database.HostInterface()
                        .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index)
                        .Project(p => p.Id)
                        .FirstOrDefaultAsync();

                    if (@interface.Addresses is not null && @interface.Addresses.Any())
                    {
                        foreach (var address in @interface.Addresses)
                        {
                            var addressFilter = Builders<HostInterfaceAddressEntity>.Filter.And(new List<FilterDefinition<HostInterfaceAddressEntity>>
                                {
                                    Builders<HostInterfaceAddressEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostInterfaceAddressEntity>.Filter.Eq(x => x.Interface, interfaceId),
                                    Builders<HostInterfaceAddressEntity>.Filter.Eq(x => x.Address, address?.IpAddress?.Address),
                                    Builders<HostInterfaceAddressEntity>.Filter.Eq(x => x.Mask, address?.Ipv4Mask?.Address)
                                });

                            var addressUpdate = Builders<HostInterfaceAddressEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.Interface, interfaceId)
                                .SetOnInsert(p => p.Address, address?.IpAddress?.Address)
                                .SetOnInsert(p => p.Mask, address?.Ipv4Mask?.Address)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch);

                            addressBulk.Add(new UpdateOneModel<HostInterfaceAddressEntity>(addressFilter, addressUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

                addressBulk.Add(new DeleteManyModel<HostInterfaceAddressEntity>(Builders<HostInterfaceAddressEntity>.Filter.And(new List<FilterDefinition<HostInterfaceAddressEntity>>
                            {
                                Builders<HostInterfaceAddressEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                Builders<HostInterfaceAddressEntity>.Filter.Ne(x => x.Batch, batch)
                            })));

                var addressResult = await _database.HostInterfaceAddress().BulkWriteAsync(addressBulk, cancellationToken: cancellationToken);
            }

            // gateways

            if (interfaces is not null && interfaces.Any())
            {
                var gatewayBulk = new List<WriteModel<HostInterfaceGatewayEntity>>();

                foreach (var @interface in interfaces)
                {
                    var interfaceId = await _database.HostInterface()
                        .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index)
                        .Project(p => p.Id)
                        .FirstOrDefaultAsync();

                    if (@interface.Gateways is not null && @interface.Gateways.Any())
                    {
                        foreach (var gateway in @interface.Gateways)
                        {
                            var gatewayFilter = Builders<HostInterfaceGatewayEntity>.Filter.And(new List<FilterDefinition<HostInterfaceGatewayEntity>>
                                {
                                    Builders<HostInterfaceGatewayEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostInterfaceGatewayEntity>.Filter.Eq(x => x.Interface, interfaceId),
                                    Builders<HostInterfaceGatewayEntity>.Filter.Eq(x => x.Address, gateway?.Address)
                                });

                            var gatewayUpdate = Builders<HostInterfaceGatewayEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.Interface, interfaceId)
                                .SetOnInsert(p => p.Address, gateway?.Address)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch);

                            gatewayBulk.Add(new UpdateOneModel<HostInterfaceGatewayEntity>(gatewayFilter, gatewayUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

                gatewayBulk.Add(new DeleteManyModel<HostInterfaceGatewayEntity>(Builders<HostInterfaceGatewayEntity>.Filter.And(new List<FilterDefinition<HostInterfaceGatewayEntity>>
                            {
                                Builders<HostInterfaceGatewayEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                Builders<HostInterfaceGatewayEntity>.Filter.Ne(x => x.Batch, batch)
                            })));

                var gatewayResult = await _database.HostInterfaceGateway().BulkWriteAsync(gatewayBulk, cancellationToken: cancellationToken);
            }

            // nameservers

            if (interfaces is not null && interfaces.Any())
            {
                var nameserverBulk = new List<WriteModel<HostInterfaceNameserverEntity>>();

                foreach (var @interface in interfaces)
                {
                    var interfaceId = await _database.HostInterface()
                        .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index)
                        .Project(p => p.Id)
                        .FirstOrDefaultAsync();

                    if (@interface.Dns is not null && @interface.Dns.Any())
                    {
                        foreach (var nameserver in @interface.Dns)
                        {
                            var nameserverFilter = Builders<HostInterfaceNameserverEntity>.Filter.And(new List<FilterDefinition<HostInterfaceNameserverEntity>>
                                {
                                    Builders<HostInterfaceNameserverEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostInterfaceNameserverEntity>.Filter.Eq(x => x.Interface, interfaceId),
                                    Builders<HostInterfaceNameserverEntity>.Filter.Eq(x => x.Address, nameserver?.Address)
                                });

                            var nameserverUpdate = Builders<HostInterfaceNameserverEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.Interface, interfaceId)
                                .SetOnInsert(p => p.Address, nameserver?.Address)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch);

                            nameserverBulk.Add(new UpdateOneModel<HostInterfaceNameserverEntity>(nameserverFilter, nameserverUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

                nameserverBulk.Add(new DeleteManyModel<HostInterfaceNameserverEntity>(Builders<HostInterfaceNameserverEntity>.Filter.And(new List<FilterDefinition<HostInterfaceNameserverEntity>>
                            {
                                Builders<HostInterfaceNameserverEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                Builders<HostInterfaceNameserverEntity>.Filter.Ne(x => x.Batch, batch)
                            })));

                var nameserverResult = await _database.HostInterfaceNameserver().BulkWriteAsync(nameserverBulk, cancellationToken: cancellationToken);
            }

            // routes

            if (interfaces is not null && interfaces.Any())
            {
                var routeBulk = new List<WriteModel<HostInterfaceRouteEntity>>();

                foreach (var @interface in interfaces)
                {
                    var interfaceId = await _database.HostInterface()
                        .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index)
                        .Project(p => p.Id)
                        .FirstOrDefaultAsync();

                    if (@interface.Routes is not null && @interface.Routes.Any())
                    {
                        foreach (var route in @interface.Routes)
                        {
                            var routeFilter = Builders<HostInterfaceRouteEntity>.Filter.And(new List<FilterDefinition<HostInterfaceRouteEntity>>
                                {
                                    Builders<HostInterfaceRouteEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostInterfaceRouteEntity>.Filter.Eq(x => x.Interface, interfaceId),
                                    Builders<HostInterfaceRouteEntity>.Filter.Eq(x => x.Destination, route?.Destination?.Address),
                                    Builders<HostInterfaceRouteEntity>.Filter.Eq(x => x.Gateway, route?.Gateway?.Address),
                                    Builders<HostInterfaceRouteEntity>.Filter.Eq(x => x.Mask, route?.Mask),
                                });

                            var routeUpdate = Builders<HostInterfaceRouteEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.Interface, interfaceId)
                                .SetOnInsert(p => p.Destination, route?.Destination?.Address)
                                .SetOnInsert(p => p.Gateway, route?.Gateway?.Address)
                                .SetOnInsert(p => p.Mask, route?.Mask)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch)

                                .Set(p => p.Metric, route?.Metric);

                            routeBulk.Add(new UpdateOneModel<HostInterfaceRouteEntity>(routeFilter, routeUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

                routeBulk.Add(new DeleteManyModel<HostInterfaceRouteEntity>(Builders<HostInterfaceRouteEntity>.Filter.And(new List<FilterDefinition<HostInterfaceRouteEntity>>
                            {
                                Builders<HostInterfaceRouteEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                Builders<HostInterfaceRouteEntity>.Filter.Ne(x => x.Batch, batch)
                            })));

                var routeResult = await _database.HostInterfaceRoute().BulkWriteAsync(routeBulk, cancellationToken: cancellationToken);
            }
        }
    }
}