package confmgr

import (
	"testing"
)

// TestParamTypeValid covers the valid/invalid branches of ParamType.valid.
func TestParamTypeValid(t *testing.T) {
	for _, ok := range []ParamType{TypeFloat, TypeInt, TypeBool, TypeString, TypeEnum, TypeFloatArray} {
		if !ok.valid() {
			t.Errorf("%q should be valid", ok)
		}
	}
	if ParamType("bogus").valid() {
		t.Error("bogus type should be invalid")
	}
}

// TestCheckValue exercises every type branch of Parameter.checkValue, including
// the type-mismatch and range-violation error paths.
func TestCheckValue(t *testing.T) {
	cases := []struct {
		name    string
		p       Parameter
		v       any
		wantErr bool
	}{
		{"float ok", Parameter{Type: TypeFloat}, 1.5, false},
		{"float from string", Parameter{Type: TypeFloat}, "2.5", false},
		{"float not numeric", Parameter{Type: TypeFloat}, true, true},
		{"float below min", Parameter{Type: TypeFloat, Min: fptr(0)}, -1.0, true},
		{"float above max", Parameter{Type: TypeFloat, Max: fptr(10)}, 11.0, true},
		{"int ok", Parameter{Type: TypeInt}, 4.0, false},
		{"int non-integer", Parameter{Type: TypeInt}, 4.5, true},
		{"bool ok", Parameter{Type: TypeBool}, true, false},
		{"bool wrong type", Parameter{Type: TypeBool}, 1.0, true},
		{"string ok", Parameter{Type: TypeString}, "hi", false},
		{"string wrong type", Parameter{Type: TypeString}, 1.0, true},
		{"enum ok", Parameter{Type: TypeEnum, EnumValues: []string{"a", "b"}}, "b", false},
		{"enum not string", Parameter{Type: TypeEnum, EnumValues: []string{"a"}}, 1.0, true},
		{"enum not allowed", Parameter{Type: TypeEnum, EnumValues: []string{"a"}}, "z", true},
		{"array ok", Parameter{Type: TypeFloatArray}, []any{1.0, 2.0}, false},
		{"array native", Parameter{Type: TypeFloatArray}, []float64{1, 2}, false},
		{"array not array", Parameter{Type: TypeFloatArray}, 1.0, true},
		{"array elem below min", Parameter{Type: TypeFloatArray, Min: fptr(0)}, []any{-1.0}, true},
		{"array elem above max", Parameter{Type: TypeFloatArray, Max: fptr(5)}, []any{9.0}, true},
		{"array bad elem", Parameter{Type: TypeFloatArray}, []any{"nope"}, true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := tc.p.checkValue(tc.v)
			if tc.wantErr && err == nil {
				t.Errorf("checkValue(%v): want error", tc.v)
			}
			if !tc.wantErr && err != nil {
				t.Errorf("checkValue(%v): unexpected error %v", tc.v, err)
			}
		})
	}
}

// TestValidateInvalidType covers the invalid-type branch of ConfigSet.Validate.
func TestValidateInvalidType(t *testing.T) {
	set := ConfigSet{Name: "x", Parameters: []Parameter{
		{Key: "a", DS: "d", Signal: "s", Type: ParamType("weird")},
	}}
	if err := set.Validate(); err == nil {
		t.Error("invalid parameter type: want error")
	}
}

// TestValidateEnumRequiresValues covers the enum-without-values branch.
func TestValidateEnumRequiresValues(t *testing.T) {
	set := ConfigSet{Name: "x", Parameters: []Parameter{
		{Key: "a", DS: "d", Signal: "s", Type: TypeEnum},
	}}
	if err := set.Validate(); err == nil {
		t.Error("enum without values: want error")
	}
}

// TestValidateMinGreaterThanMax covers the min>max branch.
func TestValidateMinGreaterThanMax(t *testing.T) {
	set := ConfigSet{Name: "x", Parameters: []Parameter{
		{Key: "a", DS: "d", Signal: "s", Type: TypeFloat, Min: fptr(10), Max: fptr(1)},
	}}
	if err := set.Validate(); err == nil {
		t.Error("min>max: want error")
	}
}

// TestValidateAgainstMandatoryNoDefault covers the mandatory-without-default
// branch of ValidateAgainst.
func TestValidateAgainstMandatoryNoDefault(t *testing.T) {
	set := ConfigSet{Name: "x", Parameters: []Parameter{
		{Key: "req", DS: "d", Signal: "s", Type: TypeFloat, Mandatory: true},
	}}
	inst := ConfigInstance{SetID: "x", Values: map[string]any{}}
	if err := inst.ValidateAgainst(set); err == nil {
		t.Error("missing mandatory value: want error")
	}
	// Providing the value clears the error.
	inst.Values["req"] = 1.0
	if err := inst.ValidateAgainst(set); err != nil {
		t.Errorf("with value: unexpected error %v", err)
	}
}

// TestResolveAndNormalize covers Resolve fallbacks and array normalization.
func TestResolveAndNormalize(t *testing.T) {
	p := Parameter{Key: "v", Type: TypeFloat, Default: 7.0}
	inst := ConfigInstance{Values: map[string]any{}}
	if got, ok := inst.Resolve(p); !ok || got != 7.0 {
		t.Errorf("Resolve default: got %v,%v want 7,true", got, ok)
	}
	inst.Values["v"] = 3.0
	if got, ok := inst.Resolve(p); !ok || got != 3.0 {
		t.Errorf("Resolve value: got %v,%v want 3,true", got, ok)
	}
	// Optional param without default → no value.
	if _, ok := inst.Resolve(Parameter{Key: "none", Type: TypeFloat}); ok {
		t.Error("Resolve no-default: want ok=false")
	}

	arr := Parameter{Type: TypeFloatArray}
	out := arr.normalize([]any{1.0, 2.0, 3.0})
	if got, ok := out.([]float64); !ok || len(got) != 3 {
		t.Errorf("normalize array: got %T %v", out, out)
	}
	// Non-array passthrough.
	if got := arr.normalize("scalar"); got != "scalar" {
		t.Errorf("normalize passthrough: got %v", got)
	}
}
