//go:build darwin

package diag

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

func isPrivileged() bool { return os.Geteuid() == 0 }

func collectPlatform(r *Report) {
	r.System.OSVersion = macOSVersion()
	r.System.KernelVersion = commandOutput("uname", "-r")

	collectMacDevices(r)
	assessMacCapabilities(r)
}

func macOSVersion() string {
	name := commandOutput("sw_vers", "-productName")
	version := commandOutput("sw_vers", "-productVersion")
	build := commandOutput("sw_vers", "-buildVersion")

	parts := []string{}
	for _, p := range []string{name, version, build} {
		if p != "" {
			parts = append(parts, p)
		}
	}
	return strings.Join(parts, " ")
}

func commandOutput(name string, args ...string) string {
	out, err := exec.Command(name, args...).Output()
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(out))
}

// system_profiler's JSON output, as much of it as we care about.
type spReport struct {
	Items []spUSBItem `json:"SPUSBDataType"`
}

type spUSBItem struct {
	Name         string      `json:"_name"`
	VendorID     string      `json:"vendor_id"`
	ProductID    string      `json:"product_id"`
	Speed        string      `json:"device_speed"`
	Manufacturer string      `json:"manufacturer"`
	SerialNumber string      `json:"serial_num"`
	LocationID   string      `json:"location_id"`
	Media        []spMedia   `json:"Media"`
	Items        []spUSBItem `json:"_items"`
}

type spMedia struct {
	Name string `json:"_name"`
}

// collectMacDevices lists USB devices via system_profiler.
//
// Going through the command rather than IOKit keeps this cgo-free, which is
// what lets the tool be cross-compiled from any machine. It is also enough:
// this reports what is present, and on macOS nothing can be shared regardless
// until there is an IOKit backend.
func collectMacDevices(r *Report) {
	out, err := exec.Command("system_profiler", "-json", "SPUSBDataType").Output()
	if err != nil {
		r.note("system_profiler failed: %v", err)
		return
	}

	var report spReport
	if err := json.Unmarshal(out, &report); err != nil {
		r.note("could not parse system_profiler output: %v", err)
		return
	}

	for _, item := range report.Items {
		collectMacItem(r, item)
	}
}

// collectMacItem walks the tree; hubs carry their devices in _items.
func collectMacItem(r *Report, item spUSBItem) {
	if item.VendorID != "" {
		r.Devices = append(r.Devices, DeviceInfo{
			BusID:     macBusID(item.LocationID),
			VendorID:  normaliseMacID(item.VendorID),
			ProductID: normaliseMacID(item.ProductID),
			Name:      macDeviceName(item),
			Speed:     item.Speed,
			Shareable: false,
			Blocker:   "macOS sharing needs an IOKit backend, which does not exist yet",
		})
	}

	for _, child := range item.Items {
		collectMacItem(r, child)
	}
}

func macDeviceName(item spUSBItem) string {
	if item.Manufacturer != "" && item.Name != "" &&
		!strings.HasPrefix(item.Name, item.Manufacturer) {
		return item.Manufacturer + " " + item.Name
	}
	return item.Name
}

// normaliseMacID turns "0x046d  (Logitech Inc.)" into "046d".
func normaliseMacID(id string) string {
	id = strings.TrimSpace(id)
	if i := strings.Index(id, " "); i > 0 {
		id = id[:i]
	}
	id = strings.TrimPrefix(id, "0x")

	// Pad to four digits so IDs sort and compare like everywhere else.
	if v, err := strconv.ParseUint(id, 16, 32); err == nil {
		return fmt.Sprintf("%04x", v)
	}
	return id
}

// macBusID derives an identifier from the location ID, which encodes the
// device's position in the port tree and is stable while it stays plugged in.
func macBusID(locationID string) string {
	locationID = strings.TrimSpace(locationID)
	if i := strings.Index(locationID, " "); i > 0 {
		locationID = locationID[:i]
	}
	return strings.TrimPrefix(locationID, "0x")
}

func assessMacCapabilities(r *Report) {
	r.Sharing = Capability{
		Available: false,
		Reason:    "no IOKit backend — macOS has no usbdevfs equivalent",
		Mechanism: "IOKit (not implemented)",
	}

	r.Using = Capability{
		Available: false,
		Reason:    "no virtual USB host controller — this needs a signed DriverKit driver",
		Mechanism: "DriverKit (not implemented)",
	}

	r.addCheck("macOS sharing", false,
		fmt.Sprintf("%d USB device(s) found, but none can be shared yet", len(r.Devices)),
		"none — the relay server runs on macOS, the client's USB side does not")

	r.note("Docker does not help here: containers share the host kernel, and " +
		"on macOS Docker runs in a Linux VM that never sees the host's USB hardware. " +
		"A full VM with USB passthrough (UTM, Parallels, VMware) does work.")
}
