using Application.Abstractions.Chat;
using Application.Abstractions.Data;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.JsonWebTokens;
using SharedKernel.Extensions;
using System.Collections.Concurrent;

namespace Web.Api.Hubs;

public sealed class ChatHub(IChatMessageStore messageStore, IChatConnectionTracker tracker, IServiceScopeFactory scopeFactory) : Hub<IChatHubClient>
{
    private static readonly ConcurrentDictionary<string, DateTime> _lastMessageTime = new();

    public override async Task OnConnectedAsync()
    {
        var messages = await messageStore.GetRecentMessagesAsync(ChatSettings.MaxMessages);
        await Clients.Caller.ReceiveHistory(messages);

        var ip = Context.GetHttpContext()?.GetClientIP() ?? "Unknown";
        var ua = Context.GetHttpContext()?.GetUserAgent() ?? "Unknown";

        ConnectedUser user;

        if (Context.User?.Identity?.IsAuthenticated == true)
        {
            var memberID = GetMemberID();
            var memberName = GetMemberName();

            string? email = null;
            if (memberID.HasValue)
            {
                using (var scope = scopeFactory.CreateScope())
                {
                    var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
                    email = await db.Member.AsNoTracking().Where(x => x.ID == memberID.Value).Select(x => x.Email).FirstOrDefaultAsync();
                }
            }

            user = new ConnectedUser(Context.ConnectionId, memberID, email, memberName, ip, ua, false, DateTime.UtcNow);

            if (!string.IsNullOrEmpty(memberName))
            {
                await Clients.Caller.Connected($"{memberName}님, 환영합니다.");
                await Clients.Others.ReceiveSystemMessage($"{memberName}님이 입장했습니다.");
            }
        }
        else
        {
            user = new ConnectedUser(Context.ConnectionId, null, null, null, ip, ua, true, DateTime.UtcNow);
        }

        Context.Items["user"] = user;

        await tracker.AddAsync(user);
        await BroadcastParticipantCountAsync();
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        _lastMessageTime.TryRemove(Context.ConnectionId, out _);
        await tracker.RemoveAsync(Context.ConnectionId);

        if (Context.Items["user"] is ConnectedUser user && !user.IsGuest)
        {
            await Clients.Others.ReceiveSystemMessage($"{user.MemberName}님이 퇴장했습니다.");
        }

        await BroadcastParticipantCountAsync();
        await base.OnDisconnectedAsync(exception);
    }

    /// <summary>
    /// 클라이언트가 로그아웃 시 호출 (invoke('Logout'))
    /// </summary>
    public async Task Logout()
    {
        await tracker.RemoveAsync(Context.ConnectionId);

        if (Context.Items["user"] is ConnectedUser user && !user.IsGuest)
        {
            await Clients.Others.ReceiveSystemMessage($"{user.MemberName}님이 퇴장했습니다.");
        }

        await Clients.Caller.Logout("로그아웃 되었습니다.");
    }

    /// <summary>
    /// 채팅 메시지 전송 시
    /// </summary>
    public async Task SendMessage(string content)
    {
        if (string.IsNullOrWhiteSpace(content))
        {
            return;
        }

        content = content.Trim();

        if (content.Length > ChatSettings.MaxContentLength)
        {
            return;
        }

        var now = DateTime.UtcNow;
        if (_lastMessageTime.TryGetValue(Context.ConnectionId, out var lastTime))
        {
            if ((now - lastTime).TotalSeconds < ChatSettings.RateLimitSeconds)
            {
                return;
            }
        }
        _lastMessageTime[Context.ConnectionId] = now;

        var memberID = GetMemberID();
        if (memberID is null)
        {
            return;
        }

        ChatMessage message;

        using (var scope = scopeFactory.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
            var member = await db.Member.AsNoTracking().Where(x => x.ID == memberID.Value).Select(x => new { x.SID, x.Name }).FirstOrDefaultAsync();
            if (member is null)
            {
                return;
            }

            message = new ChatMessage(
               memberID.Value,
               member.SID,
               member.Name ?? "익명",
               content,
               now
           );
        }

        // 채팅 기록 저장
        await messageStore.AddMessageAsync(message);

        // 모든 클라이언트에게 메시지 전송
        await Clients.All.ReceiveMessage(message);
    }

    // 회원 ID 조회
    private int? GetMemberID()
    {
        var sub = Context.User?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value;

        if (int.TryParse(sub, out var memberID))
        {
            return memberID;
        }

        return null;
    }

    // 회원 이름 조회
    private string? GetMemberName()
    {
        return Context.User?.FindFirst(JwtRegisteredClaimNames.Name)?.Value;
    }

    /// <summary>
    /// 클라이언트가 채팅 기록 요청 시 호출
    /// </summary>
    public async Task RequestHistory()
    {
        var messages = await messageStore.GetRecentMessagesAsync(ChatSettings.MaxMessages);
        await Clients.Caller.ReceiveHistory(messages);
    }

    /// <summary>
    /// 클라이언트가 접속자 수 요청 시 호출
    /// </summary>
    public async Task RequestParticipantCount()
    {
        var users = await tracker.GetAllAsync();
        await Clients.Caller.ReceiveParticipantCount(users.Count);
    }

    /// <summary>
    /// 클라이언트가 참여자 목록 요청 시 호출
    /// </summary>
    public async Task RequestParticipants()
    {
        var users = await tracker.GetAllAsync();
        var participants = users
            .Where(u => !u.IsGuest && !string.IsNullOrEmpty(u.MemberName))
            .Select(u => new ChatParticipant(u.MemberName!, u.IsGuest))
            .DistinctBy(p => p.MemberName)
            .OrderBy(p => p.MemberName)
            .ToList();
        await Clients.Caller.ReceiveParticipants(participants);
    }

    // 전체 접속자 수 브로드캐스트
    private async Task BroadcastParticipantCountAsync()
    {
        var users = await tracker.GetAllAsync();
        await Clients.All.ReceiveParticipantCount(users.Count);
    }
}
