using ConversationMetadata = Noctalk.Shared.Chat.Api.Json.Admin_client__to__server.Command.Conversation__metadata;
using ChatMessage = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Message;
using ChatReceive = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Receive;
using ChatSend = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send;
using SendAndReceiveConversation = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__conversation;
using SendAndReceiveConversationResult = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__conversation_result;
using SendAndReceiveReceive = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__receive;
using enum__Message_delivery = Noctalk.Shared.Chat.Encryption.enum__Message_delivery;

namespace Noctalk_server.Server.Conversations;

public sealed class Conversation_store
{
    private readonly object conversationStoreLock = new();
    private readonly Dictionary<Guid, Conversation_state> conversations = [];
    private readonly string metadataDirectory;

    public Conversation_store(string The_executable_directory)
    {
        metadataDirectory = Path.Combine(The_executable_directory, "Data", "Metadata", "Conversations");
        Directory.CreateDirectory(metadataDirectory);
        Conversations__metadata__load_all();
    }

    public bool Conversations__create(Guid Conversation_guid)
    {
        lock (conversationStoreLock)
        {
            if (conversations.ContainsKey(Conversation_guid))
            {
                return false;
            }

            conversations[Conversation_guid] = new Conversation_state
            {
                Metadata = new ConversationMetadata
                {
                    Conversation_guid = Conversation_guid,
                    Conversation__metadata__date__created = DateTime.UtcNow,
                    Pending_messages__count = 0,
                    Client_A__pending_message_count = 0,
                    Client_A__last_sent__date = null,
                    Client_B__pending_message_count = 0,
                    Client_B__last_sent__date = null
                }
            };

            Conversations__metadata__write(conversations[Conversation_guid].Conversations__metadata__copy());

            return true;
        }
    }

    public List<ConversationMetadata> Conversations__list()
    {
        lock (conversationStoreLock)
        {
            return conversations.Values
                .OrderBy(The_conversation => The_conversation.Metadata.Conversation__metadata__date__created)
                .Select(The_conversation => The_conversation.Conversations__metadata__copy())
                .ToList();
        }
    }

    public bool Conversations__delete(Guid Conversation_guid)
    {
        lock (conversationStoreLock)
        {
            bool deleted = conversations.Remove(Conversation_guid);

            if (deleted)
            {
                string metadataPath = Conversations__metadata__path(Conversation_guid);
                if (File.Exists(metadataPath))
                {
                    File.Delete(metadataPath);
                }
            }

            return deleted;
        }
    }

    public ChatReceive Conversations__poll(ChatSend The_send)
    {
        lock (conversationStoreLock)
        {
            if (!conversations.TryGetValue(The_send.Conversation_guid, out Conversation_state? conversation))
            {
                return new ChatReceive
                {
                    Flag__success = false,
                    Flag__conversation_does_not_exists = true,
                    Messages = []
                };
            }

            conversation.Conversations__register_client(The_send.Client_guid);

            if (The_send.Flag__delete__history)
            {
                conversation.Conversations__clear_history();
                Conversations__metadata__write(conversation.Conversations__metadata__copy());
            }

            if (The_send.Flag__conversation__has_been_opened)
            {
                conversation.Conversations__mark_messages_to_client_read(The_send.Client_guid);
            }

            if (The_send.Message is not null)
            {
                if (conversation.Conversations__is_sender_locked(The_send.Client_guid))
                {
                    List<ChatMessage> lockedReceiveMessages = conversation.Conversations__receive(The_send.Client_guid);
                    if (lockedReceiveMessages.Count > 0)
                    {
                        Conversations__metadata__write(conversation.Conversations__metadata__copy());
                    }
                    if (The_send.Flag__conversation__has_been_opened)
                    {
                        conversation.Conversations__mark_messages_to_client_read(The_send.Client_guid);
                    }

                return new ChatReceive
                {
                    Flag__success = false,
                    Flag__send_locked = true,
                    Message_delivery__from_this_client_to_peer = conversation.Conversations__delivery_from_this_client_to_peer(The_send.Client_guid),
                    Flag__peer_online = conversation.Conversations__is_peer_online(The_send.Client_guid),
                    Messages = lockedReceiveMessages
                };
                }

                conversation.Conversations__send(The_send.Client_guid, The_send.Message);
                Conversations__metadata__write(conversation.Conversations__metadata__copy());
            }

            List<ChatMessage> receiveMessages = conversation.Conversations__receive(The_send.Client_guid);
            if (receiveMessages.Count > 0)
            {
                Conversations__metadata__write(conversation.Conversations__metadata__copy());
            }
            if (The_send.Flag__conversation__has_been_opened)
            {
                conversation.Conversations__mark_messages_to_client_read(The_send.Client_guid);
            }

            return new ChatReceive
            {
                Flag__success = true,
                Flag__history__deleted = The_send.Flag__delete__history,
                Message_delivery__from_this_client_to_peer = conversation.Conversations__delivery_from_this_client_to_peer(The_send.Client_guid),
                Flag__peer_online = conversation.Conversations__is_peer_online(The_send.Client_guid),
                Messages = receiveMessages
            };
        }
    }

