using Noctalk.Shared.Chat.Configuration;
using Noctalk.Shared.Chat.Constants;
using Noctalk.UI.Components.Confirmation;
using Noctalk_client.Client.Networking.Chat;
using Microsoft.Win32;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
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 ConversationMetadata = Noctalk.Shared.Chat.Api.Json.Admin_client__to__server.Command.Conversation__metadata;

namespace Noctalk_client.Admin;

public partial class Admin_panel : UserControl
{
    private static readonly JsonSerializerOptions Json_options = new() { WriteIndented = true };
    private readonly string executableDirectory;
    private readonly List<Admin_server_access> adminServerAccesses;
    private readonly int adminClientLocalTorPort;
    private readonly Action<string> log;
    private static readonly UTF8Encoding Utf8_without_bom = new(false);
    private readonly ObservableCollection<Conversation_metadata_view_model> conversationMetadata = [];
    private Conversation_config? Client_A_config__last;
    private Conversation_config? Client_B_config__last;
    private string Client_A_name__last = string.Empty;
    private string Client_B_name__last = string.Empty;
    private Guid New_server_admin_guid__last;
    private string New_server_onion_address__last = string.Empty;
    private bool bRefreshing__conversation_list;
    private bool bCreating__conversation_pair;

    public Admin_panel(string The_executable_directory, List<Admin_server_access> The_admin_server_accesses, int The_admin_client_local_tor_port, Action<string> The_log)
    {
        InitializeComponent();
        executableDirectory = The_executable_directory;
        adminServerAccesses = The_admin_server_accesses;
        adminClientLocalTorPort = The_admin_client_local_tor_port;
        log = The_log;
        Conversation_metadata_grid.ItemsSource = conversationMetadata;
        Admin_server_combobox.ItemsSource = adminServerAccesses;
        Admin__create_mode__apply_visibility();
    }

    public async Task Admin__activate_async()
    {
        if (adminServerAccesses.Count == 0)
        {
            Admin_server_combobox.SelectedIndex = -1;
            conversationMetadata.Clear();
            return;
        }

        Admin_server_combobox.SelectedIndex = 0;

        await Admin__conversation_list__refresh();
    }

    public async Task Admin__server_accesses__updated_async(bool Flag__activate_if_visible)
    {
        Admin_server_combobox.Items.Refresh();

        if (Flag__activate_if_visible)
        {
            await Admin__activate_async();
        }
    }

    private async void Create_button__click(object sender, RoutedEventArgs e)
    {
        if (bCreating__conversation_pair)
        {
            return;
        }

        bCreating__conversation_pair = true;
        button__create.IsEnabled = false;

        try
        {
            if (Create_full_clients_radio.IsChecked == true)
            {
                await Admin__create_full_clients_package_async();
                return;
            }

            await Admin__create_conversation_pair_config_files_async();
        }
        catch (Exception exception)
        {
            log($"Admin create failed: {exception.Message}");
            Noctalk_message.Show(Window.GetWindow(this), "Create failed", exception.Message);
        }
        finally
        {
            button__create.IsEnabled = true;
            bCreating__conversation_pair = false;
        }
    }

    private void Create_mode_radio__checked(object sender, RoutedEventArgs e)
    {
        Admin__create_mode__apply_visibility();
    }

    private void Create_new_server_checkbox__checked_changed(object sender, RoutedEventArgs e)
    {
        Admin__create_mode__apply_visibility();
    }

    private void Admin__create_mode__apply_visibility()
    {
        if (Client_A__local_tor_port_label is null || Client_A__local_tor_port_numeric is null || Client_B__local_tor_port_label is null || Client_B__local_tor_port_numeric is null || Create_new_server_box is null || New_server_name_label is null || New_server_name_textbox is null || New_server_local_relay_port_label is null || New_server_local_relay_port_numeric is null)
        {
            return;
        }

        Visibility portVisibility = Create_full_clients_radio?.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
        Visibility serverVisibility = Create_full_clients_radio?.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
        Visibility serverNameVisibility = Create_full_clients_radio?.IsChecked == true && Create_new_server_checkbox?.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
        Client_A__local_tor_port_label.Visibility = portVisibility;
        Client_A__local_tor_port_numeric.Visibility = portVisibility;
        Client_B__local_tor_port_label.Visibility = portVisibility;
        Client_B__local_tor_port_numeric.Visibility = portVisibility;
        Create_new_server_box.Visibility = serverVisibility;
        New_server_name_label.Visibility = serverNameVisibility;
        New_server_name_textbox.Visibility = serverNameVisibility;
        New_server_local_relay_port_label.Visibility = serverNameVisibility;
        New_server_local_relay_port_numeric.Visibility = serverNameVisibility;
    }

