using Knot.Contracts.Conversations.Application.Abstractions;
using Knot.Contracts.Conversations.Domain;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Contracts.Settings.Application.Abstractions;
using Knot.Shared.Kernel;

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

/// <summary>
/// ������� ��� �������� ��������� � ���.
/// </summary>
public record AttachmentRequest(string Type, string Url, string? FileName, long? FileSize);

public sealed record SendMessageCommand(
    Guid ChatId,
    Guid SenderId,
    string? Content,
    string Type,
    List<AttachmentRequest>? Attachments = null,
    Guid? ReplyToId = null,
    string? Quote = null,
    Guid? ForwardedFromId = null,
    Guid? StoryId = null,
    string? StoryMediaUrl = null,
    string? StoryMediaType = null,
    List<string>? PollOptions = null,
    bool? PollIsAnonymous = null,
    bool? PollAllowMultipleAnswers = null,
    DateTime? PollExpiresAt = null,
    string? CallType = null,
    string? CallStatus = null,
    int? Duration = null) : ICommand<Guid>;

public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageCommand, Guid>
{
    private readonly IChatRepository _chatRepository;
    private readonly IMessageRepository _messageRepository;
    private readonly IChatsUnitOfWork _unitOfWork;
    private readonly MediatR.IMediator _mediator;
    private readonly IMessagesSettings _messagesSettings;

    public SendMessageCommandHandler(
        IChatRepository chatRepository,
        IMessageRepository messageRepository,
        IChatsUnitOfWork unitOfWork,
        MediatR.IMediator mediator,
        IMessagesSettings messagesSettings)
    {
        _chatRepository = chatRepository;
        _messageRepository = messageRepository;
        _unitOfWork = unitOfWork;
        _mediator = mediator;
        _messagesSettings = messagesSettings;
    }

    public async Task<Result<Guid>> Handle(SendMessageCommand request, CancellationToken cancellationToken)
    {
        // 1. ��������� ������������� ����
        var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
        if (chat is null)
        {
            return Result.Failure<Guid>(ChatErrors.ChatsNotFound);
        }

        // 2. ���������, �������� �� ����������� ����������
        if (!chat.Members.Any(m => m.UserId == request.SenderId))
        {
            return Result.Failure<Guid>(ChatErrors.ChatsForbidden);
        }

        // 3. ������� ���������
        Message message;
        if (request.Type == "story_reply" || request.Type == "story_reaction")
        {
            if (!_messagesSettings.Current.AllowMedia) return Result.Failure<Guid>(ChatErrors.MediaDisabled);


            var parsedStoryMediaType = Enum.TryParse<MediaType>(request.StoryMediaType, true, out var sTypeEnum) ? sTypeEnum : MediaType.Image;
            message = new StoryMessage(
                Guid.NewGuid(),

                request.ChatId,

                request.SenderId,

                request.StoryId ?? Guid.Empty,

                request.StoryMediaUrl ?? string.Empty,
                request.StoryMediaType,

                request.Content,

                request.ReplyToId,

                request.ForwardedFromId,

                DateTime.UtcNow,

                false);
        }
        else if (request.Attachments != null && request.Attachments.Any())
        {
            if (!_messagesSettings.Current.AllowMedia) return Result.Failure<Guid>(ChatErrors.MediaDisabled);

            var firstAtt = request.Attachments.First();
            var parsedType = Enum.TryParse<MediaType>(firstAtt.Type, true, out var mTypeEnum) ? mTypeEnum : MediaType.File;


            message = new MediaMessage(
                Guid.NewGuid(),

                request.ChatId,

                request.SenderId,
                parsedType,

                request.Content,

                request.ReplyToId,

                request.ForwardedFromId,

                DateTime.UtcNow,

                false);


            foreach (var att in request.Attachments)
            {
                var pType = Enum.TryParse<MediaType>(att.Type, true, out var tEnum) ? tEnum : MediaType.File;
                ((MediaMessage)message).AddMedia(pType.ToString().ToLower(), att.Url, att.FileName, att.FileSize);
            }
        }
        else if (request.Type == "poll")
        {
            if (!_messagesSettings.Current.AllowPolls) return Result.Failure<Guid>(ChatErrors.PollsDisabled);
            if (chat.Type != ChatType.Group) return Result.Failure<Guid>(new Error("Poll.InvalidChat", "Polls are only allowed in groups."));

            message = PollMessage.Create(
                Guid.NewGuid(),
                request.ChatId,
                request.SenderId,
                request.Content ?? "Poll",
                request.PollOptions ?? new List<string>(),
                request.PollIsAnonymous ?? true,
                request.PollAllowMultipleAnswers ?? false,
                request.PollExpiresAt,
                request.ReplyToId,
                request.ForwardedFromId);
        }
        else if (request.Type == "call")
        {
            message = new CallMessage(
                Guid.NewGuid(),
                request.ChatId,
                request.SenderId,
                request.CallType ?? "voice",
                request.CallStatus ?? "completed",
                request.Duration,
                request.ReplyToId,
                request.ForwardedFromId,
                DateTime.UtcNow,
                false);
        }
        else
        {
            message = new TextMessage(
                Guid.NewGuid(),

                request.ChatId,

                request.SenderId,

                request.Content ?? string.Empty,

                request.ReplyToId,

                request.Quote,

                request.ForwardedFromId,

                DateTime.UtcNow,

                false);
        }

        // 4. ������������������ ��������� High-Water Mark
        chat.IncrementSequenceId();
        message.SetSequenceId(chat.LastMessageSequenceId);

        var senderMember = chat.Members.First(m => m.UserId == request.SenderId);
        senderMember.UpdateReadCursor(message.Id, message.SequenceId);
        senderMember.UpdateDeliveredCursor(message.Id);
        message.MarkAsRead(request.SenderId); // Отправитель всегда "прочитал" своё сообщение

        // 5. ���������
        _messageRepository.Add(message);
        await _unitOfWork.SaveChangesAsync(cancellationToken);

        await _mediator.Publish(new MessageSentDomainEvent(
            message.Id,

            message.ChatId,

            message.SenderId,

            message.Content),

            cancellationToken);

        return Result.Success(message.Id);
    }
}
