using Insight.Infrastructure;
using Insight.Web.Models;
using MongoDB.Bson;
using MongoDB.Driver;
using System.Collections.Concurrent;
using Vaitr.MemoryBus;
using static Insight.Web.Constants.Events.Chat;

namespace Insight.Web.Services;

public class ChatService
{
    public readonly ConcurrentDictionary<ChatUser, List<string>> Users = new();
    public readonly ConcurrentDictionary<ObjectId, ChatSession> Sessions = new();

    private readonly IMemoryBus _bus;
    private readonly IMongoDatabase _database;
    private readonly ILogger<ChatService> _logger;

    public ChatService(IMemoryBus bus, IMongoDatabase database, ILogger<ChatService> logger)
    {
        _bus = bus;
        _database = database;
        _logger = logger;
    }

    public async Task InitAsync(CancellationToken cancellationToken)
    {
        // initial user feed
        var chatRole = await _database.Role().Find(p => p.Name == "chat").FirstOrDefaultAsync(cancellationToken);
        //var chatUsers = await _database.User().Find(p => p.Roles.Contains(chatRole.Id)).ToListAsync(cancellationToken);

        var chatUsers = await _database.User().Find(p => true).ToListAsync(cancellationToken);

        foreach (var user in chatUsers)
        {
            Users.TryAdd(new ChatUser(user.Id)
            {
                Username = user.UserName,
                Email = user.Email
            }, new());
        }
    }

    public async Task AddAsync(SessionState session, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(session.Id) || session.Uid is null) return;
        if (Users.Any(p => p.Key.Uid == session.Uid) is false) return;

        var user = Users.First(p => p.Key.Uid == session.Uid);
        user.Value.Add(session.Id);

        user.Key.Online = true;

        _logger.LogInformation($"Chat ({user.Key.Username} / {session.Id}) added");

        try
        {
            _ = _bus.PublishAsync(new ChatUserConnected(user.Key), default);
            _ = _bus.PublishAsync(new ChatRefresh(), default);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    }

    public async Task RemoveAsync(SessionState session, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(session.Id) || session.Uid is null) return;
        if (Users.Any(p => p.Key.Uid == session.Uid) is false) return;

        var user = Users.FirstOrDefault(p => p.Key.Uid == session.Uid);
        if (user.Key is null) return;

        if (user.Value.Contains(session.Id))
        {
            user.Value.Remove(session.Id);

            _logger.LogInformation($"Chat ({user.Key.Username} / {session.Id}) removed");
        }

        if (user.Value.Any()) return;

        user.Key.Online = false;

        try
        {
            _ = _bus.PublishAsync(new ChatUserDisconnected(user.Key), default);
            _ = _bus.PublishAsync(new ChatRefresh(), default);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    }

    public async Task<ChatSession> AddOrGetSession(IEnumerable<ChatUser> members)
    {
        //if (Sessions.Any(p => p.Value.Members.Equals(members)))
        //{
        //    return Sessions.First(p => p.Value.Members.Equals(members)).Value;
        //}

        if (Sessions.Values.FirstOrDefault(p =>
        {
            foreach (var mem in members)
            {
                if (p.Members.Any(x => x.Uid == mem.Uid) is false) return false;
            }

            return true;
        }) is ChatSession session) { return session; }

        session = new ChatSession(members, OnMessageSentAsync);

        _logger.LogWarning("Created new Session {id}", session.Id);

        if (Sessions.TryAdd(session.Id, session) is false)
        {
            _logger.LogError("Session could not be added");

            //foreach (var member in session.Members)
            //{

            //}

            //foreach (var member in Users.Where(p => session.Members.Contains(p.Key)))
            //{
            //    member.Key.
            //}
        }

        return session;
    }

    private async Task OnMessageSentAsync(ChatSession session, ChatMessage message, CancellationToken cancellationToken)
    {
        // idea => 2 concepts: reference dialogs in chatservice / stream all over bus (unsafe)

        //foreach (var member in session.Members.Where(p => p.Uid != message.SenderId))
        //{
        //    dialog X: do something
        //}

        try
        {
            _ = _bus.PublishAsync(new ChatMessageReceived(session, message), default);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    }
}