package controllogic

import (
	"math"
	"testing"
)

func TestEvalExpr(t *testing.T) {
	resolve := func(ds, name string) float64 {
		switch {
		case ds == "stub" && name == "x":
			return 10
		case ds == "local" && name == "y":
			return 3
		case ds == "sys" && name == "dt":
			return 0.5
		}
		return math.NaN()
	}
	cases := []struct {
		expr string
		want float64
	}{
		{"1 + 2 * 3", 7},
		{"(1 + 2) * 3", 9},
		{"10 % 3", 1},
		{"{stub:x} + y", 13},
		{"{stub:x} > 5 ? 1 : 0", 1},
		{"min(4, 2, 8)", 2},
		{"max(4, 2, 8)", 8},
		{"abs(-5)", 5},
		{"sqrt(16)", 4},
		{"2 < 3 && 3 < 4", 1},
		{"!0", 1},
		{"-{stub:x}", -10},
		{"{sys:dt} * 2", 1},
		{"true + false", 1},
	}
	for _, c := range cases {
		got := EvalExpr(c.expr, resolve)
		if math.Abs(got-c.want) > 1e-9 {
			t.Errorf("EvalExpr(%q) = %v, want %v", c.expr, got, c.want)
		}
	}
}

func TestEvalExprErrors(t *testing.T) {
	r := func(ds, name string) float64 { return 0 }
	for _, bad := range []string{"1 +", "(1", "1 2", "{unterminated"} {
		if v := EvalExpr(bad, r); !math.IsNaN(v) {
			t.Errorf("EvalExpr(%q) = %v, want NaN", bad, v)
		}
	}
}

func TestCollectRefs(t *testing.T) {
	refs := CollectRefs("{stub:x} + y + {epics:MY:PV} * z")
	got := map[string]bool{}
	for _, r := range refs {
		got[r.DS+":"+r.Name] = true
	}
	for _, want := range []string{"stub:x", "local:y", "epics:MY:PV", "local:z"} {
		if !got[want] {
			t.Errorf("CollectRefs missing %q (got %v)", want, got)
		}
	}
}

func TestCheckExpr(t *testing.T) {
	if msg := CheckExpr("1 + 2"); msg != "" {
		t.Errorf("CheckExpr valid returned %q", msg)
	}
	if msg := CheckExpr("1 +"); msg == "" {
		t.Errorf("CheckExpr invalid returned empty")
	}
	if msg := CheckExpr(""); msg != "" {
		t.Errorf("CheckExpr empty returned %q", msg)
	}
}

func TestEpicsRefSplitFirstColon(t *testing.T) {
	refs := CollectRefs("{epics:SR:BPM:01:X}")
	if len(refs) != 1 || refs[0].DS != "epics" || refs[0].Name != "SR:BPM:01:X" {
		t.Errorf("got %+v, want epics / SR:BPM:01:X", refs)
	}
}
