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

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

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

        private async ValueTask OnUsersAsync(AgentSession session, List<User>? users, 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;

            // users

            if (users is not null && users.Any())
            {
                var userBulk = new List<WriteModel<HostUserEntity>>();

                foreach (var user in users)
                {
                    var userFilter = Builders<HostUserEntity>.Filter.And(new List<FilterDefinition<HostUserEntity>>
                        {
                            Builders<HostUserEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                            Builders<HostUserEntity>.Filter.Eq(x => x.Domain, user?.Domain),
                            Builders<HostUserEntity>.Filter.Eq(x => x.Name, user?.Name)
                        });

                    var userUpdate = Builders<HostUserEntity>.Update
                        .SetOnInsert(p => p.Insert, date)
                        .SetOnInsert(p => p.Host, hostEntity.Id)
                        .SetOnInsert(p => p.Domain, user?.Domain)
                        .SetOnInsert(p => p.Name, user?.Name)
                        .Set(p => p.Update, date)
                        .Set(p => p.Batch, batch)

                        .Set(p => p.Sid, user?.Sid)
                        .Set(p => p.FullName, user?.FullName)
                        .Set(p => p.Description, user?.Description)
                        .Set(p => p.Status, user?.Status)
                        .Set(p => p.LocalAccount, user?.LocalAccount)
                        .Set(p => p.Disabled, user?.Disabled)
                        .Set(p => p.Lockout, user?.Lockout)
                        .Set(p => p.PasswordChangeable, user?.PasswordChangeable)
                        .Set(p => p.PasswordExpires, user?.PasswordExpires)
                        .Set(p => p.PasswordRequired, user?.PasswordRequired);

                    userBulk.Add(new UpdateOneModel<HostUserEntity>(userFilter, userUpdate)
                    {
                        IsUpsert = true
                    });
                }

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

                var userResult = await _database.HostSystemUser().BulkWriteAsync(userBulk, cancellationToken: cancellationToken);
            }

            // groups

            if (users is not null && users.Any())
            {
                var groupBulk = new List<WriteModel<HostGroupEntity>>();

                var distinctGroups = users.SelectMany(p => p.Groups)
                    .GroupBy(p => new { p?.Domain, p?.Name })
                    .Select(p => p.First());

                foreach (var group in distinctGroups)
                {
                    var groupFilter = Builders<HostGroupEntity>.Filter.And(new List<FilterDefinition<HostGroupEntity>>
                        {
                            Builders<HostGroupEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                            Builders<HostGroupEntity>.Filter.Eq(x => x.Domain, group?.Domain),
                            Builders<HostGroupEntity>.Filter.Eq(x => x.Name, group?.Name)
                        });

                    var groupUpdate = Builders<HostGroupEntity>.Update
                        .SetOnInsert(p => p.Insert, date)
                        .SetOnInsert(p => p.Host, hostEntity.Id)
                        .SetOnInsert(p => p.Domain, group?.Domain)
                        .SetOnInsert(p => p.Name, group?.Name)
                        .Set(p => p.Update, date)
                        .Set(p => p.Batch, batch)

                        .Set(p => p.Sid, group?.Sid)
                        .Set(p => p.Description, group?.Description)
                        .Set(p => p.LocalAccount, group?.LocalAccount);

                    groupBulk.Add(new UpdateOneModel<HostGroupEntity>(groupFilter, groupUpdate)
                    {
                        IsUpsert = true
                    });
                }

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

                var groupResult = await _database.HostSystemGroup().BulkWriteAsync(groupBulk, cancellationToken: cancellationToken);
            }

            // relations

            if (users is not null && users.Any())
            {
                var relationBulk = new List<WriteModel<HostUserGroupEntity>>();

                foreach (var user in users)
                {
                    var userId = await _database.HostSystemUser()
                        .Find(p => p.Host == hostEntity.Id && p.Domain == user.Domain && p.Name == user.Name)
                        .Project(p => p.Id)
                        .FirstOrDefaultAsync();

                    if (user.Groups is not null && user.Groups.Any())
                    {
                        foreach (var group in user.Groups)
                        {
                            var groupId = await _database.HostSystemGroup()
                                .Find(p => p.Host == hostEntity.Id && p.Domain == group.Domain && p.Name == group.Name)
                                .Project(p => p.Id)
                                .FirstOrDefaultAsync();

                            var relationFilter = Builders<HostUserGroupEntity>.Filter.And(new List<FilterDefinition<HostUserGroupEntity>>
                                {
                                    Builders<HostUserGroupEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                    Builders<HostUserGroupEntity>.Filter.Eq(x => x.User, userId),
                                    Builders<HostUserGroupEntity>.Filter.Eq(x => x.Group, groupId)
                                });

                            var relationUpdate = Builders<HostUserGroupEntity>.Update
                                .SetOnInsert(p => p.Insert, date)
                                .SetOnInsert(p => p.Host, hostEntity.Id)
                                .SetOnInsert(p => p.User, userId)
                                .SetOnInsert(p => p.Group, groupId)
                                .Set(p => p.Update, date)
                                .Set(p => p.Batch, batch);

                            relationBulk.Add(new UpdateOneModel<HostUserGroupEntity>(relationFilter, relationUpdate)
                            {
                                IsUpsert = true
                            });
                        }
                    }
                }

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

                var relationResult = await _database.HostSystemUserSystemGroup().BulkWriteAsync(relationBulk, cancellationToken: cancellationToken);
            }
        }
    }
}