package daemon

import (
	"slices"
	"testing"

	"banger/internal/model"
)

func TestParseDefaultUplink(t *testing.T) {
	t.Parallel()

	output := "default via 192.168.1.1 dev enp5s0 proto dhcp src 192.168.1.40 metric 100\n"
	uplink, err := parseDefaultUplink(output)
	if err != nil {
		t.Fatalf("parseDefaultUplink returned error: %v", err)
	}
	if uplink != "enp5s0" {
		t.Fatalf("uplink = %q, want enp5s0", uplink)
	}
}

func TestParseDefaultUplinkFailsWithoutRoute(t *testing.T) {
	t.Parallel()

	if _, err := parseDefaultUplink("10.0.0.0/24 dev br-fc proto kernel scope link src 10.0.0.1\n"); err == nil {
		t.Fatal("expected parseDefaultUplink to fail without a default route")
	}
}

func TestNATRulesForVM(t *testing.T) {
	t.Parallel()

	vm := model.VMRecord{
		Runtime: model.VMRuntime{
			GuestIP: "172.16.0.8",
		},
	}
	rules, err := natRulesForVM(vm, "tap-fc-abcd1234", "wlan0")
	if err != nil {
		t.Fatalf("natRulesForVM returned error: %v", err)
	}
	if len(rules) != 3 {
		t.Fatalf("rule count = %d, want 3", len(rules))
	}
	if got, want := natRuleArgs("-A", rules[0]), []string{"-t", "nat", "-A", "POSTROUTING", "-s", "172.16.0.8/32", "-o", "wlan0", "-j", "MASQUERADE"}; !slices.Equal(got, want) {
		t.Fatalf("postrouting args = %v, want %v", got, want)
	}
	if got, want := natRuleArgs("-A", rules[1]), []string{"-A", "FORWARD", "-i", "tap-fc-abcd1234", "-o", "wlan0", "-j", "ACCEPT"}; !slices.Equal(got, want) {
		t.Fatalf("forward-out args = %v, want %v", got, want)
	}
	if got, want := natRuleArgs("-A", rules[2]), []string{"-A", "FORWARD", "-i", "wlan0", "-o", "tap-fc-abcd1234", "-m", "state", "--state", "RELATED,ESTABLISHED", "-j", "ACCEPT"}; !slices.Equal(got, want) {
		t.Fatalf("forward-in args = %v, want %v", got, want)
	}
}

func TestNATRulesForVMRequiresRuntimeData(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name   string
		vm     model.VMRecord
		tap    string
		uplink string
	}{
		{
			name:   "guest ip",
			vm:     model.VMRecord{},
			tap:    "tap-fc-abcd1234",
			uplink: "eth0",
		},
		{
			name:   "tap",
			vm:     model.VMRecord{Runtime: model.VMRuntime{GuestIP: "172.16.0.8"}},
			tap:    "",
			uplink: "eth0",
		},
		{
			name:   "uplink",
			vm:     model.VMRecord{Runtime: model.VMRuntime{GuestIP: "172.16.0.8"}},
			tap:    "tap-fc-abcd1234",
			uplink: "",
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			if _, err := natRulesForVM(tt.vm, tt.tap, tt.uplink); err == nil {
				t.Fatalf("expected natRulesForVM to fail for missing %s", tt.name)
			}
		})
	}
}

func TestNATPlans(t *testing.T) {
	t.Parallel()

	rules := []natRule{
		{Table: "nat", Chain: "POSTROUTING", Args: []string{"-s", "172.16.0.8/32", "-o", "eth0", "-j", "MASQUERADE"}},
		{Chain: "FORWARD", Args: []string{"-i", "tap-fc-abcd1234", "-o", "eth0", "-j", "ACCEPT"}},
	}

	addPlan := natAddPlan(rules)
	if len(addPlan) != 3 {
		t.Fatalf("addPlan count = %d, want 3", len(addPlan))
	}
	if got, want := addPlan[0], []string{"sysctl", "-w", "net.ipv4.ip_forward=1"}; !slices.Equal(got, want) {
		t.Fatalf("sysctl command = %v, want %v", got, want)
	}
	if got, want := addPlan[1], []string{"-t", "nat", "-A", "POSTROUTING", "-s", "172.16.0.8/32", "-o", "eth0", "-j", "MASQUERADE"}; !slices.Equal(got, want) {
		t.Fatalf("add NAT command = %v, want %v", got, want)
	}

	removePlan := natRemovePlan(rules)
	if len(removePlan) != 2 {
		t.Fatalf("removePlan count = %d, want 2", len(removePlan))
	}
	if got, want := removePlan[0], []string{"-t", "nat", "-D", "POSTROUTING", "-s", "172.16.0.8/32", "-o", "eth0", "-j", "MASQUERADE"}; !slices.Equal(got, want) {
		t.Fatalf("remove NAT command = %v, want %v", got, want)
	}
}
