package wincmd

import (
	"fmt"
	"strconv"
	"strings"
	"unsafe"

	"b612.me/win32api"
	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/registry"
)

func StartProcessWithSYS(appPath, cmdLine, workDir string, runas bool) error {
	var (
		sessionId          win32api.HANDLE
		userToken          win32api.TOKEN = 0
		envInfo            win32api.HANDLE
		impersonationToken win32api.HANDLE = 0
		startupInfo        win32api.StartupInfo
		processInfo        win32api.ProcessInformation
		sessionInformation win32api.HANDLE              = win32api.HANDLE(0)
		sessionCount       int                          = 0
		sessionList        []*win32api.WTS_SESSION_INFO = make([]*win32api.WTS_SESSION_INFO, 0)
		err                error
	)
	if err := win32api.WTSEnumerateSessions(0, 0, 1, &sessionInformation, &sessionCount); err != nil {
		return err
	}
	structSize := unsafe.Sizeof(win32api.WTS_SESSION_INFO{})
	current := uintptr(sessionInformation)
	for i := 0; i < sessionCount; i++ {
		sessionList = append(sessionList, (*win32api.WTS_SESSION_INFO)(unsafe.Pointer(current)))
		current += structSize
	}
	if sessionId, err = func() (win32api.HANDLE, error) {
		for i := range sessionList {
			if sessionList[i].State == win32api.WTSActive {
				return sessionList[i].SessionID, nil
			}
		}
		if sessionId, err := win32api.WTSGetActiveConsoleSessionId(); sessionId == 0xFFFFFFFF {
			return 0xFFFFFFFF, fmt.Errorf("get current user session token: call native WTSGetActiveConsoleSessionId: %s", err)
		} else {
			return win32api.HANDLE(sessionId), nil
		}
	}(); err != nil {
		return err
	}

	if err := win32api.WTSQueryUserToken(sessionId, &impersonationToken); err != nil {
		return err
	}

	if err := win32api.DuplicateTokenEx(impersonationToken, 0, 0, int(win32api.SecurityImpersonation), win32api.TokenPrimary, &userToken); err != nil {
		return fmt.Errorf("call native DuplicateTokenEx: %s", err)
	}
	if runas {
		var admin win32api.TOKEN_LINKED_TOKEN
		var dt uintptr = 0
		if err := win32api.GetTokenInformation(impersonationToken, 19, uintptr(unsafe.Pointer(&admin)), uintptr(unsafe.Sizeof(admin)), &dt); err == nil {
			userToken = admin.LinkedToken
		}
	}
	if err := win32api.CloseHandle(impersonationToken); err != nil {
		return fmt.Errorf("close windows handle used for token duplication: %s", err)
	}

	if err := win32api.CreateEnvironmentBlock(&envInfo, userToken, 0); err != nil {
		return fmt.Errorf("create environment details for process: %s", err)
	}
	creationFlags := win32api.CREATE_UNICODE_ENVIRONMENT | win32api.CREATE_NEW_CONSOLE
	startupInfo.ShowWindow = win32api.SW_SHOW
	startupInfo.Desktop = windows.StringToUTF16Ptr("winsta0\\default")
	if err := win32api.CreateProcessAsUser(userToken, appPath, cmdLine, 0, 0, 0,
		creationFlags, envInfo, workDir, &startupInfo, &processInfo); err != nil {
		return fmt.Errorf("create process as user: %s", err)
	}
	return nil
}

func GetRunningProcess() ([]map[string]string, error) {
	result := []map[string]string{}
	pHandle, err := win32api.CreateToolhelp32Snapshot(0x2, 0x0)
	if err != nil {
		return result, err
	}
	for {
		var proc win32api.PROCESSENTRY32
		proc.DwSize = win32api.Ulong(unsafe.Sizeof(proc))
		if err := win32api.Process32Next(pHandle, &proc); err == nil {
			bytetmp := proc.SzExeFile[0:]
			var sakura []byte
			for _, v := range bytetmp {
				if v == byte(0) {
					break
				}
				sakura = append(sakura, v)
			}
			result = append(result, map[string]string{"name": string(sakura), "pid": strconv.Itoa(int(proc.Th32ProcessID)), "ppid": fmt.Sprint(int(proc.Th32ParentProcessID))})
		} else {
			break
		}
	}
	win32api.CloseHandle(pHandle)
	return result, nil
}

