using System.Text.Json;
using Noctalk.Shared.Chat.Constants;
using Noctalk_server.Server.Conversations;
using SendAndReceiveReceive = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__receive;
using SendAndReceiveSend = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__send;
using TransportChunkReceive = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Transport__chunk__receive;
using TransportChunkSend = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Transport__chunk__send;

namespace Noctalk_server.Server.Networking.Transport;

public sealed class Transport_chunk_store
{
    private readonly object gate = new();
    private readonly Dictionary<Guid, Transport_upload_session> uploadSessions = [];
    private readonly Dictionary<Guid, Transport_completed_upload_session> completedUploadSessions = [];
    private readonly Dictionary<Guid, Transport_response_session> responseSessions = [];
    private readonly Conversation_store conversationStore;
    private readonly JsonSerializerOptions jsonOptions;

    public Transport_chunk_store(Conversation_store The_conversation_store, JsonSerializerOptions The_json_options)
    {
        conversationStore = The_conversation_store;
        jsonOptions = The_json_options;
    }

    public TransportChunkReceive Transport__handle_chunk(TransportChunkSend The_chunk)
    {
        Transport__cleanup_expired_sessions();

        if (The_chunk.Flag__download_response_chunk)
        {
            return Transport__get_response_chunk(The_chunk.Transport__response_session_guid, The_chunk.Transport__chunk_index);
        }

        if (The_chunk.Transport__session_guid == Guid.Empty)
        {
            return Transport__failed("Transport session guid is missing.");
        }

        lock (gate)
        {
            if (completedUploadSessions.TryGetValue(The_chunk.Transport__session_guid, out Transport_completed_upload_session? completedSession))
            {
                completedSession.Last_activity_utc = DateTime.UtcNow;
                return completedSession.Response;
            }
        }

        byte[] chunkBytes;
        try
        {
            chunkBytes = Convert.FromBase64String(The_chunk.Transport__payload_base64);
        }
        catch (FormatException)
        {
            return Transport__failed("Transport chunk payload is not valid Base64.");
        }

        byte[]? completedRequestBytes = null;

        lock (gate)
        {
            if (!uploadSessions.TryGetValue(The_chunk.Transport__session_guid, out Transport_upload_session? session))
            {
                session = new Transport_upload_session();
                uploadSessions[The_chunk.Transport__session_guid] = session;
            }

            if (The_chunk.Transport__chunk_index < session.Next_chunk_index)
            {
                session.Last_activity_utc = DateTime.UtcNow;
                return new TransportChunkReceive
                {
                    Flag__success = true,
                    Transport__chunk_index = The_chunk.Transport__chunk_index,
                    Message_to_client = "Transport chunk was already accepted."
                };
            }

            if (The_chunk.Transport__chunk_index > session.Next_chunk_index)
            {
                uploadSessions.Remove(The_chunk.Transport__session_guid);
                return Transport__failed($"Transport chunk index {The_chunk.Transport__chunk_index} arrived before expected index {session.Next_chunk_index}.");
            }

            session.Stream.Write(chunkBytes, 0, chunkBytes.Length);
            session.Next_chunk_index++;
            session.Last_activity_utc = DateTime.UtcNow;

            if (The_chunk.Flag__last_chunk)
            {
                completedRequestBytes = session.Stream.ToArray();
                uploadSessions.Remove(The_chunk.Transport__session_guid);
            }
        }

        if (!The_chunk.Flag__last_chunk)
        {
            return new TransportChunkReceive
            {
                Flag__success = true,
                Transport__chunk_index = The_chunk.Transport__chunk_index,
                Message_to_client = "Transport chunk accepted."
            };
        }

        TransportChunkReceive completedResponse = Transport__process_completed_request(completedRequestBytes ?? []);

        lock (gate)
        {
            completedUploadSessions[The_chunk.Transport__session_guid] = new Transport_completed_upload_session(completedResponse);
        }

        return completedResponse;
    }

