package confmgr

import (
	"testing"
)

func fptr(f float64) *float64 { return &f }

func sampleSet() ConfigSet {
	return ConfigSet{
		Name:        "PSU",
		Description: "power supply config",
		Parameters: []Parameter{
			{Key: "voltage", DS: "epics", Signal: "PSU:V", Type: TypeFloat, Default: 12.0, Mandatory: true, Min: fptr(0), Max: fptr(48)},
			{Key: "enabled", DS: "epics", Signal: "PSU:EN", Type: TypeBool, Default: false},
		},
	}
}

func TestCreateAndGetSet(t *testing.T) {
	s, err := New(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	out, err := s.CreateSet(sampleSet(), "initial")
	if err != nil {
		t.Fatal(err)
	}
	if out.ID == "" {
		t.Fatal("expected generated ID")
	}
	if out.Version != 1 {
		t.Errorf("version: want 1, got %d", out.Version)
	}
	got, err := s.GetSet(out.ID)
	if err != nil {
		t.Fatal(err)
	}
	if got.Name != "PSU" || len(got.Parameters) != 2 {
		t.Errorf("round-trip mismatch: %+v", got)
	}
}

func TestRuleBlocksInvalidInstance(t *testing.T) {
	s, _ := New(t.TempDir())
	set, _ := s.CreateSet(sampleSet(), "")
	if _, err := s.CreateRule(ConfigRule{
		Name:   "voltage cap",
		SetID:  set.ID,
		Source: "voltage: <=24",
	}, ""); err != nil {
		t.Fatal(err)
	}

	// In-range value passes.
	if _, err := s.CreateInstance(ConfigInstance{
		Name: "ok", SetID: set.ID, Values: map[string]any{"voltage": 20.0},
	}, ""); err != nil {
		t.Fatalf("expected in-range instance to save, got %v", err)
	}

	// Out-of-range value is rejected by the rule.
	_, err := s.CreateInstance(ConfigInstance{
		Name: "bad", SetID: set.ID, Values: map[string]any{"voltage": 40.0},
	}, "")
	if err == nil {
		t.Fatalf("expected rule violation to block save")
	}
	var re *RuleError
	if !asRuleError(err, &re) {
		t.Fatalf("expected *RuleError, got %T: %v", err, err)
	}
}

func TestRuleTransformPersists(t *testing.T) {
	s, _ := New(t.TempDir())
	set := sampleSet()
	max := 48.0
	set.Parameters = append(set.Parameters, Parameter{Key: "vmax", DS: "epics", Signal: "PSU:VMAX", Type: TypeFloat, Min: &max})
	created, _ := s.CreateSet(set, "")
	// Rule derives vmax = voltage * 2 (within range for voltage=20 -> 40).
	if _, err := s.CreateRule(ConfigRule{
		Name: "vmax derive", SetID: created.ID, Source: "voltage: number\nvmax: voltage * 2",
	}, ""); err != nil {
		t.Fatal(err)
	}
	inst, err := s.CreateInstance(ConfigInstance{
		Name: "x", SetID: created.ID, Values: map[string]any{"voltage": 20.0},
	}, "")
	if err != nil {
		t.Fatal(err)
	}
	if f, _ := toFloat(inst.Values["vmax"]); f != 40 {
		t.Fatalf("expected derived vmax=40 to persist, got %v", inst.Values["vmax"])
	}
}

func TestDisabledRuleSkipped(t *testing.T) {
	s, _ := New(t.TempDir())
	set, _ := s.CreateSet(sampleSet(), "")
	disabled := false
	if _, err := s.CreateRule(ConfigRule{
		Name:    "voltage cap",
		SetID:   set.ID,
		Enabled: &disabled,
		Source:  "voltage: <=24",
	}, ""); err != nil {
		t.Fatal(err)
	}
	// A value that would violate the rule still saves because the rule is off.
	if _, err := s.CreateInstance(ConfigInstance{
		Name: "bad", SetID: set.ID, Values: map[string]any{"voltage": 40.0},
	}, ""); err != nil {
		t.Fatalf("expected disabled rule to be skipped, got %v", err)
	}
}

func asRuleError(err error, target **RuleError) bool {
	for err != nil {
		if re, ok := err.(*RuleError); ok {
			*target = re
			return true
		}
		type unwrapper interface{ Unwrap() error }
		u, ok := err.(unwrapper)
		if !ok {
			return false
		}
		err = u.Unwrap()
	}
	return false
}

func TestSetVersioning(t *testing.T) {
	s, _ := New(t.TempDir())
	created, _ := s.CreateSet(sampleSet(), "")
	id := created.ID

	// Update -> v2.
	upd := sampleSet()
	upd.Description = "edited"
	v2, err := s.UpdateSet(id, upd, "edit")
	if err != nil {
		t.Fatal(err)
	}
	if v2.Version != 2 {
		t.Fatalf("version after update: want 2, got %d", v2.Version)
	}

	versions, err := s.Versions(KindSet, id)
	if err != nil {
		t.Fatal(err)
	}
	if len(versions) != 2 {
		t.Fatalf("want 2 versions, got %d", len(versions))
	}
	if !versions[0].Current || versions[0].Version != 2 {
		t.Errorf("newest should be current v2: %+v", versions[0])
	}

	// Old version still retrievable.
	old, err := s.GetSetVersion(id, 1)
	if err != nil {
		t.Fatal(err)
	}
	if old.Description != "power supply config" {
		t.Errorf("v1 description changed: %q", old.Description)
	}

	// Promote v1 -> becomes v3 with original content.
	if err := s.Promote(KindSet, id, 1); err != nil {
		t.Fatal(err)
	}
	cur, _ := s.GetSet(id)
	if cur.Version != 3 || cur.Description != "power supply config" {
		t.Errorf("after promote: want v3 original desc, got v%d %q", cur.Version, cur.Description)
	}
}

func TestForkSet(t *testing.T) {
	s, _ := New(t.TempDir())
	created, _ := s.CreateSet(sampleSet(), "")
	_, _ = s.UpdateSet(created.ID, sampleSet(), "")

	newID, err := s.Fork(KindSet, created.ID, 1)
	if err != nil {
		t.Fatal(err)
	}
	forked, err := s.GetSet(newID)
	if err != nil {
		t.Fatal(err)
	}
	if forked.ID == created.ID {
		t.Error("fork should have a new ID")
	}
	if forked.Version != 1 {
		t.Errorf("fork version: want 1, got %d", forked.Version)
	}
	if forked.Tag != "" {
		t.Errorf("fork tag should be cleared, got %q", forked.Tag)
	}
}

func TestDeleteSetSoftDeletes(t *testing.T) {
	s, _ := New(t.TempDir())
	created, _ := s.CreateSet(sampleSet(), "")
	if err := s.Delete(KindSet, created.ID); err != nil {
		t.Fatal(err)
	}
	if _, err := s.GetSet(created.ID); err == nil {
		t.Error("expected set to be gone after delete")
	}
	// Deleting again is a not-found.
	if err := s.Delete(KindSet, created.ID); err != ErrNotFound {
		t.Errorf("want ErrNotFound, got %v", err)
	}
}

func TestSetValidation(t *testing.T) {
	s, _ := New(t.TempDir())
	bad := ConfigSet{Name: "", Parameters: nil}
	if _, err := s.CreateSet(bad, ""); err == nil {
		t.Error("expected empty-name validation error")
	}
	dup := ConfigSet{Name: "x", Parameters: []Parameter{
		{Key: "a", DS: "d", Signal: "s", Type: TypeFloat},
		{Key: "a", DS: "d", Signal: "s2", Type: TypeFloat},
	}}
	if _, err := s.CreateSet(dup, ""); err == nil {
		t.Error("expected duplicate-key validation error")
	}
	defaultOOR := ConfigSet{Name: "x", Parameters: []Parameter{
		{Key: "a", DS: "d", Signal: "s", Type: TypeFloat, Default: 100.0, Max: fptr(10)},
	}}
	if _, err := s.CreateSet(defaultOOR, ""); err == nil {
		t.Error("expected out-of-range default validation error")
	}
}

func TestInstanceLifecycle(t *testing.T) {
	s, _ := New(t.TempDir())
	set, _ := s.CreateSet(sampleSet(), "")

	inst := ConfigInstance{
		Name:   "nominal",
		SetID:  set.ID,
		Values: map[string]any{"voltage": 24.0, "enabled": true},
	}
	out, err := s.CreateInstance(inst, "")
	if err != nil {
		t.Fatal(err)
	}
	if out.Version != 1 {
		t.Errorf("instance version: want 1, got %d", out.Version)
	}

	// Update value -> v2.
	out.Values["voltage"] = 36.0
	v2, err := s.UpdateInstance(out.ID, out, "bump")
	if err != nil {
		t.Fatal(err)
	}
	if v2.Version != 2 {
		t.Errorf("instance version after update: want 2, got %d", v2.Version)
	}
}

func TestInstanceValidation(t *testing.T) {
	s, _ := New(t.TempDir())
	set, _ := s.CreateSet(sampleSet(), "")

	// voltage is mandatory with a default, so omitting it is OK; but an
	// out-of-range value must fail.
	bad := ConfigInstance{Name: "x", SetID: set.ID, Values: map[string]any{"voltage": 999.0}}
	if _, err := s.CreateInstance(bad, ""); err == nil {
		t.Error("expected out-of-range value error")
	}

	// Unknown parameter key must fail.
	unknown := ConfigInstance{Name: "x", SetID: set.ID, Values: map[string]any{"nope": 1.0}}
	if _, err := s.CreateInstance(unknown, ""); err == nil {
		t.Error("expected unknown-parameter error")
	}

	// Unknown set must fail.
	missing := ConfigInstance{Name: "x", SetID: "does-not-exist", Values: map[string]any{}}
	if _, err := s.CreateInstance(missing, ""); err == nil {
		t.Error("expected missing-set error")
	}
}
