package layoutauthoringv1

import (
	"strings"
	"testing"
)

func TestRecursiveValueLimits(t *testing.T) {
	valueAtDepth := func(depth int) Value {
		value := nullValue()
		for level := 1; level < depth; level++ {
			items := []Value{value}
			value = Value{Kind: ValueList, List: &items}
		}
		return value
	}
	if err := validateValue(valueAtDepth(MaxValueDepth), 1, &valueBudget{}); err != nil {
		t.Fatalf("exact depth rejected: %v", err)
	}
	if err := validateValue(valueAtDepth(MaxValueDepth+1), 1, &valueBudget{}); err == nil {
		t.Fatal("one-over depth accepted")
	}

	exactEntries := make([]Value, MaxValueCollectionEntries)
	for index := range exactEntries {
		exactEntries[index] = nullValue()
	}
	if err := validateValue(Value{Kind: ValueList, List: &exactEntries}, 1, &valueBudget{}); err != nil {
		t.Fatalf("exact collection entries rejected: %v", err)
	}
	overEntries := append(exactEntries, nullValue())
	if err := validateValue(Value{Kind: ValueList, List: &overEntries}, 1, &valueBudget{}); err == nil {
		t.Fatal("one-over collection entries accepted")
	}

	nodeBudget := &valueBudget{nodes: MaxValueNodes - 1}
	if err := validateValue(nullValue(), 1, nodeBudget); err != nil {
		t.Fatalf("exact node count rejected: %v", err)
	}
	if err := validateValue(nullValue(), 1, nodeBudget); err == nil {
		t.Fatal("one-over node count accepted")
	}

	emptyList := []Value{}
	collectionBudget := &valueBudget{collections: MaxValueCollections - 1}
	if err := validateValue(Value{Kind: ValueList, List: &emptyList}, 1, collectionBudget); err != nil {
		t.Fatalf("exact collection count rejected: %v", err)
	}
	if err := validateValue(Value{Kind: ValueList, List: &emptyList}, 1, collectionBudget); err == nil {
		t.Fatal("one-over collection count accepted")
	}
}

func TestRecursiveValueKeyAndScalarLimits(t *testing.T) {
	key := strings.Repeat("k", MaxValueKeyBytes)
	object := []NamedValue{{Name: key, Value: nullValue()}}
	if err := validateValue(Value{Kind: ValueObject, Object: &object}, 1, &valueBudget{}); err != nil {
		t.Fatalf("exact key bound rejected: %v", err)
	}
	object[0].Name += "k"
	if err := validateValue(Value{Kind: ValueObject, Object: &object}, 1, &valueBudget{}); err == nil {
		t.Fatal("one-over key bound accepted")
	}

	text := strings.Repeat("v", MaxValueStringBytes)
	if err := validateValue(stringValue(text), 1, &valueBudget{}); err != nil {
		t.Fatalf("exact string bound rejected: %v", err)
	}
	text += "v"
	if err := validateValue(stringValue(text), 1, &valueBudget{}); err == nil {
		t.Fatal("one-over string bound accepted")
	}

	decimal := strings.Repeat("1", MaxDecimalBytes)
	if err := validateValue(decimalValue(decimal), 1, &valueBudget{}); err != nil {
		t.Fatalf("exact decimal bound rejected: %v", err)
	}
	if err := validateValue(decimalValue(decimal+"1"), 1, &valueBudget{}); err == nil {
		t.Fatal("one-over decimal bound accepted")
	}
}

func TestValueUnionIsClosedAndExact(t *testing.T) {
	text := "x"
	boolean := true
	list := []Value{}
	object := []NamedValue{}
	nilList := []Value(nil)
	nilObject := []NamedValue(nil)
	for name, value := range map[string]Value{
		"unknown kind":          {Kind: "INTEGER"},
		"null with arm":         {Kind: ValueNull, String: &text},
		"missing arm":           {Kind: ValueString},
		"multiple arms":         {Kind: ValueString, String: &text, Boolean: &boolean},
		"kind mismatch":         {Kind: ValueList, Object: &object},
		"list with nil value":   {Kind: ValueList, List: &nilList},
		"object with nil value": {Kind: ValueObject, Object: &nilObject},
		"unsorted object":       {Kind: ValueObject, Object: &[]NamedValue{{Name: "z", Value: nullValue()}, {Name: "a", Value: nullValue()}}},
	} {
		t.Run(name, func(t *testing.T) {
			if err := validateValue(value, 1, &valueBudget{}); err == nil {
				t.Fatal("invalid union unexpectedly accepted")
			}
		})
	}
	for name, value := range map[string]Value{
		"null":    nullValue(),
		"boolean": boolValue(true),
		"string":  stringValue("x"),
		"decimal": decimalValue("-10.25"),
		"list":    {Kind: ValueList, List: &list},
		"object":  {Kind: ValueObject, Object: &object},
	} {
		t.Run(name, func(t *testing.T) {
			if err := validateValue(value, 1, &valueBudget{}); err != nil {
				t.Fatalf("valid union rejected: %v", err)
			}
		})
	}
}
