package watermark

import (
	"math"
	"testing"
)

func TestSTFTRoundTrip(t *testing.T) {
	const key = "test-stft-key"
	const duration = 150.0 // seconds — need > 136.5s for one full WM cycle

	nSamples := int(duration * WMRate)
	t.Logf("Generating %d samples @ %d Hz (%.1fs)", nSamples, WMRate, duration)
	t.Logf("WM cycle: %d STFT frames, %.1fs", FramesPerWM, float64(SamplesPerWM)/WMRate)

	// Generate test signal: broadband noise (the multiplicative watermark
	// needs energy in all frequency bins to work — a pure tone only has
	// energy in one bin and the watermark has no effect on silent bins)
	audio := make([]float64, nSamples)
	// Simple LCG pseudo-random for reproducibility
	var lcg uint64 = 12345
	for i := range audio {
		lcg = lcg*6364136223846793005 + 1442695040888963407
		audio[i] = 0.3 * (float64(int32(lcg>>33))/float64(1<<31))
	}

	rmsIn := rmsF64(audio)
	t.Logf("Input RMS: %.1f dBFS", 20*math.Log10(rmsIn+1e-12))

	// Embed watermark
	embedder := NewSTFTEmbedder(key)
	watermarked := embedder.ProcessBlock(audio)

	rmsOut := rmsF64(watermarked)
	t.Logf("Output RMS: %.1f dBFS", 20*math.Log10(rmsOut+1e-12))
	t.Logf("RMS change: %.2f dB", 20*math.Log10(rmsOut/rmsIn))

	// Detect watermark
	detector := NewSTFTDetector()
	// Skip embedder OLA startup (first FFTSize samples are pass-through, not watermarked)
	corrs, offset := detector.Detect(watermarked[FFTSize:])

	t.Logf("Detection offset: %d", offset)

	// Check correlations
	var nPositive, nNegative int
	var sumAbs float64
	for _, c := range corrs {
		sumAbs += math.Abs(c)
		if c > 0 {
			nPositive++
		} else {
			nNegative++
		}
	}
	avgAbs := sumAbs / float64(payloadBits)
	t.Logf("Correlations: avg|c|=%.1f, positive=%d, negative=%d", avgAbs, nPositive, nNegative)

	if avgAbs < 1.0 {
		t.Errorf("avg|c| too low: %.1f (expected >> 1.0)", avgAbs)
	}

	// Check against known payload
	payload := KeyToPayload(key)
	codeword := RSEncode(payload)
	var expectedBits [payloadBits]int
	for i := 0; i < payloadBits; i++ {
		expectedBits[i] = int((codeword[i/8] >> uint(7-(i%8))) & 1)
	}

	nerr := 0
	for i := 0; i < payloadBits; i++ {
		hard := 0
		if corrs[i] < 0 {
			hard = 1
		}
		if hard != expectedBits[i] {
			nerr++
			t.Logf("  ERR bit %d: corr=%+.2f, expected=%d got=%d", i, corrs[i], expectedBits[i], hard)
		}
	}
	t.Logf("BER: %d/%d (%.1f%%)", nerr, payloadBits, 100*float64(nerr)/float64(payloadBits))

	if nerr > 20 {
		t.Errorf("BER too high: %d/%d", nerr, payloadBits)
	}

	// Try RS decode
	var recv [rsTotalBytes]byte
	for i := 0; i < payloadBits; i++ {
		if corrs[i] < 0 {
			recv[i/8] |= 1 << uint(7-(i%8))
		}
	}

	// Try with erasures if needed
	decoded := false
	for nErase := 0; nErase <= rsCheckBytes; nErase++ {
		if nErase == 0 {
			// Try zero erasures (valid if BER=0)
			p, ok := RSDecode(recv, nil)
			if ok {
				if KeyMatchesPayload(key, p) {
					t.Logf("Decoded with 0 erasures: MATCH ✓")
					decoded = true
					break
				}
			}
			continue
		}
		// Erase weakest bytes by |correlation|
		type bc struct{ idx int; conf float64 }
		byteConfs := make([]bc, rsTotalBytes)
		for b := 0; b < rsTotalBytes; b++ {
			minC := math.Abs(corrs[b*8])
			for bit := 1; bit < 8; bit++ {
				c := math.Abs(corrs[b*8+bit])
				if c < minC {
					minC = c
				}
			}
			byteConfs[b] = bc{b, minC}
		}
		// Sort by confidence (weakest first)
		for i := 0; i < len(byteConfs); i++ {
			for j := i + 1; j < len(byteConfs); j++ {
				if byteConfs[j].conf < byteConfs[i].conf {
					byteConfs[i], byteConfs[j] = byteConfs[j], byteConfs[i]
				}
			}
		}
		erasePos := make([]int, nErase)
		for i := 0; i < nErase; i++ {
			erasePos[i] = byteConfs[i].idx
		}
		// Sort positions
		for i := 0; i < len(erasePos); i++ {
			for j := i + 1; j < len(erasePos); j++ {
				if erasePos[j] < erasePos[i] {
					erasePos[i], erasePos[j] = erasePos[j], erasePos[i]
				}
			}
		}
		p, ok := RSDecode(recv, erasePos)
		if ok {
			if KeyMatchesPayload(key, p) {
				t.Logf("Decoded with %d erasures: MATCH ✓", nErase)
				decoded = true
				break
			}
		}
	}

	if !decoded {
		t.Errorf("RS decode FAILED")
	}
}

func rmsF64(s []float64) float64 {
	var acc float64
	for _, v := range s {
		acc += v * v
	}
	return math.Sqrt(acc / float64(len(s)))
}