    public SendAndReceiveReceive Conversations__send__and__receive(IReadOnlyList<SendAndReceiveConversation> The_conversations)
    {
        lock (conversationStoreLock)
        {
            SendAndReceiveReceive receive = new() { Flag__success = true };

            foreach (SendAndReceiveConversation sendConversation in The_conversations)
            {
                receive.Conversations.Add(Conversations__send__and__receive_one(sendConversation));
            }

            return receive;
        }
    }

    private SendAndReceiveConversationResult Conversations__send__and__receive_one(SendAndReceiveConversation The_send)
    {
        if (!conversations.TryGetValue(The_send.Conversation_guid, out Conversation_state? conversation))
        {
            return new SendAndReceiveConversationResult
            {
                Conversation_guid = The_send.Conversation_guid,
                Flag__success = false,
                Flag__conversation_does_not_exists = true,
                Messages = []
            };
        }

        conversation.Conversations__register_client(The_send.Client_guid);

        if (The_send.Flag__delete__history)
        {
            conversation.Conversations__clear_history();
            Conversations__metadata__write(conversation.Conversations__metadata__copy());
        }

        if (The_send.Flag__conversation__has_been_opened)
        {
            conversation.Conversations__mark_messages_to_client_read(The_send.Client_guid);
        }

        if (The_send.Messages_to_send.Count > 0 && conversation.Conversations__is_sender_locked(The_send.Client_guid, The_send.Messages_to_send.Count))
        {
            List<ChatMessage> lockedReceiveMessages = conversation.Conversations__receive(The_send.Client_guid);
            if (lockedReceiveMessages.Count > 0)
            {
                Conversations__metadata__write(conversation.Conversations__metadata__copy());
            }
            if (The_send.Flag__conversation__has_been_opened)
            {
                conversation.Conversations__mark_messages_to_client_read(The_send.Client_guid);
            }

            return new SendAndReceiveConversationResult
            {
                Conversation_guid = The_send.Conversation_guid,
                Flag__success = false,
                Flag__send_locked = true,
                Message_delivery__from_this_client_to_peer = conversation.Conversations__delivery_from_this_client_to_peer(The_send.Client_guid),
                Flag__peer_online = conversation.Conversations__is_peer_online(The_send.Client_guid),
                Messages = lockedReceiveMessages
            };
        }

        foreach (ChatMessage message in The_send.Messages_to_send)
        {
            conversation.Conversations__send(The_send.Client_guid, message);
        }

        if (The_send.Messages_to_send.Count > 0)
        {
            Conversations__metadata__write(conversation.Conversations__metadata__copy());
        }

        List<ChatMessage> receiveMessages = conversation.Conversations__receive(The_send.Client_guid);
        if (receiveMessages.Count > 0)
        {
            Conversations__metadata__write(conversation.Conversations__metadata__copy());
        }
        if (The_send.Flag__conversation__has_been_opened)
        {
            conversation.Conversations__mark_messages_to_client_read(The_send.Client_guid);
        }

        return new SendAndReceiveConversationResult
        {
            Conversation_guid = The_send.Conversation_guid,
            Flag__success = true,
            Flag__history__deleted = The_send.Flag__delete__history,
            Message_delivery__from_this_client_to_peer = conversation.Conversations__delivery_from_this_client_to_peer(The_send.Client_guid),
            Flag__peer_online = conversation.Conversations__is_peer_online(The_send.Client_guid),
            Messages = receiveMessages
        };
    }

