using System.Net;
using System.Text;
using System.Text.Json;
using Noctalk_server.Server.Conversations;
using Noctalk_server.Server.Networking.Transport;
using AdminSend = Noctalk.Shared.Chat.Api.Json.Admin_client__to__server.Command.Send;
using AdminReceive = Noctalk.Shared.Chat.Api.Json.Admin_client__to__server.Command.Receive;
using ChatSend = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send;
using ChatReceive = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Receive;
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;

public sealed class Local_relay_listener : IDisposable
{
    private readonly Action<string> log;
    private readonly Guid Admin__guid;
    private readonly Func<string> getServerName;
    private readonly Conversation_store conversationStore;
    private readonly Transport_chunk_store transportChunkStore;
    private readonly int localRelayPort;
    private readonly HttpListener httpListener = new();
    private static readonly JsonSerializerOptions Json_options = new() { WriteIndented = false };
    private CancellationTokenSource? cancellationTokenSource;

    public Local_relay_listener(Guid The_Admin__guid, Func<string> The_get_server_name, Conversation_store The_conversation_store, int The_local_relay_port, Action<string> The_log)
    {
        Admin__guid = The_Admin__guid;
        getServerName = The_get_server_name;
        conversationStore = The_conversation_store;
        transportChunkStore = new Transport_chunk_store(conversationStore, Json_options);
        localRelayPort = The_local_relay_port;
        log = The_log;
        httpListener.Prefixes.Add($"http://127.0.0.1:{localRelayPort}/");
    }

    public bool Is__running => httpListener.IsListening;

    public void Networking__local_relay__start()
    {
        if (httpListener.IsListening)
        {
            return;
        }

        cancellationTokenSource = new CancellationTokenSource();
        httpListener.Start();
        _ = Task.Run(() => Networking__local_relay__listen_loop_async(cancellationTokenSource.Token));
        log($"Local relay listener started on http://127.0.0.1:{localRelayPort}/.");
    }

    public void Networking__local_relay__stop()
    {
        cancellationTokenSource?.Cancel();

        if (httpListener.IsListening)
        {
            httpListener.Stop();
        }

        log("Local relay listener stopped.");
    }

    public void Dispose()
    {
        Networking__local_relay__stop();
        httpListener.Close();
        cancellationTokenSource?.Dispose();
    }

    private async Task Networking__local_relay__listen_loop_async(CancellationToken The_cancellation_token)
    {
        while (!The_cancellation_token.IsCancellationRequested)
        {
            try
            {
                HttpListenerContext context = await httpListener.GetContextAsync();
                _ = Task.Run(() => Networking__local_relay__handle_request_async(context), CancellationToken.None);
            }
            catch (HttpListenerException) when (!httpListener.IsListening)
            {
                return;
            }
            catch (ObjectDisposedException)
            {
                return;
            }
            catch (Exception exception)
            {
                log($"Local relay listener error: {exception.Message}");
            }
        }
    }

    private async Task Networking__local_relay__handle_request_async(HttpListenerContext The_context)
    {
        if (The_context.Request.Url?.AbsolutePath.Equals("/admin", StringComparison.OrdinalIgnoreCase) == true)
        {
            await Networking__local_relay__handle_admin_request_async(The_context);
            return;
        }

        if (The_context.Request.Url?.AbsolutePath.Equals("/poll", StringComparison.OrdinalIgnoreCase) == true)
        {
            await Networking__local_relay__handle_poll_request_async(The_context);
            return;
        }

        string responseJson = JsonSerializer.Serialize(new
        {
            Flag__success = true,
            Message_to_client = "Noctalk_server local relay is running.",
            Path = The_context.Request.Url?.AbsolutePath ?? "/",
            Created_utc = DateTime.UtcNow
        });

        byte[] responseBytes = Encoding.UTF8.GetBytes(responseJson);
        The_context.Response.StatusCode = 200;
        The_context.Response.ContentType = "application/json";
        The_context.Response.ContentEncoding = Encoding.UTF8;
        The_context.Response.ContentLength64 = responseBytes.Length;
        await The_context.Response.OutputStream.WriteAsync(responseBytes);
        The_context.Response.Close();
    }

    private async Task Networking__local_relay__handle_admin_request_async(HttpListenerContext The_context)
    {
        if (The_context.Request.HttpMethod != "POST")
        {
            await Networking__local_relay__write_json_async(The_context, 405, new AdminReceive
            {
                Flag__success = false,
                Message_to_admin = "Admin endpoint requires POST."
            });
            return;
        }

        AdminSend? send;
        using (StreamReader reader = new(The_context.Request.InputStream, The_context.Request.ContentEncoding))
        {
            string requestJson = await reader.ReadToEndAsync();
            send = JsonSerializer.Deserialize<AdminSend>(requestJson, Json_options);
        }

        if (send is null)
        {
            await Networking__local_relay__write_json_async(The_context, 400, new AdminReceive
            {
                Flag__success = false,
                Message_to_admin = "Invalid admin command packet."
            });
            return;
        }

        AdminReceive receive = Networking__admin__handle_command(send);
        await Networking__local_relay__write_json_async(The_context, 200, receive);
    }

