package platform

import (
	"context"
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"github.com/jan/fm-rds-tx/internal/output"
)

// -----------------------------------------------------------------------
// Device capabilities and runtime stats
// -----------------------------------------------------------------------

// DeviceCaps describes what a hardware device supports.
type DeviceCaps struct {
	MinSampleRate        float64
	MaxSampleRate        float64
	SupportedSampleRates []float64
	HasGain              bool
	GainMinDB            float64
	GainMaxDB            float64
	Channels             []int
}

// RuntimeStats exposes live telemetry from the backend.
type RuntimeStats struct {
	TXEnabled      bool    `json:"txEnabled"`
	StreamActive   bool    `json:"streamActive"`
	FramesWritten  uint64  `json:"framesWritten"`
	SamplesWritten uint64  `json:"samplesWritten"`
	Underruns      uint64  `json:"underruns"`
	Overruns       uint64  `json:"overruns"`
	SlowWrites     uint64  `json:"slowWrites,omitempty"`
	SlowFills      uint64  `json:"slowFills,omitempty"`
	SlowPushes     uint64  `json:"slowPushes,omitempty"`
	LastError      string  `json:"lastError,omitempty"`
	LastErrorAt    string  `json:"lastErrorAt,omitempty"`
	EffectiveRate  float64 `json:"effectiveSampleRateHz"`
	MaxWriteMs     float64 `json:"maxWriteMs,omitempty"`
	MaxFillMs      float64 `json:"maxFillMs,omitempty"`
	MaxPushMs      float64 `json:"maxPushMs,omitempty"`
}

// -----------------------------------------------------------------------
// SoapyConfig
// -----------------------------------------------------------------------

type SoapyConfig struct {
	output.BackendConfig
	Driver         string
	Device         string
	CenterFreqHz   float64
	GainDB         float64
	Channels       []int
	DeviceArgs     map[string]string
	Simulated      bool
	SimulationPath string
}

// -----------------------------------------------------------------------
// SoapyDriver interface — extended for real HW
// -----------------------------------------------------------------------

type SoapyDriver interface {
	Name() string
	Configure(ctx context.Context, cfg SoapyConfig) error
	Capabilities(ctx context.Context) (DeviceCaps, error)
	Start(ctx context.Context) error
	Write(ctx context.Context, frame *output.CompositeFrame) (int, error)
	Stop(ctx context.Context) error
	Flush(ctx context.Context) error
	Close(ctx context.Context) error
	Stats() RuntimeStats
	// Tune changes the TX center frequency while streaming. Thread-safe.
	Tune(ctx context.Context, freqHz float64) error
}

// -----------------------------------------------------------------------
// SoapyBackend wraps driver and exposes output.Backend
// -----------------------------------------------------------------------

type SoapyBackend struct {
	mu     sync.Mutex
	driver SoapyDriver
	cfg    SoapyConfig
	info   output.BackendInfo
}

func NewSoapyBackend(cfg SoapyConfig, driver SoapyDriver) *SoapyBackend {
	if driver == nil {
		driver = NewSimulatedDriver(nil)
	}
	info := output.BackendInfo{
		Name:        fmt.Sprintf("soapy/%s", cfg.Driver),
		Description: "SoapySDR-friendly backend",
		Capabilities: output.BackendCapabilities{
			SupportsComposite:  true,
			FixedRate:          cfg.SampleRateHz > 0,
			MaxSamplesPerWrite: 8192,
		},
	}
	return &SoapyBackend{driver: driver, cfg: cfg, info: info}
}

func (sb *SoapyBackend) Configure(ctx context.Context, cfg output.BackendConfig) error {
	sb.mu.Lock()
	sb.cfg.BackendConfig = cfg
	sb.mu.Unlock()
	return sb.driver.Configure(ctx, sb.cfg)
}

func (sb *SoapyBackend) Write(ctx context.Context, frame *output.CompositeFrame) (int, error) {
	return sb.driver.Write(ctx, frame)
}

func (sb *SoapyBackend) Flush(ctx context.Context) error {
	return sb.driver.Flush(ctx)
}

func (sb *SoapyBackend) Close(ctx context.Context) error {
	return sb.driver.Close(ctx)
}

func (sb *SoapyBackend) Info() output.BackendInfo {
	sb.mu.Lock()
	defer sb.mu.Unlock()
	return sb.info
}

func (sb *SoapyBackend) Driver() SoapyDriver {
	return sb.driver
}

// -----------------------------------------------------------------------
// SimulatedDriver — implements full SoapyDriver interface
// -----------------------------------------------------------------------

type SimulatedDriver struct {
	mu             sync.Mutex
	fallback       output.Backend
	cfg            SoapyConfig
	started        bool
	framesWritten  atomic.Uint64
	samplesWritten atomic.Uint64
	lastError      string
	lastErrorAt    string
}

func NewSimulatedDriver(writer output.Backend) *SimulatedDriver {
	if writer == nil {
		writer = output.NewDummyBackend("simulated-soapy")
	}
	return &SimulatedDriver{fallback: writer}
}

func (sd *SimulatedDriver) Name() string {
	return sd.fallback.Info().Name
}

func (sd *SimulatedDriver) Configure(ctx context.Context, cfg SoapyConfig) error {
	sd.mu.Lock()
	sd.cfg = cfg
	sd.mu.Unlock()
	return sd.fallback.Configure(ctx, cfg.BackendConfig)
}

func (sd *SimulatedDriver) Capabilities(_ context.Context) (DeviceCaps, error) {
	return DeviceCaps{
		MinSampleRate: 48000,
		MaxSampleRate: 2400000,
		HasGain:       true,
		GainMinDB:     0,
		GainMaxDB:     47,
		Channels:      []int{0},
	}, nil
}

func (sd *SimulatedDriver) Start(_ context.Context) error {
	sd.mu.Lock()
	defer sd.mu.Unlock()
	sd.started = true
	return nil
}

func (sd *SimulatedDriver) Write(ctx context.Context, frame *output.CompositeFrame) (int, error) {
	n, err := sd.fallback.Write(ctx, frame)
	if err != nil {
		sd.mu.Lock()
		sd.lastError = err.Error()
		sd.lastErrorAt = time.Now().UTC().Format(time.RFC3339)
		sd.mu.Unlock()
	}
	if n > 0 {
		sd.framesWritten.Add(1)
		sd.samplesWritten.Add(uint64(n))
	}
	return n, err
}

func (sd *SimulatedDriver) Stop(_ context.Context) error {
	sd.mu.Lock()
	defer sd.mu.Unlock()
	sd.started = false
	return nil
}

func (sd *SimulatedDriver) Flush(ctx context.Context) error {
	return sd.fallback.Flush(ctx)
}

func (sd *SimulatedDriver) Close(ctx context.Context) error {
	return sd.fallback.Close(ctx)
}

func (sd *SimulatedDriver) Stats() RuntimeStats {
	sd.mu.Lock()
	defer sd.mu.Unlock()
	return RuntimeStats{
		TXEnabled:      sd.started,
		StreamActive:   sd.started,
		FramesWritten:  sd.framesWritten.Load(),
		SamplesWritten: sd.samplesWritten.Load(),
		LastError:      sd.lastError,
		LastErrorAt:    sd.lastErrorAt,
		EffectiveRate:  sd.cfg.SampleRateHz,
	}
}

func (sd *SimulatedDriver) Tune(_ context.Context, freqHz float64) error {
	sd.mu.Lock()
	sd.cfg.CenterFreqHz = freqHz
	sd.mu.Unlock()
	return nil
}