    private void Conversations__metadata__load_all()
    {
        foreach (string metadataPath in Directory.EnumerateFiles(metadataDirectory, "*.cfg"))
        {
            try
            {
                string encodedJson = File.ReadAllText(metadataPath);
                string json = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedJson));
                ConversationMetadata? metadata = System.Text.Json.JsonSerializer.Deserialize<ConversationMetadata>(json);

                if (metadata is not null && metadata.Conversation_guid != Guid.Empty)
                {
                    conversations[metadata.Conversation_guid] = new Conversation_state { Metadata = metadata };
                }
            }
            catch
            {
                // Ignore invalid metadata files; admin can recreate conversation metadata if needed.
            }
        }
    }

    private void Conversations__metadata__write(ConversationMetadata The_metadata)
    {
        string json = System.Text.Json.JsonSerializer.Serialize(The_metadata, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
        string encodedJson = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(json));
        File.WriteAllText(Conversations__metadata__path(The_metadata.Conversation_guid), encodedJson);
    }

    private string Conversations__metadata__path(Guid Conversation_guid)
    {
        return Path.Combine(metadataDirectory, $"{Conversation_guid}.cfg");
    }
}

internal sealed class Conversation_state
{
    private static readonly TimeSpan Peer_online_threshold = TimeSpan.FromMinutes(1);
    private readonly List<Queued_message> pendingForClientA = [];
    private readonly List<Queued_message> pendingForClientB = [];
    private readonly List<Queued_message> pendingForUnknownReceiver = [];

    public ConversationMetadata Metadata { get; set; } = new();

    public Guid? Client_A__guid { get; set; }

    public Guid? Client_B__guid { get; set; }

    public DateTime? Client_A__last_seen_utc { get; set; }

    public DateTime? Client_B__last_seen_utc { get; set; }

    public enum__Message_delivery Client_A__messages_to_client_B__delivery { get; set; }

    public enum__Message_delivery Client_B__messages_to_client_A__delivery { get; set; }

    public void Conversations__register_client(Guid Client_guid)
    {
        DateTime nowUtc = DateTime.UtcNow;

        if (Client_A__guid == Client_guid)
        {
            Client_A__last_seen_utc = nowUtc;
            return;
        }

        if (Client_B__guid == Client_guid)
        {
            Client_B__last_seen_utc = nowUtc;
            return;
        }

        if (Client_A__guid is null)
        {
            Client_A__guid = Client_guid;
            Client_A__last_seen_utc = nowUtc;
            Conversations__flush_unknown_receiver_messages();
            return;
        }

        if (Client_B__guid is null)
        {
            Client_B__guid = Client_guid;
            Client_B__last_seen_utc = nowUtc;
            Conversations__flush_unknown_receiver_messages();
        }
    }

    public bool Conversations__is_peer_online(Guid This_client_guid)
    {
        DateTime nowUtc = DateTime.UtcNow;
        DateTime? peerLastSeenUtc = null;

        if (Client_A__guid == This_client_guid)
        {
            peerLastSeenUtc = Client_B__last_seen_utc;
        }
        else if (Client_B__guid == This_client_guid)
        {
            peerLastSeenUtc = Client_A__last_seen_utc;
        }

        return peerLastSeenUtc.HasValue && nowUtc - peerLastSeenUtc.Value <= Peer_online_threshold;
    }

    public bool Conversations__is_sender_locked(Guid Sender_client_guid, int New_message_count = 1)
    {
        if (Client_A__guid == Sender_client_guid)
        {
            return pendingForClientB.Count + New_message_count > 250;
        }

        if (Client_B__guid == Sender_client_guid)
        {
            return pendingForClientA.Count + New_message_count > 250;
        }

        return pendingForUnknownReceiver.Count + New_message_count > 250;
    }

    public void Conversations__send(Guid Sender_client_guid, ChatMessage Message)
    {
        Queued_message queuedMessage = new(Sender_client_guid, Message, DateTime.UtcNow);

        if (Client_A__guid == Sender_client_guid)
        {
            if (Client_B__guid is null)
            {
                pendingForUnknownReceiver.Add(queuedMessage);
            }
            else
            {
                pendingForClientB.Add(queuedMessage);
            }

            Metadata.Client_A__last_sent__date = queuedMessage.Created_utc;
            Client_A__messages_to_client_B__delivery = enum__Message_delivery.None;
        }
        else if (Client_B__guid == Sender_client_guid)
        {
            pendingForClientA.Add(queuedMessage);
            Metadata.Client_B__last_sent__date = queuedMessage.Created_utc;
            Client_B__messages_to_client_A__delivery = enum__Message_delivery.None;
        }

        Conversations__update_counts();
    }

