using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace MBN_STOCK_WEBVIEW.Playout.Diagnostics;

internal readonly record struct PgmDiagnosticTargetIdentity(
    int ProcessId,
    long StartTimeUtcTicks);

internal sealed record PgmDiagnosticTargetSnapshot(
    bool InspectionSucceeded,
    int TotalTornadoProcessCount,
    int ExactWindowCount,
    bool ListenerOwnershipConfirmed,
    PgmDiagnosticTargetIdentity? Identity)
{
    public bool ProcessGatePassed =>
        InspectionSucceeded &&
        TotalTornadoProcessCount == 1 &&
        ExactWindowCount == 1 &&
        Identity is not null;

    public bool IsEligible => ProcessGatePassed && ListenerOwnershipConfirmed;

    public bool HasSameTarget(PgmDiagnosticTargetSnapshot other) =>
        IsEligible && other.IsEligible && Identity == other.Identity;
}

internal interface IPgmDiagnosticSafetyProbe
{
    PgmDiagnosticTargetSnapshot Capture(
        PgmConnectDiagnosticRequest request,
        IPAddress parsedHost);
}

internal interface ITcpListenerOwnershipProbe
{
    bool TryIsOwnedBy(
        IPAddress requestedLocalAddress,
        int port,
        int processId,
        out bool isOwned);
}

internal readonly record struct TcpListenerOwnershipEntry(
    IPAddress LocalAddress,
    int Port,
    int ProcessId);

internal sealed class WindowsPgmDiagnosticSafetyProbe : IPgmDiagnosticSafetyProbe
{
    private readonly ITcpListenerOwnershipProbe _listenerProbe;

    public WindowsPgmDiagnosticSafetyProbe()
        : this(new WindowsTcpListenerOwnershipProbe())
    {
    }

    internal WindowsPgmDiagnosticSafetyProbe(ITcpListenerOwnershipProbe listenerProbe)
    {
        _listenerProbe = listenerProbe;
    }

