package config

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type Config struct {
	Audio   AudioConfig   `json:"audio"`
	RDS     RDSConfig     `json:"rds"`
	FM      FMConfig      `json:"fm"`
	Backend BackendConfig `json:"backend"`
	Control ControlConfig `json:"control"`
}

type AudioConfig struct {
	InputPath     string  `json:"inputPath"`
	Gain          float64 `json:"gain"`
	ToneLeftHz    float64 `json:"toneLeftHz"`
	ToneRightHz   float64 `json:"toneRightHz"`
	ToneAmplitude float64 `json:"toneAmplitude"`
}

type RDSConfig struct {
	Enabled   bool   `json:"enabled"`
	PI        string `json:"pi"`
	PS        string `json:"ps"`
	RadioText string `json:"radioText"`
	PTY       int    `json:"pty"`
}

type FMConfig struct {
	FrequencyMHz        float64 `json:"frequencyMHz"`
	StereoEnabled       bool    `json:"stereoEnabled"`
	PilotLevel          float64 `json:"pilotLevel"`          // fraction of ±75kHz deviation (0.09 = 9%, ITU standard)
	RDSInjection        float64 `json:"rdsInjection"`        // fraction of ±75kHz deviation (0.04 = 4%, typical)
	PreEmphasisTauUS    float64 `json:"preEmphasisTauUS"`    // time constant in µs: 50 (EU) or 75 (US), 0=off
	OutputDrive         float64 `json:"outputDrive"`
	CompositeRateHz     int     `json:"compositeRateHz"`     // internal DSP/MPX sample rate
	MaxDeviationHz      float64 `json:"maxDeviationHz"`
	LimiterEnabled      bool    `json:"limiterEnabled"`
	LimiterCeiling      float64 `json:"limiterCeiling"`
	FMModulationEnabled bool    `json:"fmModulationEnabled"`
	MpxGain             float64 `json:"mpxGain"`             // hardware calibration: scales entire composite output (default 1.0)
	BS412Enabled        bool    `json:"bs412Enabled"`        // ITU-R BS.412 MPX power limiter (EU requirement)
	BS412ThresholdDBr   float64 `json:"bs412ThresholdDBr"`   // power limit in dBr (0 = standard, +3 = relaxed)
}

type BackendConfig struct {
	Kind               string            `json:"kind"`
	Driver             string            `json:"driver,omitempty"`
	Device             string            `json:"device"`
	URI                string            `json:"uri,omitempty"`
	DeviceArgs         map[string]string `json:"deviceArgs,omitempty"`
	OutputPath         string            `json:"outputPath"`
	DeviceSampleRateHz float64           `json:"deviceSampleRateHz"` // actual SDR device rate; 0 = same as compositeRateHz
}

type ControlConfig struct {
	ListenAddress string `json:"listenAddress"`
}

func Default() Config {
	return Config{
		Audio: AudioConfig{Gain: 1.0, ToneLeftHz: 1000, ToneRightHz: 1600, ToneAmplitude: 0.4},
		RDS:   RDSConfig{Enabled: true, PI: "1234", PS: "FMRTX", RadioText: "fm-rds-tx", PTY: 0},
		FM: FMConfig{
			FrequencyMHz:        100.0,
			StereoEnabled:       true,
			PilotLevel:          0.09,
			RDSInjection:        0.04,
			PreEmphasisTauUS:    50,
			OutputDrive:         0.5,
			CompositeRateHz:     228000,
			MaxDeviationHz:      75000,
			LimiterEnabled:      true,
			LimiterCeiling:      1.0,
			FMModulationEnabled: true,
			MpxGain:             1.0,
		},
		Backend: BackendConfig{Kind: "file", OutputPath: "build/out/composite.f32"},
		Control: ControlConfig{ListenAddress: "127.0.0.1:8088"},
	}
}

