package libbox

import (
	"net/netip"
	"os/user"
	"syscall"

	"github.com/sagernet/sing-box/common/process"
	E "github.com/sagernet/sing/common/exceptions"
	F "github.com/sagernet/sing/common/format"
)

func FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (*ConnectionOwner, error) {
	source, err := parseConnectionOwnerAddrPort(sourceAddress, sourcePort)
	if err != nil {
		return nil, E.Cause(err, "parse source")
	}
	destination, err := parseConnectionOwnerAddrPort(destinationAddress, destinationPort)
	if err != nil {
		return nil, E.Cause(err, "parse destination")
	}
	var network string
	switch ipProtocol {
	case syscall.IPPROTO_TCP:
		network = "tcp"
	case syscall.IPPROTO_UDP:
		network = "udp"
	default:
		return nil, E.New("unknown protocol: ", ipProtocol)
	}
	owner, err := process.FindDarwinConnectionOwner(network, source, destination)
	if err != nil {
		return nil, err
	}
	result := &ConnectionOwner{
		UserId:      owner.UserId,
		ProcessPath: owner.ProcessPath,
	}
	if owner.UserId != -1 && owner.UserName == "" {
		osUser, _ := user.LookupId(F.ToString(owner.UserId))
		if osUser != nil {
			result.UserName = osUser.Username
		}
	}
	return result, nil
}

func parseConnectionOwnerAddrPort(address string, port int32) (netip.AddrPort, error) {
	if port < 0 || port > 65535 {
		return netip.AddrPort{}, E.New("invalid port: ", port)
	}
	addr, err := netip.ParseAddr(address)
	if err != nil {
		return netip.AddrPort{}, err
	}
	return netip.AddrPortFrom(addr.Unmap(), uint16(port)), nil
}