    private TransportChunkReceive Transport__process_completed_request(byte[] The_request_bytes)
    {
        SendAndReceiveSend? send;

        try
        {
            send = JsonSerializer.Deserialize<SendAndReceiveSend>(The_request_bytes, jsonOptions);
        }
        catch (JsonException)
        {
            return Transport__failed("Transport request was not valid Send__and__receive JSON.");
        }

        if (send is null || send.Conversations.Count == 0)
        {
            return Transport__failed("Transport request did not contain Send__and__receive conversations.");
        }

        SendAndReceiveReceive receive = conversationStore.Conversations__send__and__receive(send.Conversations);
        byte[] responseBytes = JsonSerializer.SerializeToUtf8Bytes(receive, jsonOptions);

        if (responseBytes.Length <= Network_constants.Max__transport_chunk__bytes)
        {
            return new TransportChunkReceive
            {
                Flag__success = true,
                Flag__transport_session_completed = true,
                Flag__last_chunk = true,
                Message_to_client = "Transport request completed.",
                Transport__response_payload_base64 = Convert.ToBase64String(responseBytes)
            };
        }

        Guid responseSessionGuid = Guid.NewGuid();

        lock (gate)
        {
            responseSessions[responseSessionGuid] = new Transport_response_session(responseBytes);
        }

        return new TransportChunkReceive
        {
            Flag__success = true,
            Flag__transport_session_completed = true,
            Flag__response_requires_chunk_download = true,
            Message_to_client = "Transport request completed. Response requires chunk download.",
            Transport__response_session_guid = responseSessionGuid
        };
    }

    private TransportChunkReceive Transport__get_response_chunk(Guid The_response_session_guid, int The_chunk_index)
    {
        if (The_response_session_guid == Guid.Empty)
        {
            return Transport__failed("Transport response session guid is missing.");
        }

        lock (gate)
        {
            if (!responseSessions.TryGetValue(The_response_session_guid, out Transport_response_session? session))
            {
                return Transport__failed("Transport response session was not found.");
            }

            if (The_chunk_index < 0 || The_chunk_index >= session.Chunk_count)
            {
                return Transport__failed($"Transport response chunk index {The_chunk_index} is out of range.");
            }

            int offset = The_chunk_index * Network_constants.Max__transport_chunk__bytes;
            int remainingBytes = session.Response_bytes.Length - offset;
            int chunkLength = Math.Min(Network_constants.Max__transport_chunk__bytes, remainingBytes);
            byte[] chunkBytes = new byte[chunkLength];
            Array.Copy(session.Response_bytes, offset, chunkBytes, 0, chunkLength);
            session.Last_activity_utc = DateTime.UtcNow;
            bool flagLastChunk = The_chunk_index == session.Chunk_count - 1;

            return new TransportChunkReceive
            {
                Flag__success = true,
                Flag__transport_session_completed = true,
                Flag__last_chunk = flagLastChunk,
                Transport__chunk_index = The_chunk_index,
                Message_to_client = flagLastChunk ? "Transport response completed." : "Transport response chunk returned.",
                Transport__response_payload_base64 = Convert.ToBase64String(chunkBytes)
            };
        }
    }

    private void Transport__cleanup_expired_sessions()
    {
        DateTime cutoffUtc = DateTime.UtcNow.AddMinutes(-Network_constants.Transport__session__timeout_minutes);

        lock (gate)
        {
            foreach (Guid sessionGuid in uploadSessions.Where(pair => pair.Value.Last_activity_utc < cutoffUtc).Select(pair => pair.Key).ToList())
            {
                uploadSessions.Remove(sessionGuid);
            }

            foreach (Guid sessionGuid in completedUploadSessions.Where(pair => pair.Value.Last_activity_utc < cutoffUtc).Select(pair => pair.Key).ToList())
            {
                completedUploadSessions.Remove(sessionGuid);
            }

            foreach (Guid sessionGuid in responseSessions.Where(pair => pair.Value.Last_activity_utc < cutoffUtc).Select(pair => pair.Key).ToList())
            {
                responseSessions.Remove(sessionGuid);
            }
        }
    }

    private static TransportChunkReceive Transport__failed(string The_message_to_client)
    {
        return new TransportChunkReceive
        {
            Flag__success = false,
            Flag__transport_session_failed = true,
            Message_to_client = The_message_to_client
        };
    }

    private sealed class Transport_upload_session
    {
        public MemoryStream Stream { get; } = new();

        public int Next_chunk_index { get; set; }

        public DateTime Last_activity_utc { get; set; } = DateTime.UtcNow;
    }

    private sealed class Transport_response_session
    {
        public Transport_response_session(byte[] The_response_bytes)
        {
            Response_bytes = The_response_bytes;
        }

        public byte[] Response_bytes { get; }

        public int Chunk_count => (int)Math.Ceiling((double)Response_bytes.Length / Network_constants.Max__transport_chunk__bytes);

        public DateTime Last_activity_utc { get; set; } = DateTime.UtcNow;
    }

    private sealed class Transport_completed_upload_session
    {
        public Transport_completed_upload_session(TransportChunkReceive The_response)
        {
            Response = The_response;
        }

        public TransportChunkReceive Response { get; }

        public DateTime Last_activity_utc { get; set; } = DateTime.UtcNow;
    }
}
