package confmgr

import (
	"errors"
	"testing"
)

func TestApplyWritesValues(t *testing.T) {
	set := ConfigSet{
		ID:   "set1",
		Name: "s",
		Parameters: []Parameter{
			{Key: "v", DS: "epics", Signal: "PSU:V", Type: TypeFloat, Default: 12.0},
			{Key: "en", DS: "epics", Signal: "PSU:EN", Type: TypeBool},
			{Key: "opt", DS: "epics", Signal: "PSU:OPT", Type: TypeFloat}, // no value, no default → skipped
		},
	}
	inst := ConfigInstance{ID: "i1", SetID: "set1", Values: map[string]any{"v": 24.0, "en": true}}

	type write struct {
		ds, sig string
		val     any
	}
	var writes []write
	res := Apply(set, inst, func(ds, signal string, value any) error {
		writes = append(writes, write{ds, signal, value})
		return nil
	})

	if res.Applied != 2 || res.Skipped != 1 || res.Failed != 0 {
		t.Fatalf("summary: applied=%d skipped=%d failed=%d", res.Applied, res.Skipped, res.Failed)
	}
	if len(writes) != 2 {
		t.Fatalf("want 2 writes, got %d", len(writes))
	}
	if writes[0].sig != "PSU:V" || writes[0].val != 24.0 {
		t.Errorf("first write: %+v", writes[0])
	}
}

func TestApplyUsesDefaultWhenNoValue(t *testing.T) {
	set := ConfigSet{
		ID: "set1", Name: "s",
		Parameters: []Parameter{{Key: "v", DS: "d", Signal: "S", Type: TypeFloat, Default: 7.0}},
	}
	inst := ConfigInstance{ID: "i", SetID: "set1", Values: map[string]any{}}
	var got any
	res := Apply(set, inst, func(_, _ string, value any) error { got = value; return nil })
	if res.Applied != 1 || got != 7.0 {
		t.Errorf("want default 7.0 applied, got %v (applied=%d)", got, res.Applied)
	}
}

func TestApplyArrayNormalizes(t *testing.T) {
	set := ConfigSet{ID: "s", Name: "s", Parameters: []Parameter{
		{Key: "wf", DS: "d", Signal: "WF", Type: TypeFloatArray},
	}}
	// JSON decoding yields []any of float64 — apply must hand the datasource a []float64.
	inst := ConfigInstance{ID: "i", SetID: "s", Values: map[string]any{"wf": []any{1.0, 2.0, 3.0}}}
	var got any
	res := Apply(set, inst, func(_, _ string, value any) error { got = value; return nil })
	if res.Applied != 1 {
		t.Fatalf("applied=%d", res.Applied)
	}
	arr, ok := got.([]float64)
	if !ok || len(arr) != 3 || arr[0] != 1 || arr[2] != 3 {
		t.Fatalf("want []float64{1,2,3}, got %T %v", got, got)
	}
}

func TestArrayValidation(t *testing.T) {
	lo := 0.0
	p := Parameter{Key: "wf", DS: "d", Signal: "S", Type: TypeFloatArray, Min: &lo}
	if err := p.checkValue([]any{1.0, 2.0}); err != nil {
		t.Fatalf("valid array rejected: %v", err)
	}
	if err := p.checkValue([]any{1.0, -5.0}); err == nil {
		t.Fatal("expected out-of-range element to be rejected")
	}
	if err := p.checkValue("notarray"); err == nil {
		t.Fatal("expected non-array value to be rejected")
	}
}

func TestApplyRecordsFailures(t *testing.T) {
	set := ConfigSet{
		ID: "set1", Name: "s",
		Parameters: []Parameter{
			{Key: "a", DS: "d", Signal: "A", Type: TypeFloat, Default: 1.0},
			{Key: "b", DS: "d", Signal: "B", Type: TypeFloat, Default: 2.0},
		},
	}
	inst := ConfigInstance{ID: "i", SetID: "set1", Values: map[string]any{}}
	res := Apply(set, inst, func(_, signal string, _ any) error {
		if signal == "B" {
			return errors.New("write rejected")
		}
		return nil
	})
	if res.Applied != 1 || res.Failed != 1 {
		t.Fatalf("want applied=1 failed=1, got applied=%d failed=%d", res.Applied, res.Failed)
	}
	for _, e := range res.Entries {
		if e.Key == "b" && (e.OK || e.Error == "") {
			t.Errorf("entry b should record failure: %+v", e)
		}
	}
}