    private async Task Admin__create_conversation_pair_config_files_async()
    {
        bool flagCreateNewServer = Create_full_clients_radio.IsChecked == true && Create_new_server_checkbox.IsChecked == true;
        if (!await Admin__conversation_pair__create_on_server_async())
        {
            return;
        }

        OpenFolderDialog folderDialog = new() { Title = "Choose where to create the conversation config files" };
        if (folderDialog.ShowDialog(Window.GetWindow(this)) != true)
        {
            return;
        }

        string serverNameForFile = Admin__server_name_for_client_package__get();
        Admin__write_conversation_config_file(folderDialog.FolderName, serverNameForFile, Client_A_name__last, Client_A_config__last!);
        Admin__write_conversation_config_file(folderDialog.FolderName, serverNameForFile, Client_B_name__last, Client_B_config__last!);
        if (!flagCreateNewServer)
        {
            Admin__conversation_list__refresh_in_background();
        }
    }

    private async Task Admin__create_full_clients_package_async()
    {
        bool flagCreateNewServer = Create_new_server_checkbox.IsChecked == true;
        if (!await Admin__conversation_pair__create_on_server_async())
        {
            return;
        }

        OpenFolderDialog folderDialog = new() { Title = "Choose where to create the client packages" };
        if (folderDialog.ShowDialog(Window.GetWindow(this)) != true)
        {
            return;
        }

        string serverNameForPackage = Admin__server_name_for_client_package__get();
        string clientAPackageName = Admin__client_package_name__get(serverNameForPackage, Client_A_name__last);
        string clientBPackageName = Admin__client_package_name__get(serverNameForPackage, Client_B_name__last);
        if (clientAPackageName.Equals(clientBPackageName, StringComparison.OrdinalIgnoreCase))
        {
            clientAPackageName += "__Client_A";
            clientBPackageName += "__Client_B";
        }

        string clientAPackageDirectory = Path.Combine(folderDialog.FolderName, clientAPackageName);
        string clientBPackageDirectory = Path.Combine(folderDialog.FolderName, clientBPackageName);

        if (Create_new_server_checkbox.IsChecked == true)
        {
            string serverPackageName = $"Noctalk_server__{Admin__file_name__sanitize(New_server_name_textbox.Text)}";
            string serverPackageDirectory = Path.Combine(folderDialog.FolderName, serverPackageName);
            await Admin__write_new_server_package_async(serverPackageDirectory, New_server_name_textbox.Text.Trim(), New_server_local_relay_port_numeric.Value, Client_A_config__last!.conversation__guid);
        }

        Admin__write_client_package(Client_A_config__last!, clientAPackageDirectory, Client_A__local_tor_port_numeric.Value);
        Admin__write_client_package(Client_B_config__last!, clientBPackageDirectory, Client_B__local_tor_port_numeric.Value);
        if (!flagCreateNewServer)
        {
            Admin__conversation_list__refresh_in_background();
        }
    }

    private void Admin__conversation_list__refresh_in_background()
    {
        _ = Admin__conversation_list__refresh();
    }

    private async void Refresh_list_button__click(object sender, RoutedEventArgs e)
    {
        await Admin__conversation_list__refresh();
    }

    private async void Delete_conversation_row_button__click(object sender, RoutedEventArgs e)
    {
        if (sender is not FrameworkElement { DataContext: Conversation_metadata_view_model selectedConversation } || Admin_server_combobox.SelectedItem is not Admin_server_access access)
        {
            return;
        }

        if (!Noctalk_confirmation.Show(Window.GetWindow(this), "Delete conversation", "Are you sure you want to delete this conversation?"))
        {
            return;
        }

        AdminReceive receive = await Admin__server__send_command(access, new AdminSend
        {
            Admin__guid = access.Admin__guid,
            Flag__delete__conversation = true,
            Conversation_guid = selectedConversation.Conversation_guid
        });

        if (receive.Flag__conversation_deleted)
        {
            await Admin__conversation_list__refresh();
        }
    }