    private async Task Networking__local_relay__handle_poll_request_async(HttpListenerContext The_context)
    {
        if (The_context.Request.HttpMethod != "POST")
        {
            await Networking__local_relay__write_json_async(The_context, 405, new ChatReceive
            {
                Flag__success = false,
                Messages = []
            });
            return;
        }

        string requestJson;
        using (StreamReader reader = new(The_context.Request.InputStream, The_context.Request.ContentEncoding))
        {
            requestJson = await reader.ReadToEndAsync();
        }

        TransportChunkSend? transportChunk = JsonSerializer.Deserialize<TransportChunkSend>(requestJson, Json_options);
        if (transportChunk is not null
            && (transportChunk.Transport__session_guid != Guid.Empty
                || transportChunk.Transport__response_session_guid != Guid.Empty
                || transportChunk.Flag__download_response_chunk
                || !string.IsNullOrWhiteSpace(transportChunk.Transport__payload_base64)))
        {
            TransportChunkReceive transportReceive = transportChunkStore.Transport__handle_chunk(transportChunk);
            await Networking__local_relay__write_json_async(The_context, 200, transportReceive);
            return;
        }

        SendAndReceiveSend? sendAndReceive = JsonSerializer.Deserialize<SendAndReceiveSend>(requestJson, Json_options);
        if (sendAndReceive is not null && sendAndReceive.Conversations.Count > 0)
        {
            SendAndReceiveReceive sendAndReceiveResponse = conversationStore.Conversations__send__and__receive(sendAndReceive.Conversations);
            await Networking__local_relay__write_json_async(The_context, 200, sendAndReceiveResponse);
            return;
        }

        ChatSend? send = JsonSerializer.Deserialize<ChatSend>(requestJson, Json_options);

        if (send is null)
        {
            await Networking__local_relay__write_json_async(The_context, 400, new ChatReceive
            {
                Flag__success = false,
                Messages = []
            });
            return;
        }

        ChatReceive receive = conversationStore.Conversations__poll(send);
        await Networking__local_relay__write_json_async(The_context, 200, receive);
    }

    private AdminReceive Networking__admin__handle_command(AdminSend The_send)
    {
        if (The_send.Admin__guid != Admin__guid)
        {
            return new AdminReceive
            {
                Flag__success = false,
                Flag__invalid_admin_api_secret = true,
                Server__name = getServerName(),
                Message_to_admin = "Invalid Admin__guid."
            };
        }

        int selectedFlagCount = Convert.ToInt32(The_send.Flag__create__conversation_pair)
            + Convert.ToInt32(The_send.Flag__get__conversation_list)
            + Convert.ToInt32(The_send.Flag__delete__conversation);

        if (selectedFlagCount != 1)
        {
            return new AdminReceive
            {
                Flag__success = false,
                Server__name = getServerName(),
                Message_to_admin = "Exactly one admin command flag must be set."
            };
        }

        if (The_send.Flag__create__conversation_pair)
        {
            bool created = conversationStore.Conversations__create(The_send.Conversation_guid);
            return new AdminReceive
            {
                Flag__success = created,
                Flag__conversation_created = created,
                Server__name = getServerName(),
                Message_to_admin = created ? "Conversation created." : "Conversation already exists."
            };
        }

        if (The_send.Flag__get__conversation_list)
        {
            return new AdminReceive
            {
                Flag__success = true,
                Message_to_admin = "Conversation list returned.",
                Server__name = getServerName(),
                Conversation__metadata = conversationStore.Conversations__list()
            };
        }

        bool deleted = conversationStore.Conversations__delete(The_send.Conversation_guid);
        return new AdminReceive
        {
            Flag__success = deleted,
            Flag__conversation_deleted = deleted,
            Server__name = getServerName(),
            Message_to_admin = deleted ? "Conversation deleted." : "Conversation was not found."
        };
    }

    private static async Task Networking__local_relay__write_json_async<T>(HttpListenerContext The_context, int The_status_code, T The_response)
    {
        string responseJson = JsonSerializer.Serialize(The_response, Json_options);
        byte[] responseBytes = Encoding.UTF8.GetBytes(responseJson);
        The_context.Response.StatusCode = The_status_code;
        The_context.Response.ContentType = "application/json";
        The_context.Response.ContentEncoding = Encoding.UTF8;
        The_context.Response.ContentLength64 = responseBytes.Length;
        await The_context.Response.OutputStream.WriteAsync(responseBytes);
        The_context.Response.Close();
    }
}
