using System.Diagnostics;

namespace Noctalk_server.Server.Tor;

public sealed class Tor_service : IDisposable
{
    private readonly Tor_file_layout torFileLayout;
    private readonly Action<string> log;
    private Process? torProcess;

    public Tor_service(string The_executable_directory, Action<string> The_log)
    {
        torFileLayout = new Tor_file_layout(The_executable_directory);
        log = The_log;
    }

    public Tor_file_layout Tor__file_layout => torFileLayout;

    public async Task<Tor_startup_result> Tor__startup__start_async(int The_local_relay_port, CancellationToken The_cancellation_token)
    {
        Directory.CreateDirectory(torFileLayout.Tor__directory);
        Directory.CreateDirectory(torFileLayout.Tor__data_directory);
        Torrc_generator.Torrc__generate__server(torFileLayout, The_local_relay_port);

        if (!torFileLayout.Is__tor_placed_correctly)
        {
            return new Tor_startup_result
            {
                Flag__success = false,
                Message_to_client = "Tor files are missing. Required: darknet\\tor\\tor.exe, geoip, geoip6, torrc."
            };
        }

        torProcess = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = torFileLayout.Tor__exe_path,
                Arguments = $"-f \"{torFileLayout.Tor__torrc_path}\"",
                WorkingDirectory = torFileLayout.Tor__directory,
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            },
            EnableRaisingEvents = true
        };

        torProcess.OutputDataReceived += (_, e) => Tor__process__log(e.Data);
        torProcess.ErrorDataReceived += (_, e) => Tor__process__log(e.Data);
        torProcess.Exited += (_, _) => log($"Tor exited with code {torProcess?.ExitCode}.");

        log("Starting Tor onion service...");
        torProcess.Start();
        torProcess.BeginOutputReadLine();
        torProcess.BeginErrorReadLine();

        string? onionAddress = await Tor__onion_service__wait_for_hostname_async(The_cancellation_token);

        if (string.IsNullOrWhiteSpace(onionAddress))
        {
            return new Tor_startup_result
            {
                Flag__success = false,
                Message_to_client = "Tor started, but onion hostname was not available before timeout."
            };
        }

        return new Tor_startup_result
        {
            Flag__success = true,
            Onion_address = onionAddress,
            Message_to_client = "Tor onion service is running."
        };
    }

    public void Tor__startup__stop()
    {
        if (torProcess is null)
        {
            return;
        }

        try
        {
            if (!torProcess.HasExited)
            {
                log("Stopping Tor...");
                torProcess.Kill(entireProcessTree: true);
                torProcess.WaitForExit(5000);
            }
        }
        catch (Exception exception)
        {
            log($"Tor stop failed: {exception.Message}");
        }
        finally
        {
            torProcess.Dispose();
            torProcess = null;
        }
    }

    public void Dispose()
    {
        Tor__startup__stop();
    }

    private async Task<string?> Tor__onion_service__wait_for_hostname_async(CancellationToken The_cancellation_token)
    {
        using CancellationTokenSource timeoutCancellation = CancellationTokenSource.CreateLinkedTokenSource(The_cancellation_token);
        timeoutCancellation.CancelAfter(TimeSpan.FromSeconds(90));

        while (!timeoutCancellation.IsCancellationRequested)
        {
            string? hostname = Tor__onion_service__read_hostname();

            if (!string.IsNullOrWhiteSpace(hostname))
            {
                return hostname;
            }

            await Task.Delay(1000, timeoutCancellation.Token).ContinueWith(_ => { }, CancellationToken.None);
        }

        return null;
    }

    public string? Tor__onion_service__read_hostname()
    {
        if (!File.Exists(torFileLayout.Onion_service__hostname_path))
        {
            return null;
        }

        return File.ReadAllText(torFileLayout.Onion_service__hostname_path).Trim();
    }

    private void Tor__process__log(string? The_line)
    {
        if (!string.IsNullOrWhiteSpace(The_line))
        {
            log($"Tor: {The_line}");
        }
    }
}
