package ns

import (
	"strings"
	"testing"
)

func TestResolveEmptyConfigs(t *testing.T) {
	out, err := Resolve(map[string]*NSConfig{})
	if err != nil {
		t.Fatalf("Resolve empty: unexpected error: %v", err)
	}
	if len(out) != 0 {
		t.Fatalf("Resolve empty: want empty map, got %d entries", len(out))
	}
}

func TestResolveSingleNoParents(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName, Env: map[string]string{"A": "1"}},
		"x":          {Name: "x", Env: map[string]string{"B": "2"}},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	r := out["x"]
	if r == nil {
		t.Fatal("missing resolved x")
	}
	if !eqSlice(r.Chain, []string{"x", defaultsName}) {
		t.Errorf("chain = %v, want [x _defaults]", r.Chain)
	}
	if r.Env["A"] != "1" || r.Env["B"] != "2" {
		t.Errorf("env = %v, want A=1 B=2", r.Env)
	}
}

func TestResolveChildOverridesParentScalar(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName, Env: map[string]string{"K": "parent"}},
		"child":      {Name: "child", Parents: []string{defaultsName}, Env: map[string]string{"K": "child"}},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	if got := out["child"].Env["K"]; got != "child" {
		t.Errorf("child K = %q, want %q (child overrides parent)", got, "child")
	}
}

func TestResolveArraysUnion(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName, Constraints: []string{"a", "b"}},
		"x":          {Name: "x", Parents: []string{defaultsName}, Constraints: []string{"c", "a"}},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	c := out["x"].Constraints
	// Union de-duped; most-specific (x) first.
	if !eqSlice(c, []string{"c", "a", "b"}) {
		t.Errorf("constraints = %v, want [c a b]", c)
	}
}

func TestResolveDefaultsImplicitLast(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName},
		"mid":        {Name: "mid", Parents: []string{defaultsName}},
		"top":        {Name: "top", Parents: []string{"mid"}},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	if !eqSlice(out["top"].Chain, []string{"top", "mid", defaultsName}) {
		t.Errorf("top chain = %v, want [top mid _defaults]", out["top"].Chain)
	}
	if !eqSlice(out["mid"].Chain, []string{"mid", defaultsName}) {
		t.Errorf("mid chain = %v, want [mid _defaults]", out["mid"].Chain)
	}
}

func TestResolveDefaultsDedupExplicit(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName, Env: map[string]string{"D": "1"}},
		"x":          {Name: "x", Parents: []string{defaultsName}},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	// _defaults appears exactly once.
	count := 0
	for _, c := range out["x"].Chain {
		if c == defaultsName {
			count++
		}
	}
	if count != 1 {
		t.Errorf("_defaults appears %d times in chain %v, want 1", count, out["x"].Chain)
	}
}

func TestResolveMisorderingRejected(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName},
		"x":          {Name: "x"},
		"y":          {Name: "y", Parents: []string{defaultsName, "x"}},
	}
	_, err := Resolve(cfgs)
	if err == nil {
		t.Fatal("expected misordering error, got nil")
	}
	if !strings.Contains(err.Error(), "must be the only parent") {
		t.Errorf("error = %q, want misordering message", err.Error())
	}
}

func TestResolveOptOutImpossible(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName, Env: map[string]string{"ROOT": "1"}},
		"x":          {Name: "x", Parents: nil},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	r := out["x"]
	last := r.Chain[len(r.Chain)-1]
	if last != defaultsName {
		t.Errorf("last chain element = %q, want %q (opt-out impossible)", last, defaultsName)
	}
	if r.Env["ROOT"] != "1" {
		t.Errorf("env should inherit from _defaults: ROOT=%q", r.Env["ROOT"])
	}
}

func TestResolveCycleDetection(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName},
		"a":          {Name: "a", Parents: []string{"b"}},
		"b":          {Name: "b", Parents: []string{"a"}},
	}
	_, err := Resolve(cfgs)
	if err == nil {
		t.Fatal("expected cycle error, got nil")
	}
	if !strings.Contains(err.Error(), "cycle") {
		t.Errorf("error = %q, want cycle message", err.Error())
	}
}

func TestResolveMissingParent(t *testing.T) {
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName},
		"a":          {Name: "a", Parents: []string{"ghost"}},
	}
	_, err := Resolve(cfgs)
	if err == nil {
		t.Fatal("expected missing-parent error, got nil")
	}
	if !strings.Contains(err.Error(), "ghost") || !strings.Contains(err.Error(), "not found") {
		t.Errorf("error = %q, want contains 'ghost' and 'not found'", err.Error())
	}
}

func TestResolveMissingDefaults(t *testing.T) {
	cfgs := map[string]*NSConfig{
		"x": {Name: "x"},
	}
	_, err := Resolve(cfgs)
	if err == nil {
		t.Fatal("expected missing _defaults error, got nil")
	}
	if !strings.Contains(err.Error(), defaultsName) {
		t.Errorf("error = %q, want contains %q", err.Error(), defaultsName)
	}
}

func TestResolveChainOrderWithDiamond(t *testing.T) {
	// Diamond: top -> {left, right} -> base; base -> _defaults.
	cfgs := map[string]*NSConfig{
		defaultsName: {Name: defaultsName, Env: map[string]string{"R": "r"}},
		"base":       {Name: "base", Parents: []string{defaultsName}, Env: map[string]string{"B": "b"}},
		"left":       {Name: "left", Parents: []string{"base"}, Env: map[string]string{"L": "l"}},
		"right":      {Name: "right", Parents: []string{"base"}, Env: map[string]string{"L": "r"}},
		"top":        {Name: "top", Parents: []string{"left", "right"}, Env: map[string]string{"T": "t"}},
	}
	out, err := Resolve(cfgs)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	r := out["top"]
	if r == nil {
		t.Fatal("missing top")
	}
	// top first, _defaults last.
	if r.Chain[0] != "top" || r.Chain[len(r.Chain)-1] != defaultsName {
		t.Errorf("chain = %v, want top first and _defaults last", r.Chain)
	}
	// base appears exactly once (diamond de-duped).
	count := 0
	for _, c := range r.Chain {
		if c == "base" {
			count++
		}
	}
	if count != 1 {
		t.Errorf("base appears %d times in %v, want 1", count, r.Chain)
	}
	// top inherits R from _defaults.
	if r.Env["R"] != "r" {
		t.Errorf("top should inherit R=r, got %q", r.Env["R"])
	}
}

func eqSlice(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}
