package dsp

import (
	"math"
	"testing"
)

func TestCompositeClipperPeakLimit(t *testing.T) {
	// A signal at 2× ceiling must never exceed ceiling at output.
	c := NewCompositeClipper(CompositeClipperConfig{
		Ceiling:    1.0,
		Iterations: 3,
		SoftKnee:   0,
		SampleRate: 228000,
	})

	rate := 228000.0
	n := int(rate * 0.05) // 50ms
	maxOut := 0.0
	for i := 0; i < n; i++ {
		// 1kHz tone at amplitude 2.0 (200% modulation)
		in := 2.0 * math.Sin(2*math.Pi*1000*float64(i)/rate)
		out := c.Process(in)
		if math.Abs(out) > maxOut {
			maxOut = math.Abs(out)
		}
	}

	if maxOut > 1.001 {
		t.Errorf("peak %.6f exceeds ceiling 1.0", maxOut)
	}
	t.Logf("hard clip: peak=%.6f (ceiling=1.0)", maxOut)
}

func TestCompositeClipperSoftKneePeakLimit(t *testing.T) {
	c := NewCompositeClipper(CompositeClipperConfig{
		Ceiling:    1.0,
		Iterations: 3,
		SoftKnee:   0.2,
		SampleRate: 228000,
	})

	rate := 228000.0
	n := int(rate * 0.05)
	maxOut := 0.0
	for i := 0; i < n; i++ {
		in := 2.0 * math.Sin(2*math.Pi*1000*float64(i)/rate)
		out := c.Process(in)
		if math.Abs(out) > maxOut {
			maxOut = math.Abs(out)
		}
	}

	if maxOut > 1.001 {
		t.Errorf("soft clip peak %.6f exceeds ceiling 1.0", maxOut)
	}
	t.Logf("soft clip (knee=0.2): peak=%.6f", maxOut)
}

func TestCompositeClipperLookaheadPeakLimit(t *testing.T) {
	c := NewCompositeClipper(CompositeClipperConfig{
		Ceiling:     1.0,
		Iterations:  3,
		SoftKnee:    0.15,
		LookaheadMs: 1.0,
		SampleRate:  228000,
	})

	rate := 228000.0
	n := int(rate * 0.10) // 100ms to let look-ahead settle
	maxOut := 0.0
	for i := 0; i < n; i++ {
		in := 2.0 * math.Sin(2*math.Pi*1000*float64(i)/rate)
		out := c.Process(in)
		if math.Abs(out) > maxOut {
			maxOut = math.Abs(out)
		}
	}

	if maxOut > 1.001 {
		t.Errorf("lookahead peak %.6f exceeds ceiling 1.0", maxOut)
	}
	t.Logf("lookahead (1ms, soft knee=0.15, 3 iter): peak=%.6f", maxOut)
}

func TestCompositeClipperGuardBands(t *testing.T) {
	// Verify that after clipping, 19kHz and 57kHz energy is suppressed.
	// Feed a 1kHz sine at high level → clips → generates harmonics.
	// The clipper's notch filters should remove 19kHz and 57kHz.
	rate := 228000.0
	n := int(rate * 0.1) // 100ms

	for _, tc := range []struct {
		name       string
		iterations int
		softKnee   float64
		lookahead  float64
	}{
		{"hard-1iter", 1, 0, 0},
		{"hard-3iter", 3, 0, 0},
		{"soft-3iter", 3, 0.15, 0},
		{"lookahead-3iter", 3, 0.15, 1.0},
	} {
		t.Run(tc.name, func(t *testing.T) {
			c := NewCompositeClipper(CompositeClipperConfig{
				Ceiling:     1.0,
				Iterations:  tc.iterations,
				SoftKnee:    tc.softKnee,
				LookaheadMs: tc.lookahead,
				SampleRate:  rate,
			})

			out := make([]float64, n)
			for i := 0; i < n; i++ {
				in := 2.0 * math.Sin(2*math.Pi*1000*float64(i)/rate)
				out[i] = c.Process(in)
			}

			// Skip first 10ms for filter settling
			skip := int(rate * 0.01)
			analysis := out[skip:]

			e19 := GoertzelEnergy(analysis, rate, 19000)
			e57 := GoertzelEnergy(analysis, rate, 57000)
			e1k := GoertzelEnergy(analysis, rate, 1000)

			r19 := -100.0
			r57 := -100.0
			if e1k > 0 {
				if e19 > 0 {
					r19 = 10 * math.Log10(e19/e1k)
				}
				if e57 > 0 {
					r57 = 10 * math.Log10(e57/e1k)
				}
			}

			t.Logf("19kHz: %.1f dB below 1kHz, 57kHz: %.1f dB below 1kHz", -r19, -r57)

			// With 3 iterations, 19kHz should be suppressed by at least 40dB
			if tc.iterations >= 3 && r19 > -40 {
				t.Errorf("19kHz not sufficiently suppressed: %.1f dB (want < -40 dB)", r19)
			}
		})
	}
}

func TestSoftClipContinuity(t *testing.T) {
	// Verify C1 continuity at the knee boundary
	ceiling := 1.0
	knee := 0.2
	threshold := ceiling - knee // 0.8

	// Slope just below threshold (linear region)
	dx := 1e-8
	y0 := SoftClip(threshold-dx, ceiling, knee)
	y1 := SoftClip(threshold+dx, ceiling, knee)
	slope := (y1 - y0) / (2 * dx)

	if math.Abs(slope-1.0) > 0.01 {
		t.Errorf("slope at knee boundary = %.4f, want ~1.0", slope)
	}

	// Verify output never exceeds ceiling
	for x := 0.0; x <= 5.0; x += 0.001 {
		y := SoftClip(x, ceiling, knee)
		if y > ceiling+1e-9 {
			t.Fatalf("SoftClip(%.3f) = %.6f > ceiling %.6f", x, y, ceiling)
		}
	}

	// Verify asymptotic approach to ceiling
	y5 := SoftClip(5.0, ceiling, knee)
	if y5 < 0.99 {
		t.Errorf("SoftClip(5.0) = %.6f, expected near ceiling (%.6f)", y5, ceiling)
	}
}

func TestCompositeClipperReset(t *testing.T) {
	c := NewCompositeClipper(CompositeClipperConfig{
		Ceiling:     1.0,
		Iterations:  2,
		SoftKnee:    0.15,
		LookaheadMs: 1.0,
		SampleRate:  228000,
	})

	// Process some samples
	for i := 0; i < 1000; i++ {
		c.Process(0.5)
	}

	stats := c.Stats()
	if stats.Envelope == 0 {
		t.Error("envelope should be non-zero after processing")
	}

	c.Reset()

	stats = c.Stats()
	if stats.Envelope != 0 {
		t.Errorf("envelope should be 0 after reset, got %f", stats.Envelope)
	}
	if stats.LookaheadGain != 1.0 {
		t.Errorf("gain should be 1.0 after reset, got %f", stats.LookaheadGain)
	}
}
