package server

import (
	"errors"
	"fmt"
	"net"
	"path"
	"strings"
)

// ErrNoTailscaleInterface is returned by FindTailscaleAddr when no interface
// matching the configured pattern is present on the host. The server
// translates this into a fatal startup error unless dev_mode is enabled.
var ErrNoTailscaleInterface = errors.New("server: no Tailscale interface found")

// FindTailscaleAddr enumerates network interfaces and returns the first IPv4
// address (or IPv6 if no IPv4 is present) belonging to an interface whose
// name matches any of the comma-separated glob patterns. Matching is
// case-insensitive.
//
// The returned address is suitable for use as the host portion of
// net.JoinHostPort during HTTP server startup.
func FindTailscaleAddr(patternList string) (string, error) {
	patterns := splitPatterns(patternList)
	if len(patterns) == 0 {
		return "", errors.New("server: empty tailscale interface pattern")
	}

	ifaces, err := net.Interfaces()
	if err != nil {
		return "", fmt.Errorf("server: list interfaces: %w", err)
	}

	var v6Fallback string
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue
		}
		if !matchesAny(iface.Name, patterns) {
			continue
		}
		addrs, err := iface.Addrs()
		if err != nil {
			continue
		}
		for _, a := range addrs {
			ip := ipOf(a)
			if ip == nil {
				continue
			}
			if v4 := ip.To4(); v4 != nil {
				return v4.String(), nil
			}
			if v6Fallback == "" {
				v6Fallback = ip.String()
			}
		}
	}
	if v6Fallback != "" {
		return v6Fallback, nil
	}
	return "", ErrNoTailscaleInterface
}

func splitPatterns(patternList string) []string {
	var out []string
	for _, p := range strings.Split(patternList, ",") {
		p = strings.TrimSpace(p)
		if p != "" {
			out = append(out, strings.ToLower(p))
		}
	}
	return out
}

func matchesAny(name string, patterns []string) bool {
	lower := strings.ToLower(name)
	for _, p := range patterns {
		ok, _ := path.Match(p, lower)
		if ok {
			return true
		}
	}
	return false
}

func ipOf(a net.Addr) net.IP {
	switch v := a.(type) {
	case *net.IPNet:
		return v.IP
	case *net.IPAddr:
		return v.IP
	default:
		return nil
	}
}
