// cmd/wmdump — generates a composite WAV with watermark for offline verification.
//
// Usage:
//
//	wmdump --key FMRTX-XXX --config config.json --output composite.wav --duration 60s
//	wmdecode composite.wav FMRTX-XXX
//
// If wmdecode succeeds on the composite.wav, the watermark code is working.
// If it fails on an air recording, the issue is in the PlutoSDR/air/receiver path.
package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"math"
	"os"
	"time"

	cfgpkg "github.com/jan/fm-rds-tx/internal/config"
	"github.com/jan/fm-rds-tx/internal/license"
	offpkg "github.com/jan/fm-rds-tx/internal/offline"
	"github.com/jan/fm-rds-tx/internal/watermark"
)

func main() {
	configPath := flag.String("config", "", "path to JSON config (uses same as fmrtx)")
	key := flag.String("key", "free", "license key to embed")
	output := flag.String("output", "wmdump.wav", "output WAV path")
	duration := flag.Duration("duration", 60*time.Second, "generation duration")
	rate := flag.Int("rate", 192000, "output WAV sample rate (resampled from composite)")
	flag.Parse()

	cfg, err := cfgpkg.Load(*configPath)
	if err != nil {
		log.Fatalf("load config: %v", err)
	}
	// Match real TX: split-rate mode means FMModulationEnabled=false
	cfg.FM.FMModulationEnabled = false

	gen := offpkg.NewGenerator(cfg)
	licState := license.NewState(*key)
	gen.SetLicense(licState, *key)

	fmt.Printf("Generating composite with watermark...\n")
	fmt.Printf("  Key:       %s (licensed=%v)\n", *key, licState.Licensed())
	fmt.Printf("  Config:    %s\n", *configPath)
	fmt.Printf("  Duration:  %s\n", *duration)
	fmt.Printf("  Composite: %d Hz\n", cfg.FM.CompositeRateHz)
	fmt.Printf("  Output:    %s @ %d Hz\n", *output, *rate)
	fmt.Printf("  ChipRate:  %d Hz (PN bandwidth 0-%d Hz)\n", watermark.ChipRate, watermark.ChipRate/2)
	fmt.Println()

	frame := gen.GenerateFrame(*duration)
	if frame == nil {
		log.Fatal("GenerateFrame returned nil")
	}
	fmt.Printf("Generated %d composite samples @ %.0f Hz\n", len(frame.Samples), frame.SampleRateHz)

	// Extract composite (I channel in non-FM mode)
	compRate := frame.SampleRateHz
	nComp := len(frame.Samples)

	// RMS check
	var rmsAcc float64
	for _, s := range frame.Samples {
		rmsAcc += float64(s.I) * float64(s.I)
	}
	compRMS := math.Sqrt(rmsAcc / float64(nComp))
	fmt.Printf("Composite RMS: %.1f dBFS\n", 20*math.Log10(compRMS+1e-12))

	// Resample composite to output rate (linear interpolation)
	outRate := float64(*rate)
	ratio := outRate / compRate
	nOut := int(float64(nComp) * ratio)
	samples := make([]float64, nOut)
	for i := range samples {
		pos := float64(i) / ratio
		idx := int(pos)
		frac := pos - float64(idx)
		if idx+1 < nComp {
			samples[i] = float64(frame.Samples[idx].I)*(1-frac) + float64(frame.Samples[idx+1].I)*frac
		} else if idx < nComp {
			samples[i] = float64(frame.Samples[idx].I)
		}
	}

	// RMS after resample
	var rms2 float64
	for _, s := range samples {
		rms2 += s * s
	}
	outRMS := math.Sqrt(rms2 / float64(nOut))
	fmt.Printf("Output RMS:    %.1f dBFS (%d samples @ %.0f Hz)\n", 20*math.Log10(outRMS+1e-12), nOut, outRate)

	// Write WAV
	if err := writeWAV(*output, samples, *rate); err != nil {
		log.Fatalf("write WAV: %v", err)
	}
	fmt.Printf("\nWritten: %s\n", *output)
	fmt.Printf("\nDecode with:\n")
	fmt.Printf("  .\\wmdecode.exe %s %q\n", *output, *key)
}

func writeWAV(path string, samples []float64, rate int) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	le := binary.LittleEndian
	dataSz := uint32(len(samples) * 2)
	f.Write([]byte("RIFF"))
	binary.Write(f, le, 36+dataSz)
	f.Write([]byte("WAVE"))
	f.Write([]byte("fmt "))
	binary.Write(f, le, uint32(16))
	binary.Write(f, le, uint16(1))
	binary.Write(f, le, uint16(1))
	binary.Write(f, le, uint32(rate))
	binary.Write(f, le, uint32(rate*2))
	binary.Write(f, le, uint16(2))
	binary.Write(f, le, uint16(16))
	f.Write([]byte("data"))
	binary.Write(f, le, dataSz)
	for _, s := range samples {
		v := s * 32767.0
		if v > 32767 {
			v = 32767
		}
		if v < -32768 {
			v = -32768
		}
		binary.Write(f, le, int16(v))
	}
	return nil
}
