package emitter

import (
	"strings"
	"testing"
)

func TestNftEmitter_RenderBasic(t *testing.T) {
	files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{})
	if err != nil {
		t.Fatalf("RenderNftConfig: %v", err)
	}
	if len(files) != 1 {
		t.Fatalf("got %d files, want 1", len(files))
	}
	f := files[0]
	if f.Path != "/etc/nftables.d/orca.nft" {
		t.Errorf("Path = %q, want /etc/nftables.d/orca.nft", f.Path)
	}
	if f.Mode != "0644" {
		t.Errorf("Mode = %q, want 0644", f.Mode)
	}
	c := f.Content
	for _, want := range []string{
		"#!/usr/sbin/nft -f",
		"table inet orca-ingress",
		"set orca_trusted_probes",
		"type ipv4_addr",
		"flags interval",
		"127.0.0.1",
		"::1",
		"chain input",
		"tcp dport 443 tcp-flags != syn,rst,ack,fin notrack drop",
		"chain prerouting",
		"tcp dport 443 dnat to 127.0.0.1:8443",
		"tcp dport 80 dnat to 127.0.0.1:8080",
		"chain forward",
		"rate 100/second burst 200 packets",
		"ora_rl",
	} {
		if !strings.Contains(c, want) {
			t.Errorf("content missing %q:\n%s", want, c)
		}
	}
}

func TestNftEmitter_DefaultsApplied(t *testing.T) {
	files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{RateLimit: 0, RateBurst: 0})
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if !strings.Contains(files[0].Content, "rate 100/second burst 200 packets") {
		t.Errorf("defaults not applied:\n%s", files[0].Content)
	}
}

func TestNftEmitter_CustomRate(t *testing.T) {
	files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{RateLimit: 50, RateBurst: 150})
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	if !strings.Contains(files[0].Content, "rate 50/second burst 150 packets") {
		t.Errorf("custom rate not rendered:\n%s", files[0].Content)
	}
}

func TestNftEmitter_CustomTrustedProbes(t *testing.T) {
	files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{TrustedProbes: []string{"10.0.0.5", "192.168.1.1"}})
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	c := files[0].Content
	if !strings.Contains(c, "10.0.0.5, 192.168.1.1") {
		t.Errorf("custom probes not rendered:\n%s", c)
	}
	if strings.Contains(c, "::1") {
		t.Errorf("default ::1 should not be present when custom probes set:\n%s", c)
	}
}

func TestNftEmitter_NegativeRateRejected(t *testing.T) {
	if _, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{RateLimit: -1}); err == nil {
		t.Fatal("expected error for negative rate, got nil")
	}
}

func TestNftEmitter_ShebangFirst(t *testing.T) {
	files, _ := (NftEmitter{}).RenderNftConfig(NftClusterConfig{})
	if !strings.HasPrefix(files[0].Content, "#!/usr/sbin/nft -f\n") {
		t.Errorf("shebang not first:\n%s", files[0].Content[:40])
	}
}

// TestNftEmitter_RejectsInvalidTrustedProbe verifies that a TrustedProbes
// entry that is not a valid IP or CIDR is rejected (F9: ruleset injection
// guard). An unvalidated entry written raw into the ruleset could inject
// arbitrary nft syntax.
func TestNftEmitter_RejectsInvalidTrustedProbe(t *testing.T) {
	bad := []string{
		"not-an-ip",
		"127.0.0.1; flush ruleset",
		"$(whoami)",
		"10.0.0.0/33", // invalid CIDR prefix
	}
	for _, b := range bad {
		_, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{TrustedProbes: []string{"127.0.0.1", b}})
		if err == nil {
			t.Errorf("expected error for invalid trusted probe %q, got nil", b)
		}
	}
}

// TestNftEmitter_TrustedProbesSplitV4V6 verifies that IPv4 and IPv6
// probes are rendered into separate typed sets (F9: the prior single
// ipv4_addr set included ::1, an IPv6 address — a type mismatch).
func TestNftEmitter_TrustedProbesSplitV4V6(t *testing.T) {
	files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{TrustedProbes: []string{"10.0.0.5", "::1"}})
	if err != nil {
		t.Fatalf("Render: %v", err)
	}
	c := files[0].Content
	if !strings.Contains(c, "set orca_trusted_probes_v4") {
		t.Errorf("missing v4 set:\n%s", c)
	}
	if !strings.Contains(c, "set orca_trusted_probes_v6") {
		t.Errorf("missing v6 set:\n%s", c)
	}
	if !strings.Contains(c, "type ipv6_addr") {
		t.Errorf("missing ipv6_addr type:\n%s", c)
	}
	if !strings.Contains(c, "10.0.0.5") {
		t.Errorf("missing 10.0.0.5 in v4 set:\n%s", c)
	}
	if !strings.Contains(c, "::1") {
		t.Errorf("missing ::1 in v6 set:\n%s", c)
	}
}
