package httpraw

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

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

type Source struct {
	id         string
	sampleRate int
	channels   int

	chunks chan ingest.PCMChunk
	errs   chan error

	sequence        atomic.Uint64
	state           atomic.Value // string
	chunksIn        atomic.Uint64
	samplesIn       atomic.Uint64
	discontinuities atomic.Uint64
	lastChunkAtUnix atomic.Int64
	lastError       atomic.Value // string
}

func New(id string, sampleRate, channels int) *Source {
	if id == "" {
		id = "http-raw"
	}
	if sampleRate <= 0 {
		sampleRate = 44100
	}
	if channels <= 0 {
		channels = 2
	}
	s := &Source{
		id:         id,
		sampleRate: sampleRate,
		channels:   channels,
		chunks:     make(chan ingest.PCMChunk, 32),
		errs:       make(chan error, 8),
	}
	s.state.Store("idle")
	return s
}

func (s *Source) Descriptor() ingest.SourceDescriptor {
	return ingest.SourceDescriptor{
		ID:           s.id,
		Kind:         "http-raw",
		Family:       "raw",
		Transport:    "http",
		Codec:        "pcm_s16le",
		Channels:     s.channels,
		SampleRateHz: s.sampleRate,
		Detail:       "HTTP push /audio/stream",
	}
}

func (s *Source) Start(_ context.Context) error {
	s.state.Store("running")
	return nil
}

func (s *Source) Stop() error {
	s.state.Store("stopped")
	return nil
}

func (s *Source) Chunks() <-chan ingest.PCMChunk { return s.chunks }
func (s *Source) Errors() <-chan error           { return s.errs }

func (s *Source) Stats() ingest.SourceStats {
	state, _ := s.state.Load().(string)
	last := s.lastChunkAtUnix.Load()
	errStr, _ := s.lastError.Load().(string)
	var lastChunkAt time.Time
	if last > 0 {
		lastChunkAt = time.Unix(0, last)
	}
	return ingest.SourceStats{
		State:           state,
		Connected:       state == "running",
		LastChunkAt:     lastChunkAt,
		ChunksIn:        s.chunksIn.Load(),
		SamplesIn:       s.samplesIn.Load(),
		Discontinuities: s.discontinuities.Load(),
		LastError:       errStr,
	}
}

func (s *Source) WritePCM16(data []byte) (int, error) {
	if s.channels != 1 && s.channels != 2 {
		return 0, fmt.Errorf("unsupported configured channels: %d", s.channels)
	}
	if len(data) == 0 {
		return 0, nil
	}
	frameBytes := s.channels * 2
	usable := len(data) - (len(data) % frameBytes)
	if usable == 0 {
		return 0, nil
	}
	samples := make([]int32, 0, usable/2)
	for i := 0; i+1 < usable; i += 2 {
		v := int16(binary.LittleEndian.Uint16(data[i : i+2]))
		samples = append(samples, int32(v)<<16)
	}
	seq := s.sequence.Add(1) - 1
	chunk := ingest.PCMChunk{
		Samples:      samples,
		Channels:     s.channels,
		SampleRateHz: s.sampleRate,
		Sequence:     seq,
		Timestamp:    time.Now(),
		SourceID:     s.id,
	}
	select {
	case s.chunks <- chunk:
	default:
		s.discontinuities.Add(1)
		return 0, fmt.Errorf("http raw ingress overflow")
	}
	frames := usable / frameBytes
	s.chunksIn.Add(1)
	s.samplesIn.Add(uint64(len(samples)))
	s.lastChunkAtUnix.Store(time.Now().UnixNano())
	return frames, nil
}
