package hub

import (
	"errors"
	"log/slog"
	"strings"
	"testing"
)

// The fixtures below are MEASURED, not invented: `ip -o addr show` on demo-felhom (N100) and
// demo-hp (HP t740) on 2026-07-31, transcribed verbatim including the interfaces that carry no
// address. That matters — the filter's claim that it needs no veth/fwbr denylist rests on those
// interfaces genuinely having nothing to report, and a hand-written fixture that omitted them would
// have proved the claim by assuming it.

// demoFelhomIfaces is demo-felhom's real interface table.
func demoFelhomIfaces() []ifaceAddrs {
	return []ifaceAddrs{
		{Name: "lo", Up: true, Loopback: true, CIDRs: []string{"127.0.0.1/8", "::1/128"}},
		{Name: "enp1s0", Up: false}, // physical NIC, no address
		{Name: "wlp2s0", Up: false}, // wifi, no address
		{Name: "tailscale0", Up: true, CIDRs: []string{
			"100.70.170.35/32", "fd7a:115c:a1e0::5236:aa24/128", "fe80::4197:26fc:ccba:b0d9/64"}},
		{Name: "vmbr0", Up: true, CIDRs: []string{"192.168.0.162/24", "fe80::6a1d:efff:fe5d:a664/64"}},
		{Name: "veth9201i0", Up: true}, // per-guest plumbing — no address
		{Name: "veth9201i1", Up: true}, // per-guest plumbing — no address
		{Name: "vmbr9", Up: true, CIDRs: []string{"169.254.253.1/30", "fe80::48d4:f6ff:fe05:2f98/64"}},
		{Name: "wg-felhom", Up: true, CIDRs: []string{"10.77.0.2/32"}},
	}
}

func hasAddr(got []HostAddress, iface, cidr string) bool {
	for _, a := range got {
		if a.Iface == iface && a.CIDR == cidr {
			return true
		}
	}
	return false
}

func flatten(got []HostAddress) string {
	var b strings.Builder
	for _, a := range got {
		b.WriteString(a.Iface + "=" + a.CIDR + " ")
	}
	return b.String()
}

// The LAN address is the whole point of the feature — it must survive the filter.
// RED-PROOF 1: drop the `!p.Addr().IsGlobalUnicast()` continue → the vmbr9 + fe80 assertions below
// go red (the LAN one still passes, which is exactly why the negatives are asserted too).
func TestFilterHostAddresses_RealHost(t *testing.T) {
	got := filterHostAddresses(demoFelhomIfaces())

	// --- what MUST be there ---
	if !hasAddr(got, "vmbr0", "192.168.0.162/24") {
		t.Fatalf("the LAN address was filtered away — the feature reports nothing: %s", flatten(got))
	}
	if !hasAddr(got, "wg-felhom", "10.77.0.2/32") {
		t.Errorf("the WireGuard address was filtered away: %s", flatten(got))
	}
	if !hasAddr(got, "tailscale0", "100.70.170.35/32") {
		t.Errorf("the tailnet address was filtered away: %s", flatten(got))
	}

	// --- what MUST NOT be there, each for its own reason ---
	for _, bad := range []struct{ iface, cidr, why string }{
		{"lo", "127.0.0.1/8", "loopback is not an address of the host on any network"},
		{"lo", "::1/128", "IPv6 loopback"},
		{"vmbr9", "169.254.253.1/30", "the R-50 island literal — IDENTICAL on every box, so surfacing it is actively misleading"},
		{"vmbr0", "fe80::6a1d:efff:fe5d:a664/64", "IPv6 link-local, one per bridge, pure noise"},
		{"tailscale0", "fe80::4197:26fc:ccba:b0d9/64", "IPv6 link-local"},
	} {
		if hasAddr(got, bad.iface, bad.cidr) {
			t.Errorf("%s %s must be filtered (%s); got: %s", bad.iface, bad.cidr, bad.why, flatten(got))
		}
	}

	// The no-denylist claim: not one veth/physical interface contributed a row.
	for _, a := range got {
		if strings.HasPrefix(a.Iface, "veth") || a.Iface == "enp1s0" || a.Iface == "wlp2s0" {
			t.Errorf("%s produced a row — the fixture says it has no address, so the filter invented one", a.Iface)
		}
	}
}

// demo-hp is different hardware (4 unused NICs, different ordering) and must filter identically —
// the rule is about address CLASS, not about one box's interface names.
func TestFilterHostAddresses_SecondHostFiltersIdentically(t *testing.T) {
	got := filterHostAddresses([]ifaceAddrs{
		{Name: "lo", Up: true, Loopback: true, CIDRs: []string{"127.0.0.1/8", "::1/128"}},
		{Name: "enp2s0f0", Up: false}, {Name: "enp1s0f0", Up: false},
		{Name: "enp1s0f1", Up: false}, {Name: "enp1s0f2", Up: false},
		{Name: "enp1s0f3", Up: false}, {Name: "wlo1", Up: false},
		{Name: "tailscale0", Up: true, CIDRs: []string{
			"100.76.96.79/32", "fd7a:115c:a1e0::ce36:6051/128", "fe80::e06a:ce64:80e1:7821/64"}},
		{Name: "vmbr0", Up: true, CIDRs: []string{"192.168.0.87/24", "fe80::7ed3:aff:fe77:d976/64"}},
		{Name: "wg-felhom", Up: true, CIDRs: []string{"10.77.0.3/32"}},
		{Name: "vmbr9", Up: true, CIDRs: []string{"169.254.253.1/30", "fe80::2484:92ff:fe7d:52a5/64"}},
		{Name: "veth9201i0", Up: true}, {Name: "veth9201i1", Up: true},
	})
	if !hasAddr(got, "vmbr0", "192.168.0.87/24") {
		t.Fatalf("demo-hp's LAN address was filtered away: %s", flatten(got))
	}
	if hasAddr(got, "vmbr9", "169.254.253.1/30") {
		t.Errorf("demo-hp's island address leaked through: %s", flatten(got))
	}
	// The island address is byte-identical on both boxes — the strongest argument for excluding it.
	if strings.Contains(flatten(got), "169.254.") {
		t.Errorf("a link-local IPv4 survived: %s", flatten(got))
	}
}

