using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using MimeKit;
using MTWorkHR.Application.Filters;
using MTWorkHR.Application.Identity;
using MTWorkHR.Application.Mapper;
using MTWorkHR.Application.Models;
using MTWorkHR.Application.Services;
using MTWorkHR.Core.Entities;
using MTWorkHR.Core.Entities.Base;
using MTWorkHR.Core.Global;
using MTWorkHR.Core.UnitOfWork;
using MTWorkHR.Infrastructure.Entities;
using System;
using System.ComponentModel.Design;
using System.Linq.Dynamic.Core.Tokenizer;
using System.Security.Claims;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;

namespace MTWorkHR.API.Chat
{
    [AppAuthorize]
    public class ChatHub : Hub
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly GlobalInfo _globalInfo;
        //private readonly UserService _userService; 
        private readonly ApplicationUserManager _userManager;
        private readonly ILogger<ChatHub> _logger;
        public ChatHub(IUnitOfWork unitOfWork, GlobalInfo globalInfo, ApplicationUserManager userManager /*, UserService userService*/, ILogger<ChatHub> logger) 
        {
            _unitOfWork = unitOfWork;
            _globalInfo = globalInfo;
            //  _userService = userService;
            _userManager = userManager;
            _logger = logger;
        }

        public async Task GetUsers(bool internalUsers)
        {
            var myCompanyUsers = await GetAllCompanyEmployees(internalUsers);

            await Clients.Caller.SendAsync("UpdateUserList", myCompanyUsers);
        }



        public async Task<List<ChatUserDto>> GetAllCompanyEmployees(bool internalUsers)
        {
            var response = new List<ChatUserDto>();

            try
            {
                // Get all employees with "Employee" and "Business" roles
                var employeesInRole = await _userManager.GetUsersInRoleAsync("Employee");
                var businessInRole = await _userManager.GetUsersInRoleAsync("Business");

                // Combine employee lists if needed and apply unique filtering based on IDs
                var allEmployees = employeesInRole.Union(businessInRole).DistinctBy(e => e.Id).ToList();

                var CompanyId = GetAuthenticatedCompanyId();
                var userId = Context.User.Identities.FirstOrDefault()?.FindFirst("uid")?.Value;

                // Get all message sender and receiver IDs excluding the current user
                var allMessages = await GetAllMessages();
                var companyUserIds = allMessages
                    .SelectMany(m => new[] { m.SenderId, m.ReceiverId })
                    .Where(id => id != userId)
                    .Distinct()
                    .ToHashSet();


                var employeesToChat = allEmployees
                       .Where(e => e.Id != userId && (e.CompanyId == CompanyId || companyUserIds.Contains(e.Id)));
                if (internalUsers)
                {
                    // Filter all employees by inside Company or if they have messaged the user
                    employeesToChat = employeesToChat
                        .Where(e => e.CompanyId == CompanyId)
                        .ToList();
                }
                else
                {// Filter all employees outside  user Company or if they have messaged the user
                    employeesToChat = employeesToChat
                      .Where(e => e.CompanyId != CompanyId)
                      .ToList();
                }
                // Filter all employees by CompanyId or if they have messaged the user
                //var employeesToChat = allEmployees
                //    .Where(e => e.Id != userId &&
                //               (e.CompanyId == CompanyId || companyUserIds.Contains(e.Id)))
                //    .ToList();

                // Get online users and map to a dictionary for faster lookups
                var allConnections = await _unitOfWork.HubConnection.GetAllAsync();
                var onlineUsers = allConnections.Item1.ToDictionary(c => c.UserId, c => c.SignalrId);

                // Retrieve last message data for each employee
                foreach (var emp in employeesToChat)
                {
                    var onlineSignalrId = onlineUsers.TryGetValue(emp.Id, out var signalrId) ? signalrId : null;
                    var isOnline = onlineSignalrId != null;
                    var profileImg = ""; // Assuming this is coming from another source if needed

                    // Retrieve last message and unseen count for the employee
                    var lastMessage = await GetLastMessage(emp.Id);

                    // Create ChatUserDto and add to response
                    response.Add(new ChatUserDto(
                        emp.Id,
                        $"{emp.FirstName} {emp.LastName}",
                        onlineSignalrId,
                        emp.Email,
                        isOnline,
                        profileImg,
                        lastMessage.Item2,
                        lastMessage.Item1?.Content,
                        lastMessage.Item1?.CreateDate,
                        emp.FirstName,
                        emp.LastName
                    ));
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, e.Message);
            }

            return response;
        }

