package taste

import (
	"math"
	"testing"
)

func almostEqual(a, b float64) bool { return math.Abs(a-b) < 1e-9 }

func TestEngagement_Curve(t *testing.T) {
	p := DefaultEngagementParams() // HardSkip 0.05, Neutral 0.30, Full 0.90
	cases := []struct {
		name       string
		completion float64
		want       float64
	}{
		{"instant skip", 0.00, -1},
		{"at hard-skip boundary", 0.05, -1},
		{"neg ramp midpoint", 0.175, -0.5}, // (0.175-0.05)/(0.30-0.05)=0.5 → -0.5
		{"neutral point", 0.30, 0},
		{"pos ramp midpoint", 0.60, 0.5}, // (0.60-0.30)/(0.90-0.30)=0.5
		{"at full boundary", 0.90, 1},
		{"complete", 1.00, 1},
		{"clamp above 1", 1.50, 1},
		{"clamp below 0", -0.5, -1},
	}
	for _, c := range cases {
		if got := Engagement(c.completion, p); !almostEqual(got, c.want) {
			t.Errorf("%s: Engagement(%.3f) = %.4f, want %.4f", c.name, c.completion, got, c.want)
		}
	}
}

func TestEngagement_MonotonicNonDecreasing(t *testing.T) {
	p := DefaultEngagementParams()
	prev := math.Inf(-1)
	for i := 0; i <= 100; i++ {
		c := float64(i) / 100
		got := Engagement(c, p)
		if got < prev-1e-9 {
			t.Fatalf("engagement decreased at completion %.2f: %.4f < %.4f", c, got, prev)
		}
		prev = got
	}
}

func TestEngagement_DegenerateParamsStayInRange(t *testing.T) {
	// Non-increasing thresholds must collapse ramps (no divide-by-zero) and
	// still produce a value in [-1, 1].
	p := EngagementParams{HardSkip: 0.5, NeutralCompletion: 0.5, FullCompletion: 0.5}
	for _, c := range []float64{0, 0.25, 0.5, 0.75, 1} {
		got := Engagement(c, p)
		if got < -1 || got > 1 {
			t.Errorf("Engagement(%.2f) = %.3f out of [-1,1]", c, got)
		}
	}
}

func TestDecay_HalfLife(t *testing.T) {
	if got := decay(0, 75); !almostEqual(got, 1) {
		t.Errorf("decay(0) = %.4f, want 1", got)
	}
	if got := decay(75, 75); !almostEqual(got, 0.5) {
		t.Errorf("decay(halflife) = %.4f, want 0.5", got)
	}
	if got := decay(150, 75); !almostEqual(got, 0.25) {
		t.Errorf("decay(2*halflife) = %.4f, want 0.25", got)
	}
	if got := decay(-5, 75); !almostEqual(got, 1) {
		t.Errorf("decay(negative age) = %.4f, want 1 (clamped)", got)
	}
	if got := decay(10, 0); !almostEqual(got, 1) {
		t.Errorf("decay(halflife=0) = %.4f, want 1 (guarded)", got)
	}
}