// A DOWN interface holding a stale address must not be reported as if the box were reachable there.
// RED-PROOF 2: drop `|| !i.Up` → this goes red.
func TestFilterHostAddresses_DownInterfaceExcluded(t *testing.T) {
	got := filterHostAddresses([]ifaceAddrs{
		{Name: "vmbr0", Up: true, CIDRs: []string{"192.168.0.162/24"}},
		{Name: "vmbr1", Up: false, CIDRs: []string{"10.9.9.9/24"}},
	})
	if hasAddr(got, "vmbr1", "10.9.9.9/24") {
		t.Errorf("a DOWN interface's address was reported: %s", flatten(got))
	}
	if len(got) != 1 {
		t.Errorf("want exactly the one up interface, got: %s", flatten(got))
	}
}

// Order must be deterministic, or every report diff shows phantom churn.
func TestFilterHostAddresses_DeterministicOrder(t *testing.T) {
	a := filterHostAddresses(demoFelhomIfaces())
	// Same facts, opposite enumeration order.
	rev := demoFelhomIfaces()
	for i, j := 0, len(rev)-1; i < j; i, j = i+1, j-1 {
		rev[i], rev[j] = rev[j], rev[i]
	}
	b := filterHostAddresses(rev)
	if flatten(a) != flatten(b) {
		t.Errorf("interface-table order changed the report:\n a=%s\n b=%s", flatten(a), flatten(b))
	}
}

// A host with nothing routable yields [] and never nil — an absent key and "no addresses" must not
// look alike on the wire.
func TestFilterHostAddresses_EmptyIsNonNil(t *testing.T) {
	got := filterHostAddresses([]ifaceAddrs{{Name: "lo", Up: true, Loopback: true, CIDRs: []string{"127.0.0.1/8"}}})
	if got == nil {
		t.Fatal("filter returned nil — it would marshal as null, not []")
	}
	if len(got) != 0 {
		t.Errorf("want no addresses, got %s", flatten(got))
	}
}

// A malformed entry is skipped, never fatal — one bad address must not cost the report the others.
func TestFilterHostAddresses_MalformedSkipped(t *testing.T) {
	got := filterHostAddresses([]ifaceAddrs{
		{Name: "vmbr0", Up: true, CIDRs: []string{"not-an-address", "192.168.0.162/24"}},
	})
	if len(got) != 1 || !hasAddr(got, "vmbr0", "192.168.0.162/24") {
		t.Errorf("a malformed sibling address broke the good one: %s", flatten(got))
	}
}

// --- the WIRING half: the collector must actually call the filter ---

// The seam defaults to the REAL enumerator, so a forgotten wiring call cannot make this inert.
// RED-PROOF 3: replace the collectAddresses body with `return []HostAddress{}` → red.
func TestCollectAddresses_UsesTheInjectedEnumerator(t *testing.T) {
	c := &Collector{logger: slog.Default()}
	c.addrEnum = func() ([]ifaceAddrs, error) { return demoFelhomIfaces(), nil }
	got := c.collectAddresses()
	if !hasAddr(got, "vmbr0", "192.168.0.162/24") {
		t.Fatalf("the collector did not run the filter over the enumerator's output: %s", flatten(got))
	}
}

// An enumeration failure degrades to [] and a WARN — never a failed report.
func TestCollectAddresses_EnumerationErrorDegrades(t *testing.T) {
	c := &Collector{logger: slog.Default()}
	c.addrEnum = func() ([]ifaceAddrs, error) { return nil, errors.New("netlink is unhappy") }
	got := c.collectAddresses()
	if got == nil {
		t.Fatal("an enumeration error produced nil, which marshals as null")
	}
	if len(got) != 0 {
		t.Errorf("want [] on error, got %s", flatten(got))
	}
}

// The production enumerator must return SOMETHING on the machine running the tests, and must not
// panic. This is the only test that touches the real network stack; it asserts the contract
// (non-nil, no error, loopback correctly flagged) rather than any specific address, because the
// test host's addresses are not ours to predict.
func TestSystemInterfaces_ProductionEnumeratorWorks(t *testing.T) {
	ifaces, err := systemInterfaces()
	if err != nil {
		t.Fatalf("systemInterfaces: %v", err)
	}
	if len(ifaces) == 0 {
		t.Fatal("no interfaces at all — even a container has lo")
	}
	var sawLoopback bool
	for _, i := range ifaces {
		if i.Loopback {
			sawLoopback = true
		}
	}
	if !sawLoopback {
		t.Error("no interface reported the loopback flag — the flag mapping is wrong")
	}
	// And the filter must survive real input without panicking.
	_ = filterHostAddresses(ifaces)
}
