using Noctalk.Shared.Chat.Configuration;
using Noctalk.Shared.Chat.Constants;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
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 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_client.Client.Networking.Chat;

public sealed class Chat_poll_client : IDisposable
{
    private static readonly JsonSerializerOptions Json_options = new() { WriteIndented = false };
    public Chat_poll_client()
    {
    }

    public async Task<ChatReceive> Chat__poll_async(Conversation_config The_conversation_config, int The_client_local_tor_port, ChatMessage? Message, bool Flag__delete__history, bool Flag__conversation__has_been_opened, CancellationToken The_cancellation_token)
    {
        ChatSend send = new()
        {
            Api_secret_guid = Guid.Empty,
            Conversation_guid = The_conversation_config.conversation__guid,
            Client_guid = The_conversation_config.conversation__client__guid,
            Flag__delete__history = Flag__delete__history,
            Flag__conversation__has_been_opened = Flag__conversation__has_been_opened,
            Message = Message
        };

        string json = JsonSerializer.Serialize(send, Json_options);
        using StringContent content = new(json, Encoding.UTF8, "application/json");
        string onionAddress = The_conversation_config.Server__onion_address.Trim();
        string url = $"http://{onionAddress}:65191/poll";
        using HttpClient httpClient = Chat__create_tor_http_client(The_client_local_tor_port);
        using HttpResponseMessage response = await httpClient.PostAsync(url, content, The_cancellation_token);
        string responseJson = await response.Content.ReadAsStringAsync(The_cancellation_token);
        return JsonSerializer.Deserialize<ChatReceive>(responseJson, Json_options) ?? new ChatReceive { Flag__success = false, Messages = [] };
    }

    public async Task<SendAndReceiveReceive> Chat__send__and__receive_async(string Server__onion_address, int The_client_local_tor_port, List<SendAndReceiveConversation> Conversations, CancellationToken The_cancellation_token)
    {
        SendAndReceiveSend send = new()
        {
            Api_secret_guid = Guid.Empty,
            Conversations = Conversations
        };

        string onionAddress = Server__onion_address.Trim();
        string url = $"http://{onionAddress}:65191/poll";
        using HttpClient httpClient = Chat__create_tor_http_client(The_client_local_tor_port);
        byte[] requestBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(send, Json_options));
        Guid transportSessionGuid = Guid.NewGuid();
        TransportChunkReceive finalChunkResponse = new()
        {
            Flag__success = false,
            Flag__transport_session_failed = true,
            Message_to_client = "Transport request did not complete."
        };

        int offset = 0;
        int chunkIndex = 0;
        do
        {
            int chunkLength = Math.Min(Network_constants.Max__transport_chunk__bytes, requestBytes.Length - offset);
            byte[] chunkBytes = new byte[chunkLength];
            Array.Copy(requestBytes, offset, chunkBytes, 0, chunkLength);
            offset += chunkLength;

            TransportChunkSend transportChunk = new()
            {
                Transport__session_guid = transportSessionGuid,
                Transport__chunk_index = chunkIndex,
                Flag__last_chunk = offset >= requestBytes.Length,
                Transport__payload_base64 = Convert.ToBase64String(chunkBytes)
            };

            finalChunkResponse = await Chat__send_transport_chunk_with_retry_async(httpClient, url, transportChunk, The_cancellation_token);
            chunkIndex++;
        }
        while (offset < requestBytes.Length);

