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

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

public sealed record VotePollCommand(
    Guid MessageId,
    Guid ChatId,
    Guid UserId,
    Guid OptionId) : ICommand;

public sealed class VotePollCommandHandler : ICommandHandler<VotePollCommand>
{
    private readonly IMessageRepository _messageRepository;
    private readonly IChatRepository _chatRepository;
    private readonly IChatsUnitOfWork _unitOfWork;
    private readonly IMessageNotifier _notifier;
    private readonly IUserDisplayNameProvider _userProvider;

    public VotePollCommandHandler(
        IMessageRepository messageRepository, 
        IChatRepository chatRepository,
        IChatsUnitOfWork unitOfWork,
        IMessageNotifier notifier,
        IUserDisplayNameProvider userProvider)
    {
        _messageRepository = messageRepository;
        _chatRepository = chatRepository;
        _unitOfWork = unitOfWork;
        _notifier = notifier;
        _userProvider = userProvider;
    }

    public async Task<Result> Handle(VotePollCommand request, CancellationToken cancellationToken)
    {
        var message = await _messageRepository.GetByIdAsync(request.MessageId, cancellationToken);
        if (message is not PollMessage poll) return Result.Failure(new Error("Poll.NotFound", "Poll not found"));

        if (poll.IsClosed) return Result.Failure(new Error("Poll.Closed", "This poll is closed."));

        var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
        if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId)) return Result.Failure(ChatErrors.ChatsForbidden);

        var targetOption = poll.Options.FirstOrDefault(o => o.Id == request.OptionId);
        if (targetOption == null) return Result.Failure(new Error("Poll.InvalidOption", "Invalid option ID."));

        // Prevent duplicate or changed votes
        var existingVote = poll.Votes.FirstOrDefault(v => v.UserId == request.UserId && v.OptionId == request.OptionId);
        if (existingVote != null) return Result.Failure(new Error("Poll.AlreadyVoted", "You have already voted for this option."));

        if (!poll.IsMultipleChoice)
        {
            var hasVotedInThisPoll = poll.Votes.Any(v => v.UserId == request.UserId);
            if (hasVotedInThisPoll) return Result.Failure(new Error("Poll.AlreadyVoted", "You have already voted in this poll."));
        }

        poll.Votes.Add(new PollVote { UserId = request.UserId, OptionId = request.OptionId, VotedAt = DateTime.UtcNow });
        targetOption.VoteCount++;

        await _messageRepository.UpdateAsync(poll, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);

        // Notify updated poll
        var voterIds = poll.Votes.Select(v => v.UserId).Distinct().ToList();
        var votersInfo = poll.IsAnonymous == false 
            ? await _userProvider.GetUsersInfoAsync(voterIds, cancellationToken)
            : new Dictionary<Guid, UserInfo>();

        await _notifier.NotifyMessageUpdateAsync(poll.ChatId, "poll_updated", new
        {
            id = poll.Id,
            chatId = poll.ChatId,
            senderId = poll.SenderId,
            createdAt = poll.CreatedAt,
            type = "poll",
            content = poll.Content,
            pollOptions = poll.Options.Select(o => new { 
                id = o.Id, 
                text = o.Text, 
                voteCount = o.VoteCount,
                voters = poll.IsAnonymous == false
                    ? poll.Votes.Where(v => v.OptionId == o.Id)
                        .Select(v => {
                            votersInfo.TryGetValue(v.UserId, out var vu);
                            return vu != null 
                                ? new { id = vu.Id, username = vu.Username, displayName = vu.DisplayName, avatar = vu.Avatar }
                                : new { id = v.UserId, username = "unknown", displayName = "Unknown", avatar = (string?)null };
                        }).ToList()
                    : null,
                voterIds = poll.IsAnonymous == false
                    ? poll.Votes.Where(v => v.OptionId == o.Id).Select(v => v.UserId).ToList()
                    : null
            }).ToList(),
            pollIsMultipleChoice = poll.IsMultipleChoice,
            pollIsClosed = poll.IsClosed,
            pollIsAnonymous = poll.IsAnonymous
        }, cancellationToken);

        return Result.Success();
    }
}
