package output

import (
	"context"
	"encoding/binary"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"sync"
)

// FileBackend streams composite samples to disk so that playback or offline tooling can consume them.
type FileBackend struct {
	mu     sync.Mutex
	file   *os.File
	order  binary.ByteOrder
	info   BackendInfo
	cfg    BackendConfig
	closed bool
	wbuf   []byte // reusable write buffer
}

// NewFileBackend creates a writer that appends float32 interleaved I/Q pairs to the named file.
func NewFileBackend(path string, order binary.ByteOrder, info BackendInfo) (*FileBackend, error) {
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return nil, fmt.Errorf("create output dir: %w", err)
	}
	f, err := os.Create(path)
	if err != nil {
		return nil, fmt.Errorf("open output file: %w", err)
	}
	if info.Name == "" {
		info.Name = path
	}
	if info.Capabilities.MaxSamplesPerWrite == 0 {
		info.Capabilities.MaxSamplesPerWrite = 4096
	}
	info.Capabilities.SupportsComposite = true
	info.Capabilities.FixedRate = true

	return &FileBackend{
		file:  f,
		order: order,
		info:  info,
	}, nil
}

// Configure stores the requested configuration, but the file backend simply preserves the values.
func (fb *FileBackend) Configure(_ context.Context, cfg BackendConfig) error {
	fb.mu.Lock()
	defer fb.mu.Unlock()
	if fb.closed {
		return ErrBackendClosed
	}
	fb.cfg = cfg
	return nil
}

// Write emits the provided frame as binary interleaved float32 I/Q samples.
// Serializes the entire frame into a byte buffer and writes in one syscall.
func (fb *FileBackend) Write(ctx context.Context, frame *CompositeFrame) (int, error) {
	if err := ctx.Err(); err != nil {
		return 0, err
	}
	fb.mu.Lock()
	defer fb.mu.Unlock()
	if fb.closed {
		return 0, ErrBackendClosed
	}
	if frame == nil || len(frame.Samples) == 0 {
		return 0, nil
	}

	// Grow write buffer if needed, reuse across calls
	need := len(frame.Samples) * 8
	if cap(fb.wbuf) < need {
		fb.wbuf = make([]byte, need)
	}
	buf := fb.wbuf[:need]

	for i, sample := range frame.Samples {
		off := i * 8
		fb.order.PutUint32(buf[off:], math.Float32bits(sample.I))
		fb.order.PutUint32(buf[off+4:], math.Float32bits(sample.Q))
	}

	if _, err := fb.file.Write(buf); err != nil {
		return 0, fmt.Errorf("write sample data: %w", err)
	}
	return len(frame.Samples), nil
}

// Flush commits the current file buffer to disk.
func (fb *FileBackend) Flush(_ context.Context) error {
	fb.mu.Lock()
	defer fb.mu.Unlock()
	if fb.closed {
		return ErrBackendClosed
	}
	return fb.file.Sync()
}

// Close finalizes the file handle.
func (fb *FileBackend) Close(_ context.Context) error {
	fb.mu.Lock()
	defer fb.mu.Unlock()
	if fb.closed {
		return ErrBackendClosed
	}
	fb.closed = true
	return fb.file.Close()
}

// Info returns the backend metadata.
func (fb *FileBackend) Info() BackendInfo {
	fb.mu.Lock()
	defer fb.mu.Unlock()
	return fb.info
}
