//go:build windows

package sysproxy

import (
	"fmt"
	"os/exec"
	"strings"
	"syscall"

	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/registry"
)

const (
	internetOptionSettingsChanged = 39
	internetOptionRefresh         = 37
)

type windowsController struct {
	enabled     bool
	hadProxy    bool
	oldEnable   uint64
	oldServer   string
	oldOverride string
}

func newPlatform() Controller {
	return &windowsController{}
}

func (c *windowsController) Enabled() bool { return c.enabled }

func (c *windowsController) Enable(httpHostPort string) error {
	if httpHostPort == "" {
		return fmt.Errorf("sysproxy: empty http proxy address")
	}
	key, err := registry.OpenKey(registry.CURRENT_USER,
		`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
		registry.QUERY_VALUE|registry.SET_VALUE)
	if err != nil {
		return fmt.Errorf("sysproxy: open registry: %w", err)
	}
	defer key.Close()

	if !c.enabled {
		c.oldEnable, _, _ = key.GetIntegerValue("ProxyEnable")
		c.oldServer, _, _ = key.GetStringValue("ProxyServer")
		c.oldOverride, _, _ = key.GetStringValue("ProxyOverride")
		c.hadProxy = true
	}

	override := strings.Join([]string{
		"localhost",
		"127.*",
		"10.*",
		"172.16.*",
		"172.17.*",
		"172.18.*",
		"172.19.*",
		"172.20.*",
		"172.21.*",
		"172.22.*",
		"172.23.*",
		"172.24.*",
		"172.25.*",
		"172.26.*",
		"172.27.*",
		"172.28.*",
		"172.29.*",
		"172.30.*",
		"172.31.*",
		"192.168.*",
		"<local>",
	}, ";")

	if err := key.SetDWordValue("ProxyEnable", 1); err != nil {
		return err
	}
	if err := key.SetStringValue("ProxyServer", httpHostPort); err != nil {
		return err
	}
	if err := key.SetStringValue("ProxyOverride", override); err != nil {
		return err
	}

	if err := notifyInternetSettingsChanged(); err != nil {
		return err
	}
	_ = setWinHTTPProxy(httpHostPort)

	c.enabled = true
	return nil
}

func (c *windowsController) Disable() error {
	if !c.enabled {
		return nil
	}
	key, err := registry.OpenKey(registry.CURRENT_USER,
		`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
		registry.QUERY_VALUE|registry.SET_VALUE)
	if err != nil {
		return err
	}
	defer key.Close()

	if c.hadProxy {
		_ = key.SetDWordValue("ProxyEnable", uint32(c.oldEnable))
		if c.oldServer != "" {
			_ = key.SetStringValue("ProxyServer", c.oldServer)
		} else {
			_ = key.DeleteValue("ProxyServer")
		}
		if c.oldOverride != "" {
			_ = key.SetStringValue("ProxyOverride", c.oldOverride)
		}
	} else {
		_ = key.SetDWordValue("ProxyEnable", 0)
	}

	_ = notifyInternetSettingsChanged()
	_ = resetWinHTTPProxy()

	c.enabled = false
	return nil
}

func notifyInternetSettingsChanged() error {
	wininet := windows.NewLazySystemDLL("wininet.dll")
	proc := wininet.NewProc("InternetSetOptionW")
	r1, _, err := proc.Call(0, uintptr(internetOptionSettingsChanged), 0, 0)
	if r1 == 0 {
		return fmt.Errorf("InternetSetOption(SETTINGS_CHANGED): %w", err)
	}
	r1, _, err = proc.Call(0, uintptr(internetOptionRefresh), 0, 0)
	if r1 == 0 {
		return fmt.Errorf("InternetSetOption(REFRESH): %w", err)
	}
	return nil
}

func setWinHTTPProxy(httpHostPort string) error {
	cmd := exec.Command("netsh", "winhttp", "set", "proxy", httpHostPort,
		"bypass-list=localhost;127.*;10.*;192.168.*;<local>")
	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
	return cmd.Run()
}

func resetWinHTTPProxy() error {
	cmd := exec.Command("netsh", "winhttp", "reset", "proxy")
	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
	return cmd.Run()
}
