using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Conversations.Application.Abstractions;
using Knot.Contracts.Conversations.Domain;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Modules.Conversations.Application.DTOs;
using Knot.Shared.Kernel;
using MediatR;

namespace Knot.Modules.Conversations.Application.Messages.GetMessages;

public record GetMessagesQuery(Guid UserId, Guid ChatId, string? Cursor, long? Pivot = null, long? AfterSequenceId = null, int? Limit = null) : IQuery<List<MessageDetailDto>>;

internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery, List<MessageDetailDto>>
{
    private readonly IMessageRepository _messageRepository;
    private readonly IUserDisplayNameProvider _userProvider;
    private readonly IChatRepository _chatRepository;
    private readonly IMessageReactionRepository _reactionRepository;

    public GetMessagesQueryHandler(IMessageRepository messageRepository, IUserDisplayNameProvider userProvider, IChatRepository chatRepository, IMessageReactionRepository reactionRepository)
    {
        _messageRepository = messageRepository;
        _userProvider = userProvider;
        _chatRepository = chatRepository;
        _reactionRepository = reactionRepository;
    }

    public async Task<Result<List<MessageDetailDto>>> Handle(GetMessagesQuery request, CancellationToken cancellationToken)
    {
        var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
        if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
        {
            return Result.Failure<List<MessageDetailDto>>(ChatErrors.ChatsForbidden);
        }

        List<Message> messages;
        int queryLimit = request.Limit ?? ChatConstants.DefaultMessageQueryLimit;

        if (request.AfterSequenceId.HasValue)
        {
            // Получаем только сообщения ПОСЛЕ указанного sequenceId (для синхронизации)
            messages = await _messageRepository.GetChatMessagesAfterAsync(request.ChatId, request.AfterSequenceId.Value, queryLimit, cancellationToken);
        }
        else if (request.Pivot.HasValue)
        {
            messages = await _messageRepository.GetChatMessagesAroundAsync(request.ChatId, request.Pivot.Value, queryLimit, cancellationToken);
        }
        else
        {
            DateTime? cursorDate = null;
            long? cursorSequenceId = null;

            if (!string.IsNullOrEmpty(request.Cursor))
            {
                if (long.TryParse(request.Cursor, out var seqId))
                {
                    cursorSequenceId = seqId;
                }
                else if (DateTime.TryParse(request.Cursor, null, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed))
                {
                    cursorDate = parsed.ToUniversalTime();
                }
            }

            messages = await _messageRepository.GetChatMessagesCursorAsync(request.ChatId, cursorDate, cursorSequenceId, queryLimit, cancellationToken);
        }

        var result = new List<MessageDetailDto>();
        var userIdsToFetch = new HashSet<Guid>();
        var replyMessages = new Dictionary<Guid, Message>();

        // Filter messages that are deleted for the current user before processing
        var filteredMessages = messages.Where(m => !m.IsDeletedForUser(request.UserId)).ToList();

        foreach (var m in filteredMessages)
        {
            userIdsToFetch.Add(m.SenderId);

            if (m is PollMessage poll && !poll.IsAnonymous)
            {
                foreach (var vote in poll.Votes)
                {
                    userIdsToFetch.Add(vote.UserId);
                }
            }

            if (!m.ReplyToId.HasValue)
            {
                continue;
            }

            var replyMsg = await _messageRepository.GetByIdAsync(m.ReplyToId.Value, cancellationToken);
            if (replyMsg == null)
            {
                continue;
            }

            replyMessages[replyMsg.Id] = replyMsg;
            userIdsToFetch.Add(replyMsg.SenderId);
        }

        var senders = await _userProvider.GetUsersInfoAsync(userIdsToFetch, cancellationToken);

        var messageIds = filteredMessages.Select(m => m.Id).ToList();
        var allReactions = await _reactionRepository.GetReactionsForMessagesAsync(messageIds, cancellationToken);
        var reactionsByMessage = allReactions.GroupBy(r => r.MessageId).ToDictionary(g => g.Key, g => g.ToList());

        foreach (var message in messages)
        {
            if (message.IsDeletedForUser(request.UserId))
            {
                continue;
            }

            senders.TryGetValue(message.SenderId, out var sender);
            reactionsByMessage.TryGetValue(message.Id, out var reactions);


            Message? replyMsg = null;
            if (message.ReplyToId.HasValue)
            {
                replyMessages.TryGetValue(message.ReplyToId.Value, out replyMsg);
            }

            UserInfo? replySender = null;
            if (replyMsg != null)
            {
                senders.TryGetValue(replyMsg.SenderId, out replySender);
            }

            result.Add(new MessageDetailDto(
                message.Id,
                message.ChatId,
                message.SenderId,
                message.Content,
                message.Type.ToLower(),
                message.ReplyToId,
                replyMsg != null ? new ReplyToMessageDto(
                    replyMsg.Id,
                    replyMsg.Content,
                    replyMsg.IsDeleted,
                    replyMsg is MediaMessage mm ? mm.Media.Select(m => new MediaDto(m.Id, m.Type, m.Url, m.Filename, m.Size, m.Duration)).ToList() : new List<MediaDto>(),
                    replySender != null ? new MessageSenderDto(replySender.Id, replySender.Username, replySender.DisplayName, replySender.Avatar) : null
                ) : null,
                (message as TextMessage)?.Quote,
                message.IsEdited,
                message.IsDeleted,
                message.CreatedAt,
                message.SequenceId,
                message.ForwardedFromId,
                null, // ForwardedFrom details not implemented here yet
                (message as StoryMessage)?.StoryId,
                (message as StoryMessage)?.StoryMediaUrl,
                (message as StoryMessage)?.StoryMediaType,
                (message as MediaMessage)?.Media.Select(m => new MediaDto(m.Id, m.Type, m.Url, m.Filename, m.Size, m.Duration)).ToList() ?? new List<MediaDto>(),
                sender != null ? new MessageSenderDto(sender.Id, sender.Username, sender.DisplayName, sender.Avatar) : new MessageSenderDto(message.SenderId, "unknown", "Unknown", null),
                message.ReadByUsers.Select(id => new ReadByDto(id)).ToList(),
                reactions?.Select(r =>
                {
                    senders.TryGetValue(r.UserId, out var ru);
                    return new MessageReactionDto(r.Id, r.Emoji, r.UserId, ru != null ? new MessageSenderDto(ru.Id, ru.Username, ru.DisplayName, ru.Avatar) : null);
                }).ToList() ?? new List<MessageReactionDto>(),
                (message as CallMessage)?.CallType,
                (message as CallMessage)?.CallStatus,
                (message as CallMessage)?.Duration,
                (message as PollMessage)?.Options.Select(o =>
                {
                    var pm = (PollMessage)message;
                    var voters = pm.IsAnonymous == false
                         ? pm.Votes
                             .Where(v => v.OptionId == o.Id)
                             .Select(v =>
                             {
                                 senders.TryGetValue(v.UserId, out var vu);
                                 return vu != null
                                     ? new MessageSenderDto(vu.Id, vu.Username, vu.DisplayName, vu.Avatar)
                                     : new MessageSenderDto(v.UserId, "unknown", "Unknown", null);
                             })
                             .ToList()
                         : null;
                    return new PollOptionDto(o.Id, o.Text, o.VoteCount, voters, pm.IsAnonymous == false ? pm.Votes.Where(v => v.OptionId == o.Id).Select(v => v.UserId).ToList() : null);
                }).ToList(),
                 (message as PollMessage)?.IsMultipleChoice,
                 (message as PollMessage)?.IsAnonymous,
                 (message as PollMessage)?.IsClosed,
                 (message as PollMessage)?.Votes.Where(v => v.UserId == request.UserId).Select(v => v.OptionId).ToList(),
                 message.IsDeletedForUser(request.UserId)
             ));
        }

        return Result.Success(result);
    }
}
