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 StoragePoolHandler : IAgentMessageHandler<AgentSession>
    {
        private readonly IMongoDatabase _database;

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

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

        private async ValueTask OnStoragePoolsAsync(AgentSession session, List<StoragePool>? storagePools, 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;

            // storagepools

            if (storagePools is not null && storagePools.Any())
            {
                var storagepoolBulk = new List<WriteModel<HostStoragePoolEntity>>();

                foreach (var storagePool in storagePools)
                {
                    var storagePoolFilter = Builders<HostStoragePoolEntity>.Filter.And(new List<FilterDefinition<HostStoragePoolEntity>>
                        {
                            Builders<HostStoragePoolEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                            Builders<HostStoragePoolEntity>.Filter.Eq(x => x.UniqueId, storagePool.UniqueId)
                        });

                    List<string>? states = null;

                    if (storagePool.States is not null)
                    {
                        states = new List<string>();

                        foreach (var state in storagePool.States)
                        {
                            states.Add(state.ToString());
                        }
                    }

                    var storagePoolUpdate = Builders<HostStoragePoolEntity>.Update
                        .SetOnInsert(p => p.Insert, date)
                        .SetOnInsert(p => p.Host, hostEntity.Id)
                        .SetOnInsert(p => p.UniqueId, storagePool.UniqueId)
                        .Set(p => p.Update, date)
                        .Set(p => p.Batch, batch)

                        .Set(p => p.Name, storagePool.FriendlyName)
                        .Set(p => p.Health, storagePool.Health?.ToString())
                        .Set(p => p.Resiliency, storagePool.Resiliency)
                        .Set(p => p.Primordial, storagePool.IsPrimordial)
                        .Set(p => p.ReadOnly, storagePool.IsReadOnly)
                        .Set(p => p.Clustered, storagePool.IsClustered)
                        .Set(p => p.Size, storagePool.Size)
                        .Set(p => p.AllocatedSize, storagePool.AllocatedSize)
                        .Set(p => p.SectorSize, storagePool.SectorSize)
                        .Set(p => p.States, states);

                    storagepoolBulk.Add(new UpdateOneModel<HostStoragePoolEntity>(storagePoolFilter, storagePoolUpdate)
                    {
                        IsUpsert = true
                    });
                }

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

                var storagePoolResult = await _database.HostStoragePool().BulkWriteAsync(storagepoolBulk, cancellationToken: cancellationToken);
            }

            // physicaldisks

            if (storagePools is not null && storagePools.Any())
            {
                var physicalDiskBulk = new List<WriteModel<HostStoragePoolPhysicalDiskEntity>>();

                foreach (var storagePool in storagePools)
                {
                    var storagePoolId = await _database.HostStoragePool()
                        .Find(p => p.Host == hostEntity.Id && p.UniqueId == storagePool.UniqueId)
                        .Project(p => p.Id)
                        .FirstOrDefaultAsync();

                    if (storagePool.PhysicalDisks is not null && storagePool.PhysicalDisks.Any())
                    {
                        foreach (var physicalDisk in storagePool.PhysicalDisks)
                        {
                            var physicalDiskFilter = Builders<HostStoragePoolPhysicalDiskEntity>.Filter.And(new List<FilterDefinition<HostStoragePoolPhysicalDiskEntity>>
                                {
                                    Builders<HostStoragePoolPhysicalDiskEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostStoragePoolPhysicalDiskEntity>.Filter.Eq(x => x.StoragePool, storagePoolId),
                                    Builders<HostStoragePoolPhysicalDiskEntity>.Filter.Eq(x => x.UniqueId, physicalDisk.UniqueId)
                                });

                            List<string>? states = null;

                            if (physicalDisk.States is not null)
                            {
                                states = new List<string>();

                                foreach (var state in physicalDisk.States)
                                {
                                    states.Add(state.ToString());
                                }
                            }

                            var physicalDiskUpdate = Builders<HostStoragePoolPhysicalDiskEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.StoragePool, storagePoolId)
                                .SetOnInsert(p => p.UniqueId, physicalDisk.UniqueId)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch)

                                .Set(p => p.DeviceId, physicalDisk.DeviceId)
                                .Set(p => p.Name, physicalDisk.FriendlyName)
                                .Set(p => p.Manufacturer, physicalDisk.Manufacturer)
                                .Set(p => p.Model, physicalDisk.Model)
                                .Set(p => p.Media, physicalDisk.MediaType.ToString())
                                .Set(p => p.Bus, physicalDisk.BusType.ToString())
                                .Set(p => p.Health, physicalDisk.Health.ToString())
                                .Set(p => p.Usage, physicalDisk.Usage)
                                .Set(p => p.Location, physicalDisk.PhysicalLocation)
                                .Set(p => p.Serial, physicalDisk.SerialNumber)
                                .Set(p => p.Firmware, physicalDisk.FirmwareVersion)
                                .Set(p => p.Size, physicalDisk.Size)
                                .Set(p => p.AllocatedSize, physicalDisk.AllocatedSize)
                                .Set(p => p.Footprint, physicalDisk.VirtualDiskFootprint)
                                .Set(p => p.LogicalSectorSize, physicalDisk.LogicalSectorSize)
                                .Set(p => p.PhysicalSectorSize, physicalDisk.PhysicalSectorSize)
                                .Set(p => p.States, states);

                            physicalDiskBulk.Add(new UpdateOneModel<HostStoragePoolPhysicalDiskEntity>(physicalDiskFilter, physicalDiskUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

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

                var physicalDiskResult = await _database.HostStoragePoolPhysicalDisk().BulkWriteAsync(physicalDiskBulk, cancellationToken: cancellationToken);
            }

            // virtual disks

            if (storagePools is not null && storagePools.Any())
            {
                var virtualDiskBulk = new List<WriteModel<HostStoragePoolVirtualDiskEntity>>();

                foreach (var storagePool in storagePools)
                {
                    if (storagePool.VirtualDisks is not null && storagePool.VirtualDisks.Any())
                    {
                        foreach (var virtualDisk in storagePool.VirtualDisks)
                        {
                            var storagePoolId = await _database.HostStoragePool()
                                .Find(p => p.Host == hostEntity.Id && p.UniqueId == storagePool.UniqueId)
                                .Project(p => p.Id)
                                .FirstOrDefaultAsync();

                            var virtualDiskFilter = Builders<HostStoragePoolVirtualDiskEntity>.Filter.And(new List<FilterDefinition<HostStoragePoolVirtualDiskEntity>>
                                {
                                    Builders<HostStoragePoolVirtualDiskEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostStoragePoolVirtualDiskEntity>.Filter.Eq(x => x.StoragePool, storagePoolId),
                                    Builders<HostStoragePoolVirtualDiskEntity>.Filter.Eq(x => x.UniqueId, virtualDisk.UniqueId)
                                });

                            List<string>? states = null;

                            if (virtualDisk.States is not null)
                            {
                                states = new List<string>();

                                foreach (var state in virtualDisk.States)
                                {
                                    states.Add(state.ToString());
                                }
                            }

                            var virtualDiskUpdate = Builders<HostStoragePoolVirtualDiskEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.StoragePool, storagePoolId)
                                .SetOnInsert(p => p.UniqueId, virtualDisk.UniqueId)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch)

                                .Set(p => p.Name, virtualDisk.FriendlyName)
                                .Set(p => p.Health, virtualDisk.Health.ToString())
                                .Set(p => p.Access, virtualDisk.AccessType.ToString())
                                .Set(p => p.Provisioning, virtualDisk.ProvisioningType.ToString())
                                .Set(p => p.PhysicalRedundancy, virtualDisk.PhysicalDiskRedundancy)
                                .Set(p => p.Resiliency, virtualDisk.ResiliencySettingName)
                                .Set(p => p.Deduplication, virtualDisk.Deduplication)
                                .Set(p => p.Snapshot, virtualDisk.IsSnapshot)
                                .Set(p => p.Size, virtualDisk.Size)
                                .Set(p => p.AllocatedSize, virtualDisk.AllocatedSize)
                                .Set(p => p.Footprint, virtualDisk.FootprintOnPool)
                                .Set(p => p.ReadCacheSize, virtualDisk.ReadCacheSize)
                                .Set(p => p.WriteCacheSize, virtualDisk.WriteCacheSize)
                                .Set(p => p.States, states);

                            virtualDiskBulk.Add(new UpdateOneModel<HostStoragePoolVirtualDiskEntity>(virtualDiskFilter, virtualDiskUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

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

                var virtualDiskResult = await _database.HostStoragePoolVirtualDisk().BulkWriteAsync(virtualDiskBulk, cancellationToken: cancellationToken);
            }
        }
    }
}