    private async void Admin_server_combobox__selection_changed(object sender, SelectionChangedEventArgs e)
    {
        if (Admin_server_combobox.SelectedItem is not null)
        {
            await Admin__conversation_list__refresh();
        }
    }

    private void Conversation_metadata_grid__preview_mouse_wheel(object sender, MouseWheelEventArgs e)
    {
        if (UI__parent_scrollviewer__find((DependencyObject)sender) is not { } scrollViewer)
        {
            return;
        }

        e.Handled = true;
        scrollViewer.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
        {
            RoutedEvent = MouseWheelEvent,
            Source = sender
        });
    }

    private static ScrollViewer? UI__parent_scrollviewer__find(DependencyObject Dependency_object)
    {
        DependencyObject? parent = VisualTreeHelper.GetParent(Dependency_object);
        while (parent is not null)
        {
            if (parent is ScrollViewer scrollViewer)
            {
                return scrollViewer;
            }

            parent = VisualTreeHelper.GetParent(parent);
        }

        return null;
    }

    private async Task Admin__conversation_list__refresh()
    {
        if (bRefreshing__conversation_list)
        {
            return;
        }

        if (Admin_server_combobox.SelectedItem is not Admin_server_access access)
        {
            return;
        }

        bRefreshing__conversation_list = true;
        button__refresh_list.IsEnabled = false;

        try
        {
            AdminReceive receive = await Admin__server__send_command(access, new AdminSend
            {
                Admin__guid = access.Admin__guid,
                Flag__get__conversation_list = true
            });

            if (!receive.Flag__success)
            {
                log($"Refresh conversation list failed: {receive.Message_to_admin}");
                return;
            }

            conversationMetadata.Clear();
            if (!string.IsNullOrWhiteSpace(receive.Server__name))
            {
                access.Server__name = receive.Server__name;
            }

            foreach (ConversationMetadata item in receive.Conversation__metadata)
            {
                conversationMetadata.Add(Conversation_metadata_view_model.From__metadata(item));
            }

            Admin_server_combobox.Items.Refresh();
            Conversation_metadata_grid.Items.Refresh();
        }
        finally
        {
            button__refresh_list.IsEnabled = true;
            bRefreshing__conversation_list = false;
        }
    }

    private async Task<bool> Admin__conversation_pair__create_on_server_async()
    {
        if (Admin_server_combobox.SelectedItem is not Admin_server_access access)
        {
            Noctalk_message.Show(Window.GetWindow(this), "Admin server required", "Select an admin server.");
            return false;
        }

        string clientAName = Client_A__nickname_textbox.Text.Trim();
        string clientBName = Client_B__nickname_textbox.Text.Trim();
        if (clientAName.Equals(clientBName, StringComparison.OrdinalIgnoreCase))
        {
            Client_A__nickname_textbox.Make_textbox_blink_red_for_1_second();
            Client_B__nickname_textbox.Make_textbox_blink_red_for_1_second();
            return false;
        }

        Client_A_name__last = clientAName;
        Client_B_name__last = clientBName;

        Guid conversationGuid = Guid.NewGuid();
        string encryptionKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));

        if (Create_full_clients_radio.IsChecked == true && Create_new_server_checkbox.IsChecked == true)
        {
            New_server_admin_guid__last = Guid.NewGuid();
            Client_A_config__last = Admin__create_conversation_config(conversationGuid, encryptionKey, clientBName, string.Empty, Client_A__admin_rights_checkbox.IsChecked == true ? New_server_admin_guid__last : null);
            Client_B_config__last = Admin__create_conversation_config(conversationGuid, encryptionKey, clientAName, string.Empty, Client_B__admin_rights_checkbox.IsChecked == true ? New_server_admin_guid__last : null);
            return true;
        }

        Client_A_config__last = Admin__create_conversation_config(conversationGuid, encryptionKey, clientBName, access.Server__onion_address, Client_A__admin_rights_checkbox.IsChecked == true ? access.Admin__guid : null);
        Client_B_config__last = Admin__create_conversation_config(conversationGuid, encryptionKey, clientAName, access.Server__onion_address, Client_B__admin_rights_checkbox.IsChecked == true ? access.Admin__guid : null);

        AdminReceive receive = await Admin__server__send_command(access, new AdminSend
        {
            Admin__guid = access.Admin__guid,
            Flag__create__conversation_pair = true,
            Conversation_guid = conversationGuid
        });

        if (!receive.Flag__success)
        {
            Noctalk_message.Show(Window.GetWindow(this), "Create conversation pair failed", $"Server response: {receive.Message_to_admin}");
            return false;
        }

        return true;
    }

    private async Task<AdminReceive> Admin__server__send_command(Admin_server_access The_access, AdminSend The_send)
    {
        try
        {
            using HttpClient httpClient = Chat_poll_client.Chat__create_tor_http_client(adminClientLocalTorPort);
            string json = JsonSerializer.Serialize(The_send, Json_options);
            using StringContent content = new(json, Encoding.UTF8, "application/json");
            using HttpResponseMessage response = await httpClient.PostAsync($"http://{The_access.Server__onion_address}:65191/admin", content);
            string responseJson = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<AdminReceive>(responseJson, Json_options) ?? new AdminReceive { Message_to_admin = "Invalid server response." };
        }
        catch (Exception exception)
        {
            return new AdminReceive { Flag__success = false, Message_to_admin = $"Admin server request failed: {exception.Message}" };
        }
    }

    private static Conversation_config Admin__create_conversation_config(Guid The_conversation_guid, string The_key, string The_nickname, string The_server_onion_address, Guid? The_admin_guid)
    {
        return new Conversation_config
        {
            conversation__guid = The_conversation_guid,
            conversation__client__guid = Guid.NewGuid(),
            conversation__local_encryption__key = The_key,
            Peer__nickname = The_nickname,
            Server__onion_address = The_server_onion_address,
            Admin__guid = The_admin_guid
        };
    }

    private void Admin__write_client_package(Conversation_config The_config, string The_package_directory, int The_client_local_tor_port)
    {
        Admin__copy_client_package(executableDirectory, The_package_directory);
        Base64_json_file.Write__to__relative_path(The_package_directory, "Data/config.cfg", new Main_client_config { Flag__sound__on = true, Current_conversation__guid = The_config.conversation__guid, Client__local_tor__port = The_client_local_tor_port });
        Base64_json_file.Write__to__relative_path(The_package_directory, Path.Combine("Data", "Chats", The_config.conversation__guid.ToString(), "config.cfg"), The_config);
    }

    private async Task Admin__write_new_server_package_async(string The_package_directory, string The_server_name, int The_server_local_relay_port, Guid The_conversation_guid)
    {
        Admin__copy_server_package(executableDirectory, The_package_directory);
        Directory.CreateDirectory(Path.Combine(The_package_directory, "Config"));
        Directory.CreateDirectory(Path.Combine(The_package_directory, "Data", "Metadata", "Conversations"));

        New_server_onion_address__last = await Admin__server_package__generate_onion_address_async(The_package_directory, The_server_local_relay_port, log);
        Client_A_config__last!.Server__onion_address = New_server_onion_address__last;
        Client_B_config__last!.Server__onion_address = New_server_onion_address__last;

        Base64_json_file.Write__to__relative_path(The_package_directory, "Config/main.cfg", new Server_config
        {
            Admin__guid = New_server_admin_guid__last,
            Server__name = string.IsNullOrWhiteSpace(The_server_name) ? "My private chat server" : The_server_name,
            Server__local_relay_port = The_server_local_relay_port,
            Onion_address = New_server_onion_address__last
        });

        ConversationMetadata metadata = new()
        {
            Conversation_guid = The_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
        };

        string metadataJson = JsonSerializer.Serialize(metadata, Json_options);
        string encodedMetadataJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(metadataJson));
        File.WriteAllText(Path.Combine(The_package_directory, "Data", "Metadata", "Conversations", $"{The_conversation_guid}.cfg"), encodedMetadataJson, Encoding.UTF8);
    }

    private static void Admin__write_conversation_config_file(string The_target_directory, string The_server_name, string The_importing_client_name, Conversation_config The_config)
    {
        string fileName = Admin__conversation_config_file_name__get(The_server_name, The_importing_client_name, The_config.Peer__nickname);
        Base64_json_file.Write__to__relative_path(The_target_directory, fileName, The_config);
    }

    private static string Admin__conversation_config_file_name__get(string The_server_name, string The_importing_client_name, string The_peer_name)
    {
        return $"Client_conversation___{Admin__file_name__sanitize(The_server_name)}___{Admin__file_name__sanitize(The_importing_client_name)}_imports__to_talk_to__{Admin__file_name__sanitize(The_peer_name)}{File_constants.Conversation_config__extension}";
    }

    private string Admin__server_name_for_client_package__get()
    {
        if (Create_new_server_checkbox.IsChecked == true)
        {
            string newServerName = New_server_name_textbox.Text.Trim();
            return string.IsNullOrWhiteSpace(newServerName) ? "My private chat server" : newServerName;
        }

        return Admin_server_combobox.SelectedItem is Admin_server_access access && !string.IsNullOrWhiteSpace(access.Server__name)
            ? access.Server__name
            : "Noctalk server";
    }

    private static string Admin__client_package_name__get(string The_server_name, string The_client_name)
    {
        return $"Client__for__{Admin__file_name__sanitize(The_server_name)}___used_by__{Admin__file_name__sanitize(The_client_name)}";
    }

    private static void Admin__copy_client_package(string The_source_directory, string The_target_directory)
    {
        if (Directory.Exists(The_target_directory))
        {
            Directory.Delete(The_target_directory, true);
        }

        Directory.CreateDirectory(The_target_directory);
        File.Copy(Path.Combine(The_source_directory, "Noctalk_client.exe"), Path.Combine(The_target_directory, "Noctalk_client.exe"), true);
        string dataTarget = Path.Combine(The_target_directory, "Data");
        Directory.CreateDirectory(dataTarget);
        File.Copy(Admin__server_data_path__get(The_source_directory), Path.Combine(dataTarget, "Server.data"), true);
        string torTarget = Path.Combine(The_target_directory, "darknet", "tor");
        Directory.CreateDirectory(torTarget);
        foreach (string fileName in new[] { "tor.exe", "geoip", "geoip6" })
        {
            File.Copy(Path.Combine(The_source_directory, "darknet", "tor", fileName), Path.Combine(torTarget, fileName), true);
        }
        Directory.CreateDirectory(Path.Combine(The_target_directory, "darknet", "data"));
        Directory.CreateDirectory(Path.Combine(The_target_directory, "Data", "Chats"));
    }

    private static void Admin__copy_server_package(string The_source_directory, string The_target_directory)
    {
        if (Directory.Exists(The_target_directory))
        {
            Directory.Delete(The_target_directory, true);
        }

        Directory.CreateDirectory(The_target_directory);
        File.Copy(Admin__server_data_path__get(The_source_directory), Path.Combine(The_target_directory, "Noctalk_server.exe"), true);
        string torTarget = Path.Combine(The_target_directory, "darknet", "tor");
        Directory.CreateDirectory(torTarget);
        foreach (string fileName in new[] { "tor.exe", "geoip", "geoip6" })
        {
            File.Copy(Path.Combine(The_source_directory, "darknet", "tor", fileName), Path.Combine(torTarget, fileName), true);
        }
        Directory.CreateDirectory(Path.Combine(The_target_directory, "darknet", "data"));
        Directory.CreateDirectory(Path.Combine(The_target_directory, "Data", "Metadata", "Conversations"));
    }

    private static string Admin__server_data_path__get(string The_source_directory)
    {
        string serverDataPath = Path.Combine(The_source_directory, "Data", "Server.data");
        if (!File.Exists(serverDataPath))
        {
            throw new FileNotFoundException("Data\\Server.data was not found. Server package creation requires Server.data.", serverDataPath);
        }

        if (new FileInfo(serverDataPath).Length == 0)
        {
            throw new InvalidDataException("Data\\Server.data is empty. Server package creation requires a valid Server.data file.");
        }

        return serverDataPath;
    }

    private static async Task<string> Admin__server_package__generate_onion_address_async(string The_package_directory, int The_server_local_relay_port, Action<string> The_log)
    {
        string torDirectory = Path.Combine(The_package_directory, "darknet", "tor");
        string dataDirectory = Path.Combine(The_package_directory, "darknet", "data");
        string onionDirectory = Path.Combine(dataDirectory, "onion_service");
        string torrcPath = Path.Combine(torDirectory, "torrc");
        string hostnamePath = Path.Combine(onionDirectory, "hostname");
        Directory.CreateDirectory(torDirectory);
        Directory.CreateDirectory(dataDirectory);
        Directory.CreateDirectory(onionDirectory);

        string quote(string path) => $"\"{Path.GetFullPath(path).Replace("\\", "\\\\")}\"";
        string torrc = string.Join(Environment.NewLine,
        [
            "# Noctalk_server generated torrc",
            string.Empty,
            $"DataDirectory {quote(dataDirectory)}",
            string.Empty,
            "AvoidDiskWrites 1",
            "SafeLogging 1",
            "Log err stdout",
            string.Empty,
            $"GeoIPFile {quote(Path.Combine(torDirectory, "geoip"))}",
            $"GeoIPv6File {quote(Path.Combine(torDirectory, "geoip6"))}",
            string.Empty,
            $"HiddenServiceDir {quote(onionDirectory)}",
            "HiddenServiceVersion 3",
            $"HiddenServicePort 65191 127.0.0.1:{The_server_local_relay_port}",
            string.Empty,
            "SocksPort 0",
            string.Empty
        ]);
        File.WriteAllText(torrcPath, torrc, Utf8_without_bom);

        List<string> torOutputLines = [];
        using Process torProcess = new()
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = Path.Combine(torDirectory, "tor.exe"),
                Arguments = $"-f \"{torrcPath}\"",
                WorkingDirectory = torDirectory,
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            }
        };

        torProcess.OutputDataReceived += (_, eventArgs) => Admin__server_package__log_tor_line(eventArgs.Data, torOutputLines, The_log);
        torProcess.ErrorDataReceived += (_, eventArgs) => Admin__server_package__log_tor_line(eventArgs.Data, torOutputLines, The_log);
        The_log("Admin create server: starting Tor to generate onion address.");
        torProcess.Start();
        torProcess.BeginOutputReadLine();
        torProcess.BeginErrorReadLine();
        DateTime deadlineUtc = DateTime.UtcNow.AddSeconds(90);
        try
        {
            while (DateTime.UtcNow < deadlineUtc)
            {
                if (File.Exists(hostnamePath))
                {
                    string hostname = (await File.ReadAllTextAsync(hostnamePath)).Trim();
                    if (!string.IsNullOrWhiteSpace(hostname))
                    {
                        The_log("Admin create server: generated onion address.");
                        return hostname;
                    }
                }

                if (torProcess.HasExited)
                {
                    string earlyExitTorOutput = Admin__server_package__last_tor_output(torOutputLines);
                    throw new InvalidOperationException($"Tor exited before generating a new server onion address. Exit code: {torProcess.ExitCode}. Tor output: {earlyExitTorOutput}");
                }

                await Task.Delay(1000);
            }

            string torOutput = Admin__server_package__last_tor_output(torOutputLines);
            throw new TimeoutException($"Timed out while generating a new server onion address. Tor output: {torOutput}");
        }
        finally
        {
            if (!torProcess.HasExited)
            {
                The_log("Admin create server: stopping temporary Tor process.");
                torProcess.Kill(entireProcessTree: true);
                torProcess.WaitForExit(5000);
            }
        }
    }

    private static string Admin__server_package__last_tor_output(List<string> The_tor_output_lines)
    {
        string[] lastTorOutputLines;
        lock (The_tor_output_lines)
        {
            lastTorOutputLines = The_tor_output_lines.TakeLast(8).ToArray();
        }

        return string.Join(" ", lastTorOutputLines);
    }

    private static void Admin__server_package__log_tor_line(string? The_line, List<string> The_tor_output_lines, Action<string> The_log)
    {
        if (string.IsNullOrWhiteSpace(The_line))
        {
            return;
        }

        lock (The_tor_output_lines)
        {
            The_tor_output_lines.Add(The_line);
            if (The_tor_output_lines.Count > 40)
            {
                The_tor_output_lines.RemoveAt(0);
            }
        }

        try
        {
            The_log($"Admin create server Tor: {The_line}");
        }
        catch
        {
            // Tor output logging must never interrupt package creation.
        }
    }

    private static string Admin__file_name__sanitize(string The_file_name)
    {
        StringBuilder sanitized = new();
        bool previousWasSeparator = false;

        foreach (char character in The_file_name.Trim())
        {
            bool keepCharacter = character is >= 'A' and <= 'Z'
                || character is >= 'a' and <= 'z'
                || character is >= '0' and <= '9';

            if (keepCharacter)
            {
                sanitized.Append(character);
                previousWasSeparator = false;
                continue;
            }

            if (character is ' ' or '-' or '_')
            {
                if (!previousWasSeparator)
                {
                    sanitized.Append('_');
                    previousWasSeparator = true;
                }
            }
        }

        string safeName = sanitized.ToString().Trim('_');
        return string.IsNullOrWhiteSpace(safeName) ? "Conversation" : safeName;
    }
}
