package player

import (
	"context"
	"log/slog"
	"testing"
	"time"

	"github.com/wailsapp/wails/v3/pkg/application"

	"yellowjacket/backend/events"
)

// recordedPlayer is a player with an event sink installed and no audio
// device: enough to assert on what the frontend would receive from the
// paths that do not touch the speaker.
func recordedPlayer(t *testing.T) (*Player, *events.Recorder) {
	t.Helper()

	p := NewPlayer(slog.Default(), nil)
	rec := events.NewRecorder()
	_ = p.ServiceStartup(events.WithSink(t.Context(), rec), application.ServiceOptions{})

	return p, rec
}

func TestSeek_WithNoTrackEmitsSeekFailed(t *testing.T) {
	t.Parallel()

	p, rec := recordedPlayer(t)

	if err := p.Seek(5); err == nil {
		t.Fatal("Seek with no track loaded returned nil error")
	}

	// C2: the frontend has made an optimistic move it now has to take
	// back, and this is the only thing that tells it so.
	if _, ok := rec.Last(events.SeekFailed); !ok {
		t.Errorf("no SeekFailed emitted; got %v", rec.Names())
	}
}

func TestPositionTicker_SilentWhileNotPlaying(t *testing.T) {
	t.Parallel()

	_, rec := recordedPlayer(t)

	// The ticker is running (SetContext started it) but nothing is
	// playing, so a paused app must not push a position a second
	// forever.
	time.Sleep(positionTickInterval * 2)

	if got := rec.Count(events.PlaybackPositionChanged); got != 0 {
		t.Errorf("position emitted %d times while stopped, want 0", got)
	}
}

func TestEmitPosition_CarriesLengthAndSequence(t *testing.T) {
	t.Parallel()

	p := NewPlayer(slog.Default(), nil)
	rec := events.NewRecorder()
	p.ctx = events.WithSink(context.Background(), rec)

	p.mu.Lock()
	p.emitPositionLocked()
	p.emitPositionLocked()
	p.mu.Unlock()

	ticks := rec.Named(events.PlaybackPositionChanged)
	if len(ticks) != 2 {
		t.Fatalf("emitted %d positions, want 2", len(ticks))
	}

	first, ok := ticks[0].Payload().(PositionInfo)
	if !ok {
		t.Fatalf("payload is %T, want player.PositionInfo", ticks[0].Payload())
	}

	second, _ := ticks[1].Payload().(PositionInfo)

	// The sequence is what lets the seek bar reset its interpolation
	// on a tick that reports the same second twice.
	if second.Seq <= first.Seq {
		t.Errorf("seq did not advance: %d then %d", first.Seq, second.Seq)
	}
}
