// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/signal.go and server/service.go in the NATS server Go source.

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ZB.MOM.NatsNet.Server.Internal;

/// <summary>
/// Maps <see cref="ServerCommand"/> to OS signal-like behavior.
/// Mirrors <c>CommandToSignal</c> and <c>ProcessSignal</c> from signal.go.
/// In .NET, signal sending is replaced by process-level signaling on Unix.
/// </summary>
public static class SignalHandler
{
    private static string _processName = "nats-server";

    /// <summary>
    /// Sets the process name used for resolving PIDs.
    /// Mirrors <c>SetProcessName</c> in signal.go.
    /// </summary>
    public static void SetProcessName(string name) => _processName = name;

    /// <summary>
    /// Sends a signal command to a running NATS server process.
    /// On Unix, maps commands to kill signals.
    /// On Windows, this is a no-op (service manager handles signals).
    /// Mirrors <c>ProcessSignal</c> in signal.go.
    /// </summary>
    public static Exception? ProcessSignal(ServerCommand command, string pidExpr = "")
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            return new PlatformNotSupportedException("Signal processing not supported on Windows; use service manager.");

        try
        {
            List<int> pids;
            if (string.IsNullOrEmpty(pidExpr))
            {
                pids = ResolvePids();
                if (pids.Count == 0)
                    return new InvalidOperationException("no nats-server processes found");
            }
            else
            {
                if (int.TryParse(pidExpr, out var pid))
                    pids = [pid];
                else
                    return new InvalidOperationException($"invalid pid: {pidExpr}");
            }

            var signal = CommandToUnixSignal(command);

            foreach (var pid in pids)
                Process.GetProcessById(pid).Kill(signal == UnixSignal.SigKill);

            return null;
        }
        catch (Exception ex)
        {
            return ex;
        }
    }

    /// <summary>
    /// Resolves PIDs of running nats-server processes via pgrep.
    /// Mirrors <c>resolvePids</c> in signal.go.
    /// </summary>
    public static List<int> ResolvePids()
    {
        var pids = new List<int>();
        try
        {
            var psi = new ProcessStartInfo("pgrep", _processName)
            {
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var proc = Process.Start(psi);
            if (proc == null) return pids;

            var output = proc.StandardOutput.ReadToEnd();
            proc.WaitForExit();

            var currentPid = Environment.ProcessId;
            foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
            {
                if (int.TryParse(line.Trim(), out var pid) && pid != currentPid)
                    pids.Add(pid);
            }
        }
        catch
        {
            // pgrep not available or failed
        }
        return pids;
    }

    /// <summary>
    /// Maps a server command to Unix signal.
    /// Mirrors <c>CommandToSignal</c> in signal.go.
    /// </summary>
    public static UnixSignal CommandToUnixSignal(ServerCommand command) => command switch
    {
        ServerCommand.Stop => UnixSignal.SigKill,
        ServerCommand.Quit => UnixSignal.SigInt,
        ServerCommand.Reopen => UnixSignal.SigUsr1,
        ServerCommand.Reload => UnixSignal.SigHup,
        _ => throw new ArgumentOutOfRangeException(nameof(command), $"unknown command: {command}"),
    };

    /// <summary>
    /// Runs the server (non-Windows). Mirrors <c>Run</c> in service.go.
    /// </summary>
    public static void Run(Action startServer) => startServer();

    /// <summary>
    /// Returns false on non-Windows. Mirrors <c>isWindowsService</c>.
    /// </summary>
    public static bool IsWindowsService() => false;
}

/// <summary>Unix signal codes for NATS command mapping.</summary>
public enum UnixSignal
{
    SigInt = 2,
    SigKill = 9,
    SigUsr1 = 10,
    SigHup = 1,
    SigUsr2 = 12,
    SigTerm = 15,
}