        private long GetAuthenticatedCompanyId()
        {
            long CompanyIdLong = 0;
            var identity = Context.User?.Identities.FirstOrDefault();
            var CompanyId = identity!=null ? identity.FindFirst("companyId")?.Value : "0";
            
            if(CompanyId != null)
            {
                CompanyIdLong = long.Parse(CompanyId);
            }
            return CompanyIdLong;
        }


        // Get previous messages between two users
        public async Task GetPreviousMessages(string contactId)
        {
            var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
            var allmessages = await _unitOfWork.ChatMessage.GetAllWithChildrenAsync(userId, contactId);
            // Ensure the query is fully materialized before passing it to SignalR
            var messagesList = await allmessages.Item1.ToListAsync();

            await Clients.Caller.SendAsync("PreviousMessages", messagesList);
        }


        // Send a message from one user to another

        public async Task SendMessage(string receiverUserId, string msg)
        {
            try
            {
                var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
                var userName = Context.User.Identities.FirstOrDefault().FindFirst("name")?.Value;

                var allConnections = await _unitOfWork.HubConnection.GetAllAsync();
                var receiverUser = allConnections.Item1.FirstOrDefault(c => c.UserId == receiverUserId);
                var receiverconnIdDB = receiverUser != null ? receiverUser.SignalrId : "0";
                ChatMessage messag = new ChatMessage { 
                    Content = msg, 
                    ReceiverId = receiverUserId, 
                    ReceiverName = receiverUser?.UserName ?? "", 
                    SenderId = userId, 
                    SenderName = userName, 
                    IsSeen = false 
                };
                await _unitOfWork.ChatMessage.AddAsync(messag);
                await _unitOfWork.CompleteAsync();
                await Clients.Client(receiverconnIdDB).SendAsync("ReceiveMessage", messag);
                await Clients.Caller.SendAsync("ReceiveMessage", messag);
            }
            catch (Exception e) { _logger.LogError(e, e.Message); }
        }


        public async Task<IList<ChatMessage>> GetAllMessages()
        {
            var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
            var allmessages = await _unitOfWork.ChatMessage.GetAllUserMessagesAsync(userId);
            // Ensure the query is fully materialized before passing it to SignalR
            var messagesList = await allmessages.Item1.ToListAsync();
            return messagesList;
        }

        public async Task<Tuple<ChatMessage, int>> GetLastMessage(string contactId)
        {
            var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
            var allmessages = await _unitOfWork.ChatMessage.GetAllWithChildrenAsync(userId, contactId);
            // Ensure the query is fully materialized before passing it to SignalR
            var lastOne = allmessages.Item1.OrderByDescending(m=> m.Id).FirstOrDefault();
            var unseen = allmessages.Item1.Count(m=> m.IsSeen == false);
            return new Tuple<ChatMessage, int>( lastOne?? new ChatMessage(), unseen);
        }
        //----------------------------------------------------------------------
        //----------------------------------------------------------------------
        //----------------------------------------------------------------------
        public async Task<bool> MarkAsSeen(long messageId)
        {
            //var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
            var message = await _unitOfWork.ChatMessage.GetByIdAsync(messageId);
            message.IsSeen = true;
            await _unitOfWork.CompleteAsync();

            return true;
        }

        //Simple Test Method
        public async Task SendMessageAll(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }

        public async override Task OnDisconnectedAsync(Exception exception)
        {
            var email = Context.User?.FindFirst(ClaimTypes.Email)?.Value;
            var connections = await _unitOfWork.HubConnection.GetAllAsync(Context.ConnectionId);
            var currUserId = connections.Item1.Select(c => c.UserId).SingleOrDefault();
            await _unitOfWork.HubConnection.DeleteRangeAsync(connections.Item1.ToList());
            await _unitOfWork.CompleteAsync();
            await Clients.Others.SendAsync("userOff", currUserId);
            await base.OnDisconnectedAsync(exception);
        }
        public override async Task OnConnectedAsync()
        {
            try
            {
                string currSignalrID = Context.ConnectionId;
                var email = Context.User?.FindFirst(ClaimTypes.Email)?.Value;
                var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
                var userName = Context.User.Identities.FirstOrDefault().FindFirst("name")?.Value;

                if (userId != null) //if credentials are correct
                {
                    //--------------delete
                    var connections = await _unitOfWork.HubConnection.GetAllByUserAsync(userId);
                    await _unitOfWork.HubConnection.DeleteRangeAsync(connections.Item1.ToList());
                    await _unitOfWork.CompleteAsync();
                    //
                    HubConnection currUser = new HubConnection
                    {
                        UserId = userId,
                        UserName = userName,
                        SignalrId = currSignalrID,
                        TimeStamp = DateTime.UtcNow
                    };
                    var newConnection = await _unitOfWork.HubConnection.AddAsync(currUser);

                    await _unitOfWork.CompleteAsync();

                    ChatUserDto newUser = new ChatUserDto(userId, userName, currSignalrID);
                    await Clients.Caller.SendAsync("authMeResponseSuccess", newUser);//4Tutorial
                    await Clients.Others.SendAsync("userOn", newUser);//4Tutorial
                }

                else //if credentials are incorrect
                {
                    await Clients.Caller.SendAsync("authMeResponseFail");
                }
            }
            catch (Exception e) { _logger.LogError(e, e.Message); }

            await base.OnConnectedAsync();
        }

      

