using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Auth.Domain;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Contracts.Settings.Application.Abstractions;
using Knot.Contracts.Settings.Application.DTOs;
using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Constants;
using MediatR;

namespace Host.Application.Federation.Commands;

public record InboundFederationCommand(FederationMessagePacket Packet) : ICommand;

internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundFederationCommand>
{
    private readonly ISettingsService _settingsService;
    private readonly IMessageRepository _messageRepository;
    private readonly IUserRepository _userRepository;
    private readonly IMessageReactionRepository _reactionRepository;
    private readonly IMessageNotifier _notifier;
    private readonly IMediator _mediator;

    public InboundFederationCommandHandler(
        ISettingsService settingsService,

        IMessageRepository messageRepository,
        IUserRepository userRepository,
        IMessageReactionRepository reactionRepository,
        IMessageNotifier notifier,
        IMediator mediator)
    {
        _settingsService = settingsService;
        _messageRepository = messageRepository;
        _userRepository = userRepository;
        _reactionRepository = reactionRepository;
        _notifier = notifier;
        _mediator = mediator;
    }

    public async Task<Result> Handle(InboundFederationCommand request, CancellationToken cancellationToken)
    {
        var settings = await _settingsService.GetSettingsAsync(cancellationToken);
        if (!settings.Federation.Enabled)
            return Result.Failure(new Error(Errors.DisabledByAdmin, "Federation disabled."));

        var packet = request.Packet;

        // 1. ��������� ������� �����������
        var senderConfig = settings.Federation.AllowedDomains.FirstOrDefault(d => d.IsEnabled && d.Domain.Equals(packet.SenderDomain, StringComparison.OrdinalIgnoreCase));
        if (senderConfig == null || string.IsNullOrEmpty(senderConfig.PublicKey))
            return Result.Failure(new Error("Federation.SenderNotAllowed", "Sender domain not in allowlist."));

        using (var rsaVerify = RSA.Create())
        {
            rsaVerify.ImportRSAPublicKey(Convert.FromBase64String(senderConfig.PublicKey), out _);
            var dataToVerify = Encoding.UTF8.GetBytes(packet.EncryptedPayload + packet.SenderDomain + packet.RecipientDomain);
            if (!rsaVerify.VerifyData(dataToVerify, Convert.FromBase64String(packet.Signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
            {
                return Result.Failure(new Error("Federation.InvalidSignature", "RSA Signature verification failed."));
            }
        }

        // 2. �������������� IV ����� Private Key
        byte[] aesKey;
        byte[] aesIv;
        using (var rsaDecrypt = RSA.Create())
        {
            rsaDecrypt.ImportPkcs8PrivateKey(Convert.FromBase64String(settings.Federation.PrivateKey!), out _);
            var decryptedKeys = rsaDecrypt.Decrypt(Convert.FromBase64String(packet.EncryptedIV), RSAEncryptionPadding.Pkcs1);

            // AES-256 Key (32 bytes) + IV (16 bytes)

            aesKey = new byte[32];
            aesIv = new byte[16];
            Buffer.BlockCopy(decryptedKeys, 0, aesKey, 0, 32);
            Buffer.BlockCopy(decryptedKeys, 32, aesIv, 0, 16);
        }

        // 3. �������������� ���� ��������� (AES-256)
        string plainText;
        using (var aes = Aes.Create())
        {
            aes.Key = aesKey;
            aes.IV = aesIv;
            using (var decryptor = aes.CreateDecryptor())
            {
                var cipherBytes = Convert.FromBase64String(packet.EncryptedPayload);
                var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
                plainText = Encoding.UTF8.GetString(plainBytes);
            }
        }

        // 4. ������ ��������� ����� ���������
        if (packet.Metadata.MessageType == "sync_capabilities")
        {
            var capabilities = JsonSerializer.Deserialize<RemoteCapabilities>(plainText);
            if (capabilities != null && senderConfig != null)
            {
                senderConfig.Capabilities = capabilities;
                await _settingsService.UpdateSettingsAsync(settings, cancellationToken);
            }
            return Result.Success();
        }

        if (packet.Metadata.MessageType == "presence_update")
        {
            var statusData = JsonSerializer.Deserialize<JsonElement>(plainText);
            var isOnline = statusData.GetProperty("IsOnline").GetBoolean();


            var user = await _userRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken);
            if (user != null && user.IsExternal)
            {
                user.IsOnline = isOnline;
                await _userRepository.UpdateAsync(user, cancellationToken);

                // ���������� ��������� ������������� ����� SignalR

                await _notifier.NotifyNewMessageAsync(Guid.Empty, new { type = "presence_update", userId = user.Id, isOnline = user.IsOnline }, cancellationToken);
            }
            return Result.Success();
        }

        if (packet.Metadata.MessageType == "message_edited")
        {
            var messageToEdit = await _messageRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken);
            if (messageToEdit != null)
            {
                messageToEdit.Edit(plainText);
                await _messageRepository.UpdateAsync(messageToEdit, cancellationToken);
                await _notifier.NotifyNewMessageAsync(messageToEdit.ChatId, new { type = "message_edited", messageId = messageToEdit.Id, content = plainText }, cancellationToken);
            }
            return Result.Success();
        }

        if (packet.Metadata.MessageType == "message_deleted")
        {
            var messageToDelete = await _messageRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken);
            if (messageToDelete != null)
            {
                messageToDelete.Delete();
                await _messageRepository.UpdateAsync(messageToDelete, cancellationToken);
                await _notifier.NotifyNewMessageAsync(messageToDelete.ChatId, new { type = "message_deleted", messageId = messageToDelete.Id }, cancellationToken);
            }
            return Result.Success();
        }

        if (packet.Metadata.MessageType == "reaction_added" || packet.Metadata.MessageType == "reaction_removed")
        {
            var messageForReaction = await _messageRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken);
            if (messageForReaction != null)
            {
                if (packet.Metadata.MessageType == "reaction_added")
                {
                    var reaction = new MessageReaction(messageForReaction.Id, packet.Metadata.UserId, plainText);
                    await _reactionRepository.AddAsync(reaction, cancellationToken);
                }
                else
                {
                    await _reactionRepository.RemoveAsync(messageForReaction.Id, packet.Metadata.UserId, plainText, cancellationToken);
                }


                await _notifier.NotifyNewMessageAsync(messageForReaction.ChatId, new
                {

                    type = packet.Metadata.MessageType,

                    messageId = messageForReaction.Id,

                    userId = packet.Metadata.UserId,

                    emoji = plainText

                }, cancellationToken);
            }
            return Result.Success();
        }

        if (packet.Metadata.MessageType == "rtc_signal")
        {
            await _notifier.NotifyNewMessageAsync(packet.Metadata.ChatId, new { type = "rtc_signal", payload = plainText, senderId = packet.Metadata.SenderId }, cancellationToken);
            return Result.Success();
        }

        if (packet.Metadata.MessageType == "poll" && !settings.Messages.AllowPolls)
            return Result.Failure(new Error("Federation.PollsDisabled", "We do not accept polls."));

        // 5. ���������� � ���� ��������� (MongoDB)
        var message = new TextMessage(
            Guid.NewGuid(),

            packet.Metadata.ChatId,

            packet.Metadata.SenderId,
            plainText,

            null,
            null,
            null,
            packet.Metadata.CreatedAt,

            false);


        _messageRepository.Add(message);

        // 6. ����������� ������������ ����� SignalR
        await _notifier.NotifyNewMessageAsync(packet.Metadata.ChatId, message, cancellationToken);

        return Result.Success();
    }
}