func IsProcessRunningByPID(pid int) bool {
	pHandle, err := win32api.CreateToolhelp32Snapshot(0x2, 0x0)
	if err != nil {
		return false
	}
	for {
		var proc win32api.PROCESSENTRY32
		proc.DwSize = win32api.Ulong(unsafe.Sizeof(proc))
		if err := win32api.Process32Next(pHandle, &proc); err == nil {
			bytetmp := int(proc.Th32ProcessID)
			if bytetmp == pid {
				return true
			}
		} else {
			break
		}
	}
	win32api.CloseHandle(pHandle)
	return false
}
func IsProcessRunning(name string) bool {
	pHandle, err := win32api.CreateToolhelp32Snapshot(0x2, 0x0)
	if err != nil {
		return false
	}
	for {
		var proc win32api.PROCESSENTRY32
		proc.DwSize = win32api.Ulong(unsafe.Sizeof(proc))
		if err := win32api.Process32Next(pHandle, &proc); err == nil {
			bytetmp := proc.SzExeFile[0:]
			var sakura []byte
			for _, v := range bytetmp {
				if v == byte(0) {
					break
				}
				sakura = append(sakura, v)
			}
			if strings.ToLower(strings.TrimSpace(string(sakura))) == strings.ToLower(strings.TrimSpace(name)) {
				return true
			}
		} else {
			break
		}
	}
	win32api.CloseHandle(pHandle)
	return false
}

func GetProcessCount(name string) int {
	var res int = 0
	pHandle, err := win32api.CreateToolhelp32Snapshot(0x2, 0x0)
	if err != nil {
		return 0
	}
	for {
		var proc win32api.PROCESSENTRY32
		proc.DwSize = win32api.Ulong(unsafe.Sizeof(proc))
		if err := win32api.Process32Next(pHandle, &proc); err == nil {
			bytetmp := proc.SzExeFile[0:]
			var sakura []byte
			for _, v := range bytetmp {
				if v == byte(0) {
					break
				}
				sakura = append(sakura, v)
			}
			if strings.ToLower(strings.TrimSpace(string(sakura))) == strings.ToLower(strings.TrimSpace(name)) {
				res++
			}
		} else {
			break
		}
	}
	win32api.CloseHandle(pHandle)
	return res
}

func Isas() bool {
	_, errs := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM`, registry.ALL_ACCESS)
	if errs != nil {
		return false
	}
	return true
}

func StartProcess(appPath, cmdLine, wordDir string, runas bool, ShowWindow int) bool {
	var cst string
	if runas {
		cst = "runas"
	} else {
		cst = "open"
	}
	r := win32api.ShellExecute(0, cst, appPath, cmdLine, wordDir, ShowWindow)
	if r != nil {
		return false
	}
	return true
}
func StartProcessWithPID(appPath, cmdLine, workDir string, runas bool, ShowWindow int) int {
	var sakura win32api.SHELLEXECUTEINFOW
	sakura.Hwnd = 0
	sakura.NShow = ShowWindow
	sakura.FMask = 0x00000040
	sakura.LpParameters = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cmdLine)))
	sakura.LpFile = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath)))
	sakura.LpDirectory = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(workDir)))
	if runas {
		sakura.LpVerb = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("runas")))
	} else {
		sakura.LpVerb = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("open")))
	}
	sakura.CbSize = win32api.DWORD(unsafe.Sizeof(sakura))

	if err := win32api.ShellExecuteEx(&sakura); err != nil {
		return 0
	}
	return int(win32api.GetProcessId(sakura.HProcess))
}

func AutoRun(key, path string) (bool, error) {
	reg, errs := registry.OpenKey(registry.LOCAL_MACHINE, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.ALL_ACCESS)
	if errs != nil {
		return false, errs
	}
	if errs = reg.SetStringValue(key, path); errs != nil {
		return false, errs
	}
	return true, nil
}

func DeleteAutoRun(key string) (bool, error) {
	reg, errs := registry.OpenKey(registry.LOCAL_MACHINE, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.ALL_ACCESS)
	if errs != nil {
		return false, errs
	}
	if _, i, _ := reg.GetStringValue(key); i == 0 {
		return true, nil
	}
	if errs = reg.DeleteValue(key); errs != nil {
		return false, errs
	}
	return true, nil
}

func IsAutoRun(key, path string) bool {
	reg, errs := registry.OpenKey(registry.LOCAL_MACHINE, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.ALL_ACCESS)
	if errs != nil {
		return false
	}
	if sa, _, _ := reg.GetStringValue(key); sa == path {
		return true
	}
	return false
}