        public void logOut(string userId)
        {
            var userIdAuth = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
            var connections = _unitOfWork.HubConnection.GetAllAsync(Context.ConnectionId).Result;
           // var currUserId = connections.Item1.Select(c => c.UserId).SingleOrDefault();
            _unitOfWork.HubConnection.DeleteRangeAsync(connections.Item1.ToList());
            _unitOfWork.CompleteAsync();

            Clients.Caller.SendAsync("logoutResponse");
            Clients.Others.SendAsync("userOff", userIdAuth);
        }

      



        public async Task getOnlineUsers()
        {
            var allConnections = await _unitOfWork.HubConnection.GetAllAsync();
            var currUserId = allConnections.Item1.Where(c => c.SignalrId == Context.ConnectionId).Select(c => c.UserId).SingleOrDefault();
            List<ChatUserDto> onlineUsers = allConnections.Item1
                .Where(c => c.UserId != currUserId)
                .Select(c =>
                    new ChatUserDto(c.UserId, c.UserName, c.SignalrId)
                ).ToList();
            await Clients.Caller.SendAsync("getOnlineUsersResponse", onlineUsers);
        }

        public async Task authMe(PersonalInfo person)
        {
            try
            {
                string currSignalrID = Context.ConnectionId;
                //Person tempPerson = ctx.Person.Where(p => p.Username == personInfo.userName && p.Password == personInfo.password)
                //    .SingleOrDefault();
                var companyId = Context.User.Identities.FirstOrDefault().FindFirst("companyId")?.Value;
                var userId = Context.User.Identities.FirstOrDefault().FindFirst("uid")?.Value;
                var userName = Context.User.Identities.FirstOrDefault().FindFirst("name")?.Value;

                if (userId != null) //if credentials are correct
                {
                    Console.WriteLine("\n" + userName + " logged in" + "\nSignalrID: " + currSignalrID);

                    HubConnection currUser = new HubConnection
                    {
                        UserId = userId,
                        UserName = userName,
                        SignalrId = currSignalrID,
                        TimeStamp = DateTime.UtcNow
                    };
                    var newConnection = await _unitOfWork.HubConnection.AddAsync(currUser);

                    await _unitOfWork.CompleteAsync();

                    ChatUserDto newUser = new ChatUserDto(userId, userName, currSignalrID);
                    await Clients.Caller.SendAsync("authMeResponseSuccess", newUser);//4Tutorial
                    await Clients.Others.SendAsync("userOn", newUser);//4Tutorial
                }

                else //if credentials are incorrect
                {
                    await Clients.Caller.SendAsync("authMeResponseFail");
                }
            }
            catch (Exception e)
            {
                await Clients.Caller.SendAsync("authMeResponseFail");
            }
        }




        //public async Task reauthMe(string userId)
        //{
        //    string currSignalrID = Context.ConnectionId;
        //    //ApplicationUser tempPerson = ctx.Person.Where(p => p.Id == personId)
        //    //    .SingleOrDefault();

        //    if (userId == _globalInfo.UserId) //if credentials are correct
        //    {
        //        Console.WriteLine("\n" + _globalInfo.UserName + " logged in" + "\nSignalrID: " + currSignalrID);

        //        HubConnection currUser = new HubConnection
        //        {
        //            UserId = _globalInfo.UserId,
        //            SignalrId = currSignalrID,
        //            TimeStamp = DateTime.UtcNow
        //        };
        //        var newConnection = await _unitOfWork.HubConnection.AddAsync(currUser);

        //        await _unitOfWork.CompleteAsync();

        //        ChatUserDto newUser = new ChatUserDto(_globalInfo.UserId, _globalInfo.UserName, currSignalrID);

        //        await Clients.Caller.SendAsync("reauthMeResponse", newUser);//4Tutorial
        //        await Clients.Others.SendAsync("userOn", newUser);//4Tutorial
        //    }
        //} //end of reauthMe


    }

    public class PersonalInfo
    {
        public string userName { get; set; }
        public string password { get; set; }
        public string userId { get; set; }
    }
}