    public PgmDiagnosticTargetSnapshot Capture(
        PgmConnectDiagnosticRequest request,
        IPAddress parsedHost)
    {
        var inspectionSucceeded = true;
        var totalCount = 0;
        var exactWindowCount = 0;
        PgmDiagnosticTargetIdentity? identity = null;
        Process[] processes;
        try
        {
            processes = Process.GetProcesses();
        }
        catch (Exception exception) when (IsInspectionException(exception))
        {
            return new PgmDiagnosticTargetSnapshot(false, 0, 0, false, null);
        }

        foreach (var process in processes)
        {
            try
            {
                string processName;
                try
                {
                    processName = process.ProcessName;
                }
                catch (Exception exception) when (IsExitedProcessException(exception))
                {
                    continue;
                }
                catch (Exception exception) when (IsInspectionException(exception))
                {
                    inspectionSucceeded = false;
                    continue;
                }

                if (!processName.StartsWith("Tornado2", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                totalCount++;
                string title;
                try
                {
                    title = process.MainWindowTitle;
                }
                catch (Exception exception) when (IsInspectionException(exception))
                {
                    inspectionSucceeded = false;
                    continue;
                }

                if (!string.Equals(
                        title,
                        request.ExpectedPgmWindowTitle,
                        StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                exactWindowCount++;
                try
                {
                    identity = new PgmDiagnosticTargetIdentity(
                        process.Id,
                        process.StartTime.ToUniversalTime().Ticks);
                }
                catch (Exception exception) when (IsInspectionException(exception))
                {
                    inspectionSucceeded = false;
                    identity = null;
                }
            }
            finally
            {
                process.Dispose();
            }
        }

        var listenerOwned = false;
        if (inspectionSucceeded &&
            totalCount == 1 &&
            exactWindowCount == 1 &&
            identity is { } target)
        {
            if (!_listenerProbe.TryIsOwnedBy(
                    parsedHost,
                    request.Port,
                    target.ProcessId,
                    out listenerOwned))
            {
                inspectionSucceeded = false;
                listenerOwned = false;
            }
        }

        return new PgmDiagnosticTargetSnapshot(
            inspectionSucceeded,
            totalCount,
            exactWindowCount,
            listenerOwned,
            identity);
    }

    private static bool IsInspectionException(Exception exception) =>
        exception is InvalidOperationException or
            ArgumentException or
            Win32Exception or
            NotSupportedException or
            UnauthorizedAccessException;

    private static bool IsExitedProcessException(Exception exception) =>
        exception is InvalidOperationException or ArgumentException;
}

internal sealed class WindowsTcpListenerOwnershipProbe : ITcpListenerOwnershipProbe
{
    private const uint ErrorInsufficientBuffer = 122;
    private const uint NoError = 0;
    private const int AfInet = 2;
    private const int AfInet6 = 23;

    public bool TryIsOwnedBy(
        IPAddress requestedLocalAddress,
        int port,
        int processId,
        out bool isOwned)
    {
        isOwned = false;
        if (!IsValidRequestedEndpoint(requestedLocalAddress, port, processId))
        {
            return false;
        }

        var addressFamily = requestedLocalAddress.AddressFamily;
        var family = addressFamily switch
        {
            AddressFamily.InterNetwork => AfInet,
            AddressFamily.InterNetworkV6 => AfInet6,
            _ => 0
        };
        if (family == 0)
        {
            return false;
        }

        IntPtr buffer = IntPtr.Zero;
        try
        {
            var size = 0;
            var result = GetExtendedTcpTable(
                IntPtr.Zero,
                ref size,
                false,
                family,
                TcpTableClass.OwnerPidListener,
                0);
            if (result is not (ErrorInsufficientBuffer or NoError) || size <= 0)
            {
                return false;
            }

            buffer = Marshal.AllocHGlobal(size);
            result = GetExtendedTcpTable(
                buffer,
                ref size,
                false,
                family,
                TcpTableClass.OwnerPidListener,
                0);
            if (result != NoError)
            {
                return false;
            }

            var count = Marshal.ReadInt32(buffer);
            if (count < 0)
            {
                return false;
            }

            var listeners = new List<TcpListenerOwnershipEntry>(count);
            var rowPointer = IntPtr.Add(buffer, sizeof(uint));
            if (addressFamily == AddressFamily.InterNetwork)
            {
                var rowSize = Marshal.SizeOf<MibTcpRowOwnerPid>();
                for (var index = 0; index < count; index++)
                {
                    var row = Marshal.PtrToStructure<MibTcpRowOwnerPid>(rowPointer);
                    listeners.Add(new TcpListenerOwnershipEntry(
                        new IPAddress(row.LocalAddress),
                        DecodePort(row.LocalPort),
                        row.OwningPid));

                    rowPointer = IntPtr.Add(rowPointer, rowSize);
                }
            }
            else
            {
                var rowSize = Marshal.SizeOf<MibTcp6RowOwnerPid>();
                for (var index = 0; index < count; index++)
                {
                    var row = Marshal.PtrToStructure<MibTcp6RowOwnerPid>(rowPointer);
                    if (row.LocalAddress is not { Length: 16 })
                    {
                        return false;
                    }

                    listeners.Add(new TcpListenerOwnershipEntry(
                        new IPAddress(row.LocalAddress, row.LocalScopeId),
                        DecodePort(row.LocalPort),
                        row.OwningPid));

                    rowPointer = IntPtr.Add(rowPointer, rowSize);
                }
            }

            return TryEvaluateOwnership(
                requestedLocalAddress,
                port,
                processId,
                listeners,
                out isOwned);
        }
        catch
        {
            isOwned = false;
            return false;
        }
        finally
        {
            if (buffer != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(buffer);
            }
        }
    }

    internal static bool TryEvaluateOwnership(
        IPAddress requestedLocalAddress,
        int port,
        int processId,
        IReadOnlyList<TcpListenerOwnershipEntry> listeners,
        out bool isOwned)
    {
        isOwned = false;
        if (!IsValidRequestedEndpoint(requestedLocalAddress, port, processId) ||
            listeners is null)
        {
            return false;
        }

        var wildcard = requestedLocalAddress.AddressFamily switch
        {
            AddressFamily.InterNetwork => IPAddress.Any,
            AddressFamily.InterNetworkV6 => IPAddress.IPv6Any,
            _ => null
        };
        if (wildcard is null)
        {
            return false;
        }

        var targetListenerFound = false;
        foreach (var listener in listeners)
        {
            if (listener.LocalAddress is null)
            {
                return false;
            }

            if (listener.Port != port ||
                listener.LocalAddress.AddressFamily != requestedLocalAddress.AddressFamily ||
                (!listener.LocalAddress.Equals(requestedLocalAddress) &&
                 !listener.LocalAddress.Equals(wildcard)))
            {
                continue;
            }

            if (listener.ProcessId != processId)
            {
                // Any competing owner able to accept the requested endpoint fails closed,
                // even if the target-owned row appeared earlier in the table.
                return true;
            }

            targetListenerFound = true;
        }

        isOwned = targetListenerFound;
        return true;
    }

    private static bool IsValidRequestedEndpoint(
        IPAddress? requestedLocalAddress,
        int port,
        int processId) =>
        requestedLocalAddress is not null &&
        IPAddress.IsLoopback(requestedLocalAddress) &&
        requestedLocalAddress.AddressFamily is
            AddressFamily.InterNetwork or AddressFamily.InterNetworkV6 &&
        port is >= 1 and <= 65_535 &&
        processId > 0;

    internal static int DecodePort(uint value) =>
        unchecked((ushort)IPAddress.NetworkToHostOrder((short)value));

    [DllImport("iphlpapi.dll", SetLastError = true)]
    private static extern uint GetExtendedTcpTable(
        IntPtr tcpTable,
        ref int size,
        [MarshalAs(UnmanagedType.Bool)] bool order,
        int addressFamily,
        TcpTableClass tableClass,
        uint reserved);

    private enum TcpTableClass
    {
        OwnerPidListener = 3
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MibTcpRowOwnerPid
    {
        public uint State;
        public uint LocalAddress;
        public uint LocalPort;
        public uint RemoteAddress;
        public uint RemotePort;
        public int OwningPid;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MibTcp6RowOwnerPid
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] LocalAddress;
        public uint LocalScopeId;
        public uint LocalPort;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] RemoteAddress;
        public uint RemoteScopeId;
        public uint RemotePort;
        public uint State;
        public int OwningPid;
    }
}
