package main

import (
	"net"
	"strings"
	"testing"
)

func TestResolveBindAddr_PassThrough(t *testing.T) {
	// Inputs that don't match any local interface name must be returned
	// unchanged: empty string, hostnames, IPv4/IPv6 literals, and bogus
	// strings the user might have typed.
	tests := []string{
		"",
		"localhost",
		"127.0.0.1",
		"192.0.2.5",
		"::1",
		"definitely-not-an-iface-xyz",
	}

	for _, input := range tests {
		t.Run(quoted(input), func(t *testing.T) {
			got, err := resolveBindAddr(input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if got != input {
				t.Errorf("got %q, want %q (input should pass through unchanged)", got, input)
			}
		})
	}
}

func TestResolveBindAddr_LoopbackInterface(t *testing.T) {
	loopback, expected, ok := findLoopbackWithSingleIPv4(t)
	if !ok {
		t.Skipf("no loopback interface with exactly one IPv4 address found")
	}

	got, err := resolveBindAddr(loopback)
	if err != nil {
		t.Fatalf("unexpected error resolving %q: %v", loopback, err)
	}

	if got != expected {
		t.Errorf("got %q, want %q for loopback interface %q", got, expected, loopback)
	}
}

// findLoopbackWithSingleIPv4 returns the name of a loopback interface and the
// single IPv4 address attached to it. If the host has multiple loopback
// interfaces or the loopback has zero or several IPv4 addresses, it returns
// ok=false so the caller can skip the test rather than fail on an environment
// quirk.
func findLoopbackWithSingleIPv4(t *testing.T) (name, addr string, ok bool) {
	t.Helper()

	ifaces, err := net.Interfaces()
	if err != nil {
		t.Fatalf("net.Interfaces: %v", err)
	}

	for _, iface := range ifaces {
		if iface.Flags&net.FlagLoopback == 0 {
			continue
		}

		addrs, addrErr := iface.Addrs()
		if addrErr != nil {
			continue
		}

		var ipv4s []string

		for _, a := range addrs {
			if ipnet, isIPNet := a.(*net.IPNet); isIPNet {
				if v4 := ipnet.IP.To4(); v4 != nil {
					ipv4s = append(ipv4s, v4.String())
				}
			}
		}

		if len(ipv4s) == 1 {
			return iface.Name, ipv4s[0], true
		}
	}

	return "", "", false
}

func TestDefaultDiscoveryInterface(t *testing.T) {
	tests := []struct {
		name         string
		rawInterface string
		rawBind      string
		resolvedBind string
		want         string
	}{
		{
			name:         "explicit interface wins over bind-derived default",
			rawInterface: "eth1",
			rawBind:      "eth0",
			resolvedBind: "192.0.2.5",
			want:         "eth1",
		},
		{
			name:         "derive from --bind when --bind was an interface name",
			rawInterface: "",
			rawBind:      "eth0",
			resolvedBind: "192.0.2.5",
			want:         "eth0",
		},
		{
			name:         "no derivation when --bind was an IP literal",
			rawInterface: "",
			rawBind:      "192.0.2.5",
			resolvedBind: "192.0.2.5",
			want:         "",
		},
		{
			name:         "no derivation when --bind was a hostname (pass-through)",
			rawInterface: "",
			rawBind:      "localhost",
			resolvedBind: "localhost",
			want:         "",
		},
		{
			name:         "both empty stays empty (auto-pick)",
			rawInterface: "",
			rawBind:      "",
			resolvedBind: "",
			want:         "",
		},
		{
			name:         "explicit interface alone, --bind empty",
			rawInterface: "eth1",
			rawBind:      "",
			resolvedBind: "",
			want:         "eth1",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := defaultDiscoveryInterface(tc.rawInterface, tc.rawBind, tc.resolvedBind)
			if got != tc.want {
				t.Errorf("got %q, want %q (rawInterface=%q rawBind=%q resolvedBind=%q)",
					got, tc.want, tc.rawInterface, tc.rawBind, tc.resolvedBind)
			}
		})
	}
}

func quoted(s string) string {
	if s == "" {
		return "(empty)"
	}

	return strings.ReplaceAll(s, "/", "_")
}
