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

namespace Insight.Server.Network.Agent.Handlers;

public class UserHandler : IMessageHandler<AgentSession>
{
    private readonly IMongoDatabase _database;

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

    public async ValueTask HandleAsync<TMessage>(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage
    {
        switch (message)
        {
            case Collection<User> users:
                await OnUsersAsync(sender, users, cancellationToken);
                break;
        }
    }

    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<HostSysUserEntity>>();

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

                var userUpdate = Builders<HostSysUserEntity>.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<HostSysUserEntity>(userFilter, userUpdate)
                {
                    IsUpsert = true
                });
            }

            userBulk.Add(new DeleteManyModel<HostSysUserEntity>(Builders<HostSysUserEntity>.Filter.And(new List<FilterDefinition<HostSysUserEntity>>
                {
                    Builders<HostSysUserEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                    Builders<HostSysUserEntity>.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<HostSysGroupEntity>>();

            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<HostSysGroupEntity>.Filter.And(new List<FilterDefinition<HostSysGroupEntity>>
                    {
                        Builders<HostSysGroupEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                        Builders<HostSysGroupEntity>.Filter.Eq(x => x.Domain, group?.Domain),
                        Builders<HostSysGroupEntity>.Filter.Eq(x => x.Name, group?.Name)
                    });

                var groupUpdate = Builders<HostSysGroupEntity>.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<HostSysGroupEntity>(groupFilter, groupUpdate)
                {
                    IsUpsert = true
                });
            }

            groupBulk.Add(new DeleteManyModel<HostSysGroupEntity>(Builders<HostSysGroupEntity>.Filter.And(new List<FilterDefinition<HostSysGroupEntity>>
                {
                    Builders<HostSysGroupEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                    Builders<HostSysGroupEntity>.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<HostSysUserSysGroupEntity>>();

            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<HostSysUserSysGroupEntity>.Filter.And(new List<FilterDefinition<HostSysUserSysGroupEntity>>
                            {
                                Builders<HostSysUserSysGroupEntity>.Filter.Eq(x => x.Host, hostEntity.Id),
                                Builders<HostSysUserSysGroupEntity>.Filter.Eq(x => x.User, userId),
                                Builders<HostSysUserSysGroupEntity>.Filter.Eq(x => x.Group, groupId)
                            });

                        var relationUpdate = Builders<HostSysUserSysGroupEntity>.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<HostSysUserSysGroupEntity>(relationFilter, relationUpdate)
                        {
                            IsUpsert = true
                        });
                    }
                }
            }

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

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