package runtime

import (
	"testing"

	"sdr-visual-suite/internal/config"
)

func TestApplyConfigUpdate(t *testing.T) {
	cfg := config.Default()
	mgr := New(cfg)

	center := 7.2e6
	sampleRate := 1_024_000
	fftSize := 4096
	threshold := -35.0
	bw := 1536
	cfarMode := "OS"
	cfarWrap := true
	cfarGuard := 2
	cfarTrain := 12
	cfarRank := 18
	cfarScale := 5.5

	updated, err := mgr.ApplyConfig(ConfigUpdate{
		CenterHz:   &center,
		SampleRate: &sampleRate,
		FFTSize:    &fftSize,
		TunerBwKHz: &bw,
		Detector: &DetectorUpdate{
			ThresholdDb:    &threshold,
			CFARMode:       &cfarMode,
			CFARWrapAround: &cfarWrap,
			CFARGuardCells: &cfarGuard,
			CFARTrainCells: &cfarTrain,
			CFARRank:       &cfarRank,
			CFARScaleDb:    &cfarScale,
		},
	})
	if err != nil {
		t.Fatalf("apply: %v", err)
	}
	if updated.CenterHz != center {
		t.Fatalf("center hz: %v", updated.CenterHz)
	}
	if updated.SampleRate != sampleRate {
		t.Fatalf("sample rate: %v", updated.SampleRate)
	}
	if updated.FFTSize != fftSize {
		t.Fatalf("fft size: %v", updated.FFTSize)
	}
	if updated.Detector.ThresholdDb != threshold {
		t.Fatalf("threshold: %v", updated.Detector.ThresholdDb)
	}
	if updated.Detector.CFARMode != cfarMode {
		t.Fatalf("cfar mode: %v", updated.Detector.CFARMode)
	}
	if updated.Detector.CFARWrapAround != cfarWrap {
		t.Fatalf("cfar wrap: %v", updated.Detector.CFARWrapAround)
	}
	if updated.Detector.CFARGuardCells != cfarGuard {
		t.Fatalf("cfar guard: %v", updated.Detector.CFARGuardCells)
	}
	if updated.Detector.CFARTrainCells != cfarTrain {
		t.Fatalf("cfar train: %v", updated.Detector.CFARTrainCells)
	}
	if updated.Detector.CFARRank != cfarRank {
		t.Fatalf("cfar rank: %v", updated.Detector.CFARRank)
	}
	if updated.Detector.CFARScaleDb != cfarScale {
		t.Fatalf("cfar scale: %v", updated.Detector.CFARScaleDb)
	}
	if updated.TunerBwKHz != bw {
		t.Fatalf("tuner bw: %v", updated.TunerBwKHz)
	}
}

func TestApplyConfigRejectsInvalid(t *testing.T) {
	cfg := config.Default()

	{
		mgr := New(cfg)
		bad := 0
		if _, err := mgr.ApplyConfig(ConfigUpdate{SampleRate: &bad}); err == nil {
			t.Fatalf("expected error")
		}
		snap := mgr.Snapshot()
		if snap.SampleRate != cfg.SampleRate {
			t.Fatalf("sample rate changed on error")
		}
	}

	{
		mgr := New(cfg)
		badAlpha := -0.5
		if _, err := mgr.ApplyConfig(ConfigUpdate{Detector: &DetectorUpdate{EmaAlpha: &badAlpha}}); err == nil {
			t.Fatalf("expected ema_alpha error")
		}
		if mgr.Snapshot().Detector.EmaAlpha != cfg.Detector.EmaAlpha {
			t.Fatalf("ema_alpha changed on error")
		}
	}

	{
		mgr := New(cfg)
		badAlpha := 1.5
		if _, err := mgr.ApplyConfig(ConfigUpdate{Detector: &DetectorUpdate{EmaAlpha: &badAlpha}}); err == nil {
			t.Fatalf("expected ema_alpha upper bound error")
		}
		if mgr.Snapshot().Detector.EmaAlpha != cfg.Detector.EmaAlpha {
			t.Fatalf("ema_alpha changed on error")
		}
	}

	{
		mgr := New(cfg)
		badHyst := -1.0
		if _, err := mgr.ApplyConfig(ConfigUpdate{Detector: &DetectorUpdate{HysteresisDb: &badHyst}}); err == nil {
			t.Fatalf("expected hysteresis_db error")
		}
		if mgr.Snapshot().Detector.HysteresisDb != cfg.Detector.HysteresisDb {
			t.Fatalf("hysteresis_db changed on error")
		}
	}

	{
		mgr := New(cfg)
		badStable := 0
		if _, err := mgr.ApplyConfig(ConfigUpdate{Detector: &DetectorUpdate{MinStableFrames: &badStable}}); err == nil {
			t.Fatalf("expected min_stable_frames error")
		}
		if mgr.Snapshot().Detector.MinStableFrames != cfg.Detector.MinStableFrames {
			t.Fatalf("min_stable_frames changed on error")
		}
	}

	{
		mgr := New(cfg)
		badGap := -10
		if _, err := mgr.ApplyConfig(ConfigUpdate{Detector: &DetectorUpdate{GapToleranceMs: &badGap}}); err == nil {
			t.Fatalf("expected gap_tolerance_ms error")
		}
		if mgr.Snapshot().Detector.GapToleranceMs != cfg.Detector.GapToleranceMs {
			t.Fatalf("gap_tolerance_ms changed on error")
		}
	}
}

func TestApplySettings(t *testing.T) {
	cfg := config.Default()
	mgr := New(cfg)
	agc := true
	dc := true
	iq := true
	updated, err := mgr.ApplySettings(SettingsUpdate{
		AGC:       &agc,
		DCBlock:   &dc,
		IQBalance: &iq,
	})
	if err != nil {
		t.Fatalf("apply settings: %v", err)
	}
	if !updated.AGC || !updated.DCBlock || !updated.IQBalance {
		t.Fatalf("settings not applied: %+v", updated)
	}
}