        byte[] responseBytes = await Chat__read_transport_response_bytes_async(httpClient, url, finalChunkResponse, The_cancellation_token);
        string responseJson = Encoding.UTF8.GetString(responseBytes);
        return JsonSerializer.Deserialize<SendAndReceiveReceive>(responseJson, Json_options) ?? new SendAndReceiveReceive { Flag__success = false, Conversations = [] };
    }

    private static async Task<byte[]> Chat__read_transport_response_bytes_async(HttpClient The_http_client, string The_url, TransportChunkReceive The_final_chunk_response, CancellationToken The_cancellation_token)
    {
        if (!The_final_chunk_response.Flag__success || The_final_chunk_response.Flag__transport_session_failed)
        {
            throw new InvalidOperationException(The_final_chunk_response.Message_to_client);
        }

        if (!The_final_chunk_response.Flag__response_requires_chunk_download)
        {
            return Convert.FromBase64String(The_final_chunk_response.Transport__response_payload_base64);
        }

        if (The_final_chunk_response.Transport__response_session_guid == Guid.Empty)
        {
            throw new InvalidOperationException("Transport response session was not returned.");
        }

        using MemoryStream responseStream = new();
        bool flagLastChunk = false;
        int chunkIndex = 0;

        while (!flagLastChunk)
        {
            TransportChunkSend downloadChunkRequest = new()
            {
                Transport__session_guid = Guid.NewGuid(),
                Flag__download_response_chunk = true,
                Transport__chunk_index = chunkIndex,
                Transport__response_session_guid = The_final_chunk_response.Transport__response_session_guid
            };

            TransportChunkReceive downloadChunk = await Chat__send_transport_chunk_with_retry_async(The_http_client, The_url, downloadChunkRequest, The_cancellation_token);

            if (!downloadChunk.Flag__success || downloadChunk.Flag__transport_session_failed)
            {
                throw new InvalidOperationException(downloadChunk.Message_to_client);
            }

            byte[] chunkBytes = Convert.FromBase64String(downloadChunk.Transport__response_payload_base64);
            await responseStream.WriteAsync(chunkBytes, The_cancellation_token);
            flagLastChunk = downloadChunk.Flag__last_chunk;
            chunkIndex++;
        }

        return responseStream.ToArray();
    }

    private static async Task<TransportChunkReceive> Chat__send_transport_chunk_with_retry_async(HttpClient The_http_client, string The_url, TransportChunkSend The_transport_chunk, CancellationToken The_cancellation_token)
    {
        Exception? lastException = null;

        for (int attempt = 1; attempt <= Network_constants.Max__transport_chunk__retry_count; attempt++)
        {
            try
            {
                TransportChunkReceive receive = await Chat__send_transport_chunk_async(The_http_client, The_url, The_transport_chunk, The_cancellation_token);
                if (receive.Flag__success && !receive.Flag__transport_session_failed)
                {
                    return receive;
                }

                lastException = new InvalidOperationException(receive.Message_to_client);
            }
            catch (Exception exception) when (exception is not OperationCanceledException)
            {
                lastException = exception;
            }
        }

        throw new InvalidOperationException($"Transport chunk failed after {Network_constants.Max__transport_chunk__retry_count} attempts: {lastException?.Message}", lastException);
    }

    private static async Task<TransportChunkReceive> Chat__send_transport_chunk_async(HttpClient The_http_client, string The_url, TransportChunkSend The_transport_chunk, CancellationToken The_cancellation_token)
    {
        string json = JsonSerializer.Serialize(The_transport_chunk, Json_options);
        using StringContent content = new(json, Encoding.UTF8, "application/json");
        using HttpResponseMessage response = await The_http_client.PostAsync(The_url, content, The_cancellation_token);
        string responseJson = await response.Content.ReadAsStringAsync(The_cancellation_token);
        return JsonSerializer.Deserialize<TransportChunkReceive>(responseJson, Json_options) ?? new TransportChunkReceive
        {
            Flag__success = false,
            Flag__transport_session_failed = true,
            Message_to_client = "Invalid transport chunk response."
        };
    }

    public void Dispose()
    {
    }

    public static HttpClient Chat__create_tor_http_client(int The_client_local_tor_port)
    {
        HttpClientHandler torHandler = new()
        {
            Proxy = new WebProxy($"socks5://127.0.0.1:{The_client_local_tor_port}"),
            UseProxy = true
        };

        return new HttpClient(torHandler) { Timeout = TimeSpan.FromSeconds(45) };
    }
}