// ParsePI parses a hex PI code string. Returns an error for invalid input.
func ParsePI(pi string) (uint16, error) {
	trimmed := strings.TrimSpace(pi)
	if trimmed == "" {
		return 0, fmt.Errorf("rds.pi is required")
	}
	trimmed = strings.TrimPrefix(trimmed, "0x")
	trimmed = strings.TrimPrefix(trimmed, "0X")
	v, err := strconv.ParseUint(trimmed, 16, 16)
	if err != nil {
		return 0, fmt.Errorf("invalid rds.pi: %q", pi)
	}
	return uint16(v), nil
}

func Load(path string) (Config, error) {
	cfg := Default()
	if path == "" {
		return cfg, cfg.Validate()
	}
	data, err := os.ReadFile(path)
	if err != nil {
		return Config{}, err
	}
	if err := json.Unmarshal(data, &cfg); err != nil {
		return Config{}, err
	}
	return cfg, cfg.Validate()
}

func (c Config) Validate() error {
	if c.Audio.Gain < 0 || c.Audio.Gain > 4 {
		return fmt.Errorf("audio.gain out of range")
	}
	if c.Audio.ToneLeftHz <= 0 || c.Audio.ToneRightHz <= 0 {
		return fmt.Errorf("audio tone frequencies must be positive")
	}
	if c.Audio.ToneAmplitude < 0 || c.Audio.ToneAmplitude > 1 {
		return fmt.Errorf("audio.toneAmplitude out of range")
	}
	if c.FM.FrequencyMHz < 65 || c.FM.FrequencyMHz > 110 {
		return fmt.Errorf("fm.frequencyMHz out of range")
	}
	if c.FM.PilotLevel < 0 || c.FM.PilotLevel > 0.2 {
		return fmt.Errorf("fm.pilotLevel out of range")
	}
	if c.FM.RDSInjection < 0 || c.FM.RDSInjection > 0.15 {
		return fmt.Errorf("fm.rdsInjection out of range")
	}
	if c.FM.OutputDrive < 0 || c.FM.OutputDrive > 10 {
		return fmt.Errorf("fm.outputDrive out of range (0..3)")
	}
	if c.FM.CompositeRateHz < 96000 || c.FM.CompositeRateHz > 1520000 {
		return fmt.Errorf("fm.compositeRateHz out of range")
	}
	if c.FM.PreEmphasisTauUS < 0 || c.FM.PreEmphasisTauUS > 100 {
		return fmt.Errorf("fm.preEmphasisTauUS out of range (0=off, 50=EU, 75=US)")
	}
	if c.FM.MaxDeviationHz < 0 || c.FM.MaxDeviationHz > 150000 {
		return fmt.Errorf("fm.maxDeviationHz out of range")
	}
	if c.FM.LimiterCeiling < 0 || c.FM.LimiterCeiling > 2 {
		return fmt.Errorf("fm.limiterCeiling out of range")
	}
	if c.FM.MpxGain == 0 { c.FM.MpxGain = 1.0 } // default if omitted from JSON
	if c.FM.MpxGain < 0.1 || c.FM.MpxGain > 5 {
		return fmt.Errorf("fm.mpxGain out of range (0.1..5)")
	}
	if c.Backend.Kind == "" {
		return fmt.Errorf("backend.kind is required")
	}
	if c.Backend.DeviceSampleRateHz < 0 {
		return fmt.Errorf("backend.deviceSampleRateHz must be >= 0")
	}
	if c.Control.ListenAddress == "" {
		return fmt.Errorf("control.listenAddress is required")
	}
	// Fail-loud PI validation
	if c.RDS.Enabled {
		if _, err := ParsePI(c.RDS.PI); err != nil {
			return fmt.Errorf("rds config: %w", err)
		}
	}
	if c.RDS.PTY < 0 || c.RDS.PTY > 31 {
		return fmt.Errorf("rds.pty out of range (0-31)")
	}
	return nil
}

// EffectiveDeviceRate returns the device sample rate, falling back to composite rate.
func (c Config) EffectiveDeviceRate() float64 {
	if c.Backend.DeviceSampleRateHz > 0 {
		return c.Backend.DeviceSampleRateHz
	}
	return float64(c.FM.CompositeRateHz)
}
