package reconcile

import (
	"sort"
	"strings"
	"testing"
)

func TestNormDescription_TrimsTrailingNewlines(t *testing.T) {
	cases := map[string]string{
		"felhom-selftest 2026":   "felhom-selftest 2026",
		"felhom-selftest 2026\n": "felhom-selftest 2026", // PVE's single trailing \n
		"x\n\n":                  "x",                    // defensive: multiple
		"":                       "",
		"\n":                     "",
		"keep  trailing spaces ": "keep  trailing spaces ", // only newlines stripped
	}
	for in, want := range cases {
		if got := NormDescription(in); got != want {
			t.Errorf("NormDescription(%q) = %q, want %q", in, got, want)
		}
	}
	// Idempotent.
	if NormDescription(NormDescription("a\n")) != NormDescription("a\n") {
		t.Error("NormDescription not idempotent")
	}
}

func TestFieldNormalizers_DescriptionRoundTrip(t *testing.T) {
	n := DefaultNormalizers()
	// A value the agent wrote ("...Z") and read back with PVE's newline ("...Z\n")
	// must compare EQUAL — the whole point of the layer.
	if !n.Equal("description", "felhom-op", "felhom-op\n") {
		t.Error("description round-trip should normalize equal")
	}
	if n.Equal("description", "felhom-op", "different") {
		t.Error("genuinely different descriptions must not be equal")
	}
}

func TestFieldNormalizers_UnknownFieldIsIdentity(t *testing.T) {
	n := DefaultNormalizers()
	if n.Norm("cores", "2\n") != "2\n" {
		t.Error("a field with no normalizer must compare verbatim")
	}
	if n.Equal("cores", "2", "2\n") {
		t.Error("unknown field must not normalize away differences")
	}
}

// TestFieldNormalizers_ExtensibilitySeam proves the structure accepts new quirks
// (boolean coercion, list ordering) the way it will as they're discovered — the task's
// "structure it so other normalizers slot in." These are synthetic, not production.
func TestFieldNormalizers_ExtensibilitySeam(t *testing.T) {
	booleanCoerce := func(s string) string {
		switch strings.ToLower(strings.TrimSpace(s)) {
		case "1", "true", "on", "yes":
			return "1"
		default:
			return "0"
		}
	}
	sortCSV := func(s string) string {
		parts := strings.Split(s, ",")
		sort.Strings(parts)
		return strings.Join(parts, ",")
	}
	n := FieldNormalizers{
		"description": NormDescription,
		"onboot":      booleanCoerce,
		"tags":        sortCSV,
	}

	if !n.Equal("onboot", "true", "1") || !n.Equal("onboot", "on", "1") {
		t.Error("boolean coercion normalizer should equate truthy forms")
	}
	if n.Equal("onboot", "true", "0") {
		t.Error("boolean coercion must still distinguish true from false")
	}
	if !n.Equal("tags", "b,a,c", "a,b,c") {
		t.Error("list-ordering normalizer should equate reordered lists")
	}
	// The built-in still works alongside the synthetic ones.
	if !n.Equal("description", "d", "d\n") {
		t.Error("description normalizer should coexist with added ones")
	}
}
