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

namespace ITNexusAgent.Services;

// Startet Prozesse in der aktiven User-Session (aus SYSTEM-Service heraus)
// Korrekte Win32-Methode: WTSQueryUserToken + CreateProcessAsUser
public static class SessionSpawner
{
    #region Win32 P/Invoke

    [DllImport("kernel32.dll")] static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll", SetLastError = true)]
    static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken);

    [DllImport("Wtsapi32.dll", SetLastError = true)]
    static extern bool WTSEnumerateSessions(IntPtr hServer, uint reserved, uint version,
        out IntPtr ppSessionInfo, out uint pCount);

    [DllImport("Wtsapi32.dll")] static extern void WTSFreeMemory(IntPtr pMemory);

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess,
        IntPtr lpTokenAttributes, int impersonationLevel, int tokenType, out IntPtr phNewToken);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool CreateProcessAsUser(IntPtr hToken, string? lpApplicationName,
        string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,
        bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment,
        string? lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("userenv.dll", SetLastError = true)]
    static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

    [DllImport("userenv.dll", SetLastError = true)]
    static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr hObject);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct STARTUPINFO
    {
        public int cb; public string? lpReserved; public string? lpDesktop; public string? lpTitle;
        public uint dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
        public ushort wShowWindow, cbReserved2; public IntPtr lpReserved2;
        public IntPtr hStdInput, hStdOutput, hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr hProcess, hThread;
        public uint dwProcessId, dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct WTS_SESSION_INFO
    {
        public uint SessionId; [MarshalAs(UnmanagedType.LPStr)] public string? pWinStationName;
        public int State; // 0=Active
    }

    #endregion

    // Spawnt Prozess als eingeloggter Desktop-User. Gibt PID zurück oder -1 bei Fehler.
    public static int SpawnInUserSession(string exePath, string args)
    {
        var sessionId = FindActiveUserSession();
        if (sessionId == uint.MaxValue)
        {
            AgentWorker.Log("SessionSpawner: Keine aktive User-Session gefunden");
            return -1;
        }

        if (!WTSQueryUserToken(sessionId, out var userToken))
        {
            AgentWorker.Log($"SessionSpawner: WTSQueryUserToken fehlgeschlagen (Session={sessionId}, Error={Marshal.GetLastWin32Error()})");
            return -1;
        }

        try
        {
            if (!DuplicateTokenEx(userToken, 0x10000000u, IntPtr.Zero, 2, 1, out var dupToken))
            {
                AgentWorker.Log($"SessionSpawner: DuplicateTokenEx fehlgeschlagen (Error={Marshal.GetLastWin32Error()})");
                return -1;
            }

            try
            {
                CreateEnvironmentBlock(out var envBlock, dupToken, false);

                var si = new STARTUPINFO
                {
                    cb = Marshal.SizeOf<STARTUPINFO>(),
                    lpDesktop = "winsta0\\default",
                    dwFlags = 1,    // STARTF_USESHOWWINDOW
                    wShowWindow = 1 // SW_SHOWNORMAL
                };

                var cmdLine = $"\"{exePath}\" {args}";
                bool ok = CreateProcessAsUser(dupToken, null, cmdLine,
                    IntPtr.Zero, IntPtr.Zero, false,
                    0x0400,  // CREATE_UNICODE_ENVIRONMENT
                    envBlock, null, ref si, out var pi);

                if (envBlock != IntPtr.Zero) DestroyEnvironmentBlock(envBlock);

                if (!ok)
                {
                    AgentWorker.Log($"SessionSpawner: CreateProcessAsUser fehlgeschlagen (Error={Marshal.GetLastWin32Error()})");
                    return -1;
                }

                CloseHandle(pi.hThread);
                CloseHandle(pi.hProcess);
                AgentWorker.Log($"SessionSpawner: Prozess gestartet (PID={pi.dwProcessId}, Session={sessionId})");
                return (int)pi.dwProcessId;
            }
            finally { CloseHandle(dupToken); }
        }
        finally { CloseHandle(userToken); }
    }

    private static uint FindActiveUserSession()
    {
        // Zuerst Console-Session versuchen
        var consoleSession = WTSGetActiveConsoleSessionId();
        if (consoleSession != uint.MaxValue && TryGetTokenForSession(consoleSession))
            return consoleSession;

        // Alle Sessions durchsuchen → erste aktive (State=0) nehmen
        if (!WTSEnumerateSessions(IntPtr.Zero, 0, 1, out var pInfo, out var count))
            return uint.MaxValue;

        try
        {
            var size = Marshal.SizeOf<WTS_SESSION_INFO>();
            for (int i = 0; i < count; i++)
            {
                var info = Marshal.PtrToStructure<WTS_SESSION_INFO>(IntPtr.Add(pInfo, i * size));
                if (info.State == 0 && info.SessionId != 0) // Active, nicht Session 0
                    return info.SessionId;
            }
        }
        finally { WTSFreeMemory(pInfo); }

        return uint.MaxValue;
    }

    private static bool TryGetTokenForSession(uint sessionId)
    {
        if (!WTSQueryUserToken(sessionId, out var tok)) return false;
        CloseHandle(tok);
        return true;
    }
}
