using System.Diagnostics;
using System.IO;

namespace Noctalk_client.Client.Networking.Tor;

public sealed class Tor_client_service : IDisposable
{
    private readonly string executableDirectory;
    private readonly Action<string> log;
    private Process? torProcess;
    private bool isStopping;
    private TaskCompletionSource<bool>? bootstrapTaskCompletionSource;

    public Tor_client_service(string The_executable_directory, Action<string> The_log)
    {
        executableDirectory = The_executable_directory;
        log = The_log;
    }

    public async Task<bool> Tor__client__start_async(int The_client_local_tor_port, CancellationToken The_cancellation_token)
    {
        Torrc_generator.Torrc__generate__client(executableDirectory, The_client_local_tor_port);

        string torDirectory = Path.Combine(executableDirectory, "darknet", "tor");
        string torExePath = Path.Combine(torDirectory, "tor.exe");
        string torrcPath = Path.Combine(torDirectory, "torrc");

        if (!File.Exists(torExePath))
        {
            log("Client Tor not started: darknet\\tor\\tor.exe missing.");
            return false;
        }

        Tor__client__stop_existing_runtime_tor_processes(torExePath);
        await Tor__client__wait_until_port_free(The_client_local_tor_port, The_cancellation_token);

        if (torProcess is not null && !torProcess.HasExited)
        {
            return true;
        }

        isStopping = false;

        torProcess = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = torExePath,
                Arguments = $"-f \"{torrcPath}\"",
                WorkingDirectory = torDirectory,
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            },
            EnableRaisingEvents = true
        };

        torProcess.OutputDataReceived += Tor__client__output_data_received;
        torProcess.ErrorDataReceived += Tor__client__output_data_received;
        torProcess.Exited += (_, _) =>
        {
            int? exitCode = torProcess?.ExitCode;
            log($"Client Tor exited with code {exitCode}.");

            if (exitCode != 0)
            {
                bootstrapTaskCompletionSource?.TrySetResult(false);
                Tor__client__log_port_owner();
            }
        };

        bootstrapTaskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
        log($"Starting client Tor SOCKS proxy on 127.0.0.1:{The_client_local_tor_port}...");
        torProcess.Start();
        torProcess.BeginOutputReadLine();
        torProcess.BeginErrorReadLine();
        using CancellationTokenRegistration registration = The_cancellation_token.Register(() => bootstrapTaskCompletionSource.TrySetCanceled(The_cancellation_token));
        return await bootstrapTaskCompletionSource.Task;
    }

    private void Tor__client__stop_existing_runtime_tor_processes(string The_tor_exe_path)
    {
        string expectedTorExePath = Path.GetFullPath(The_tor_exe_path);

        foreach (Process process in Process.GetProcessesByName("tor"))
        {
            try
            {
                string? processPath = process.MainModule?.FileName;

                if (process.Id == torProcess?.Id || !string.Equals(Path.GetFullPath(processPath ?? string.Empty), expectedTorExePath, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                log($"Stopping existing client Tor process {process.Id} before startup.");
                process.Kill(entireProcessTree: true);
                process.WaitForExit(2500);
            }
            catch (Exception exception)
            {
                log($"Could not stop existing client Tor process {process.Id}: {exception.Message}");
            }
            finally
            {
                process.Dispose();
            }
        }
    }

    private void Tor__client__log_port_owner()
    {
        try
        {
            ProcessStartInfo processStartInfo = new()
            {
                FileName = "netstat.exe",
                Arguments = "-ano -p tcp",
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            };

            using Process process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("Could not start netstat.");
            string output = process.StandardOutput.ReadToEnd();
            process.WaitForExit(2000);

            string? portLine = output.Split(Environment.NewLine).FirstOrDefault(The_line => The_line.Contains("127.0.0.1:") && The_line.Contains("LISTENING"));

            if (string.IsNullOrWhiteSpace(portLine))
            {
                return;
            }

            string[] parts = portLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
            string processIdText = parts.LastOrDefault() ?? string.Empty;

            if (!int.TryParse(processIdText, out int processId))
            {
                log($"Client Tor port owner could not be parsed: {portLine.Trim()}");
                return;
            }

            using Process ownerProcess = Process.GetProcessById(processId);
            string ownerPath = ownerProcess.MainModule?.FileName ?? "unknown path";
            log($"Client Tor port is owned by process {processId}: {ownerPath}");
        }
        catch (Exception exception)
        {
            log($"Client Tor port owner check failed: {exception.Message}");
        }
    }

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

        isStopping = true;

        try
        {
            torProcess.OutputDataReceived -= Tor__client__output_data_received;
            torProcess.ErrorDataReceived -= Tor__client__output_data_received;

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

    public void Dispose()
    {
        Tor__client__stop();
    }

    private void Tor__client__output_data_received(object sender, DataReceivedEventArgs e)
    {
        Tor__client__log(e.Data);
    }

    private void Tor__client__log(string? The_line)
    {
        if (!isStopping && !string.IsNullOrWhiteSpace(The_line))
        {
            log($"Client Tor: {The_line}");

            if (The_line.Contains("Bootstrapped 100% (done): Done", StringComparison.OrdinalIgnoreCase))
            {
                bootstrapTaskCompletionSource?.TrySetResult(true);
            }

            if (The_line.Contains("Reading config failed", StringComparison.OrdinalIgnoreCase) || The_line.Contains("Failed to bind", StringComparison.OrdinalIgnoreCase))
            {
                bootstrapTaskCompletionSource?.TrySetResult(false);
            }
        }
    }

    private async Task Tor__client__wait_until_port_free(int The_port, CancellationToken The_cancellation_token)
    {
        for (int index = 0; index < 20; index++)
        {
            if (!Tor__client__is_port_listening(The_port))
            {
                return;
            }

            await Task.Delay(250, The_cancellation_token);
        }
    }

    public static bool Tor__client__is_port_listening(int The_port)
    {
        try
        {
            ProcessStartInfo processStartInfo = new()
            {
                FileName = "netstat.exe",
                Arguments = "-ano -p tcp",
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true
            };

            using Process process = Process.Start(processStartInfo) ?? throw new InvalidOperationException();
            string output = process.StandardOutput.ReadToEnd();
            process.WaitForExit(2000);
            return output.Split(Environment.NewLine).Any(The_line => The_line.Contains($"127.0.0.1:{The_port}") && The_line.Contains("LISTENING"));
        }
        catch
        {
            return false;
        }
    }
}