    public List<ChatMessage> Conversations__receive(Guid Receiver_client_guid)
    {
        List<Queued_message> source;

        if (Client_A__guid == Receiver_client_guid)
        {
            source = pendingForClientA;
        }
        else if (Client_B__guid == Receiver_client_guid)
        {
            source = pendingForClientB;
        }
        else
        {
            return [];
        }

        List<ChatMessage> messages = source.Select(The_message => The_message.Message).ToList();
        if (messages.Count > 0)
        {
            if (Client_A__guid == Receiver_client_guid)
            {
                Client_B__messages_to_client_A__delivery = enum__Message_delivery.Delivered_to_peer;
            }
            else if (Client_B__guid == Receiver_client_guid)
            {
                Client_A__messages_to_client_B__delivery = enum__Message_delivery.Delivered_to_peer;
            }
        }

        source.Clear();
        Conversations__update_counts();
        return messages;
    }

    public void Conversations__mark_messages_to_client_read(Guid Reader_client_guid)
    {
        if (Client_A__guid == Reader_client_guid && Client_B__messages_to_client_A__delivery == enum__Message_delivery.Delivered_to_peer)
        {
            Client_B__messages_to_client_A__delivery = enum__Message_delivery.Read_by_peer;
        }
        else if (Client_B__guid == Reader_client_guid && Client_A__messages_to_client_B__delivery == enum__Message_delivery.Delivered_to_peer)
        {
            Client_A__messages_to_client_B__delivery = enum__Message_delivery.Read_by_peer;
        }
    }

    public enum__Message_delivery Conversations__delivery_from_this_client_to_peer(Guid Sender_client_guid)
    {
        if (Client_A__guid == Sender_client_guid)
        {
            return Client_A__messages_to_client_B__delivery;
        }

        if (Client_B__guid == Sender_client_guid)
        {
            return Client_B__messages_to_client_A__delivery;
        }

        return enum__Message_delivery.None;
    }

    public void Conversations__clear_history()
    {
        pendingForClientA.Clear();
        pendingForClientB.Clear();
        pendingForUnknownReceiver.Clear();
        Conversations__update_counts();
    }

    public ConversationMetadata Conversations__metadata__copy()
    {
        Conversations__update_counts();
        return new ConversationMetadata
        {
            Conversation_guid = Metadata.Conversation_guid,
            Conversation__metadata__date__created = Metadata.Conversation__metadata__date__created,
            Pending_messages__count = Metadata.Pending_messages__count,
            Client_A__pending_message_count = Metadata.Client_A__pending_message_count,
            Client_A__last_sent__date = Metadata.Client_A__last_sent__date,
            Client_B__pending_message_count = Metadata.Client_B__pending_message_count,
            Client_B__last_sent__date = Metadata.Client_B__last_sent__date
        };
    }

    private void Conversations__flush_unknown_receiver_messages()
    {
        if (Client_A__guid is null || Client_B__guid is null)
        {
            return;
        }

        foreach (Queued_message queuedMessage in pendingForUnknownReceiver.ToArray())
        {
            if (queuedMessage.Sender_client_guid == Client_A__guid)
            {
                pendingForClientB.Add(queuedMessage);
            }
            else if (queuedMessage.Sender_client_guid == Client_B__guid)
            {
                pendingForClientA.Add(queuedMessage);
            }
        }

        pendingForUnknownReceiver.Clear();
        Conversations__update_counts();
    }

    private void Conversations__update_counts()
    {
        Metadata.Client_A__pending_message_count = pendingForClientA.Count;
        Metadata.Client_B__pending_message_count = pendingForClientB.Count;
        Metadata.Pending_messages__count = pendingForClientA.Count + pendingForClientB.Count + pendingForUnknownReceiver.Count;
    }
}

internal sealed record Queued_message(Guid Sender_client_guid, ChatMessage Message, DateTime Created_utc);
