package cli

import (
	"os"
	"runtime"
	"syscall"
	"time"
	"unsafe"

	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/svc/mgr"
)

// hasElevatedPrivilege checks if the current process has elevated privileges on Windows
func hasElevatedPrivilege() (bool, error) {
	var sid *windows.SID
	if err := windows.AllocateAndInitializeSid(
		&windows.SECURITY_NT_AUTHORITY,
		2,
		windows.SECURITY_BUILTIN_DOMAIN_RID,
		windows.DOMAIN_ALIAS_RID_ADMINS,
		0,
		0,
		0,
		0,
		0,
		0,
		&sid,
	); err != nil {
		return false, err
	}
	token := windows.Token(0)
	return token.IsMember(sid)
}

// ConfigureWindowsServiceFailureActions checks if the given service
// has the correct failure actions configured, and updates them if not.
func ConfigureWindowsServiceFailureActions(serviceName string) error {
	if runtime.GOOS != "windows" {
		return nil // no-op on non-Windows
	}

	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()

	s, err := m.OpenService(serviceName)
	if err != nil {
		return err
	}
	defer s.Close()

	// 1. Retrieve the current config
	cfg, err := s.Config()
	if err != nil {
		return err
	}

	// 2. Update the Description
	cfg.Description = "A highly configurable, multi-protocol DNS forwarding proxy"

	// 3. Apply the updated config
	if err := s.UpdateConfig(cfg); err != nil {
		return err
	}

	// Then proceed with existing actions, e.g. setting failure actions
	actions := []mgr.RecoveryAction{
		{Type: mgr.ServiceRestart, Delay: time.Second * 5}, // 5 seconds
		{Type: mgr.ServiceRestart, Delay: time.Second * 5}, // 5 seconds
		{Type: mgr.ServiceRestart, Delay: time.Second * 5}, // 5 seconds
	}

	// Set the recovery actions (3 restarts, reset period = 120).
	err = s.SetRecoveryActions(actions, 120)
	if err != nil {
		return err
	}

	// Ensure that failure actions are NOT triggered on user-initiated stops.
	var failureActionsFlag windows.SERVICE_FAILURE_ACTIONS_FLAG
	failureActionsFlag.FailureActionsOnNonCrashFailures = 0

	if err := windows.ChangeServiceConfig2(
		s.Handle,
		windows.SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
		(*byte)(unsafe.Pointer(&failureActionsFlag)),
	); err != nil {
		return err
	}

	return nil
}

// openLogFile opens a log file with the specified mode on Windows
func openLogFile(path string, mode int) (*os.File, error) {
	if len(path) == 0 {
		return nil, &os.PathError{Path: path, Op: "open", Err: syscall.ERROR_FILE_NOT_FOUND}
	}

	pathP, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return nil, err
	}
	var access uint32
	switch mode & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
	case os.O_RDONLY:
		access = windows.GENERIC_READ
	case os.O_WRONLY:
		access = windows.GENERIC_WRITE
	case os.O_RDWR:
		access = windows.GENERIC_READ | windows.GENERIC_WRITE
	}
	if mode&os.O_CREATE != 0 {
		access |= windows.GENERIC_WRITE
	}
	if mode&os.O_APPEND != 0 {
		access &^= windows.GENERIC_WRITE
		access |= windows.FILE_APPEND_DATA
	}

	shareMode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)

	var sa *syscall.SecurityAttributes

	var createMode uint32
	switch {
	case mode&(os.O_CREATE|os.O_EXCL) == (os.O_CREATE | os.O_EXCL):
		createMode = windows.CREATE_NEW
	case mode&(os.O_CREATE|os.O_TRUNC) == (os.O_CREATE | os.O_TRUNC):
		createMode = windows.CREATE_ALWAYS
	case mode&os.O_CREATE == os.O_CREATE:
		createMode = windows.OPEN_ALWAYS
	case mode&os.O_TRUNC == os.O_TRUNC:
		createMode = windows.TRUNCATE_EXISTING
	default:
		createMode = windows.OPEN_EXISTING
	}

	handle, err := syscall.CreateFile(pathP, access, shareMode, sa, createMode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
	if err != nil {
		return nil, &os.PathError{Path: path, Op: "open", Err: err}
	}

	return os.NewFile(uintptr(handle), path), nil
}
