//go:build darwin

package sysproxy

import (
	"bufio"
	"bytes"
	"fmt"
	"net"
	"os/exec"
	"strings"
	"sync"
)

type darwinController struct {
	mu      sync.Mutex
	enabled bool
	host    string
	httpPort string
	socksPort string
	services []string
}

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

func (c *darwinController) Enable(httpHostPort string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	host, port, err := net.SplitHostPort(httpHostPort)
	if err != nil {
		return fmt.Errorf("sysproxy: bad address %q: %w", httpHostPort, err)
	}
	if host == "" || host == "0.0.0.0" {
		host = "127.0.0.1"
	}
	socks := "1080"
	if port == "1081" {
		socks = "1080"
	}

	services, err := listNetworkServices()
	if err != nil {
		return err
	}
	if len(services) == 0 {
		return fmt.Errorf("sysproxy: no network services")
	}

	for _, svc := range services {
		if err := runNetworkSetup("-setwebproxy", svc, host, port); err != nil {
			return err
		}
		if err := runNetworkSetup("-setsecurewebproxy", svc, host, port); err != nil {
			return err
		}
		if err := runNetworkSetup("-setsocksfirewallproxy", svc, host, socks); err != nil {
			return err
		}
		if err := runNetworkSetup("-setwebproxystate", svc, "on"); err != nil {
			return err
		}
		if err := runNetworkSetup("-setsecurewebproxystate", svc, "on"); err != nil {
			return err
		}
		if err := runNetworkSetup("-setsocksfirewallproxystate", svc, "on"); err != nil {
			return err
		}
	}

	c.enabled = true
	c.host = host
	c.httpPort = port
	c.socksPort = socks
	c.services = services
	return nil
}

func (c *darwinController) Disable() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	if !c.enabled {
		return nil
	}
	services := c.services
	if len(services) == 0 {
		var err error
		services, err = listNetworkServices()
		if err != nil {
			return err
		}
	}
	var first error
	for _, svc := range services {
		for _, args := range [][]string{
			{"-setwebproxystate", svc, "off"},
			{"-setsecurewebproxystate", svc, "off"},
			{"-setsocksfirewallproxystate", svc, "off"},
		} {
			if err := runNetworkSetup(args...); err != nil && first == nil {
				first = err
			}
		}
	}
	c.enabled = false
	c.services = nil
	return first
}

func (c *darwinController) Enabled() bool {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.enabled
}

func listNetworkServices() ([]string, error) {
	out, err := exec.Command("networksetup", "-listallnetworkservices").Output()
	if err != nil {
		return nil, fmt.Errorf("networksetup: %w", err)
	}
	var services []string
	sc := bufio.NewScanner(bytes.NewReader(out))
	for sc.Scan() {
		line := strings.TrimSpace(sc.Text())
		if line == "" || strings.HasPrefix(line, "An asterisk") {
			continue
		}
		if strings.HasPrefix(line, "*") {
			continue // disabled service
		}
		services = append(services, line)
	}
	return services, sc.Err()
}

func runNetworkSetup(args ...string) error {
	cmd := exec.Command("networksetup", args...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("networksetup %v: %w (%s)", args, err, strings.TrimSpace(string(out)))
	}
	return nil
}
