package stub_test

import (
	"context"
	"testing"
	"time"

	"github.com/uopi/uopi/internal/datasource"
	"github.com/uopi/uopi/internal/datasource/stub"
)

func newStub(t *testing.T) *stub.Stub {
	t.Helper()
	s := stub.New()
	if err := s.Connect(context.Background()); err != nil {
		t.Fatalf("Connect: %v", err)
	}
	return s
}

func TestName(t *testing.T) {
	if stub.New().Name() != "stub" {
		t.Error("Name() should return 'stub'")
	}
}

func TestListSignals(t *testing.T) {
	s := newStub(t)
	sigs, err := s.ListSignals(context.Background())
	if err != nil {
		t.Fatalf("ListSignals: %v", err)
	}
	if len(sigs) == 0 {
		t.Fatal("expected at least one signal")
	}
	names := make(map[string]bool)
	for _, m := range sigs {
		names[m.Name] = true
	}
	for _, want := range []string{"sine_1hz", "ramp_10s", "toggle_1hz", "noise", "setpoint"} {
		if !names[want] {
			t.Errorf("signal %q not in ListSignals", want)
		}
	}
}

func TestGetMetadata(t *testing.T) {
	s := newStub(t)
	m, err := s.GetMetadata(context.Background(), "sine_1hz")
	if err != nil {
		t.Fatalf("GetMetadata: %v", err)
	}
	if m.Type != datasource.TypeFloat64 {
		t.Errorf("Type = %v, want TypeFloat64", m.Type)
	}
	if m.Unit == "" {
		t.Error("expected non-empty Unit")
	}
}

func TestGetMetadataNotFound(t *testing.T) {
	s := newStub(t)
	_, err := s.GetMetadata(context.Background(), "no:such:pv")
	if err != datasource.ErrNotFound {
		t.Errorf("want ErrNotFound, got %v", err)
	}
}

func TestSubscribeReceivesValues(t *testing.T) {
	s := newStub(t)
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	ch := make(chan datasource.Value, 4)
	stop, err := s.Subscribe(ctx, "sine_1hz", ch)
	if err != nil {
		t.Fatalf("Subscribe: %v", err)
	}
	defer stop()

	select {
	case v := <-ch:
		if v.Quality != datasource.QualityGood {
			t.Errorf("Quality = %v, want Good", v.Quality)
		}
		if _, ok := v.Data.(float64); !ok {
			t.Errorf("Data type = %T, want float64", v.Data)
		}
		if v.Timestamp.IsZero() {
			t.Error("Timestamp is zero")
		}
	case <-ctx.Done():
		t.Fatal("timeout waiting for first value")
	}
}

func TestSubscribeUnknown(t *testing.T) {
	s := newStub(t)
	_, err := s.Subscribe(context.Background(), "no:such", make(chan datasource.Value, 1))
	if err != datasource.ErrNotFound {
		t.Errorf("want ErrNotFound, got %v", err)
	}
}

func TestSubscribeCancel(t *testing.T) {
	s := newStub(t)
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	ch := make(chan datasource.Value, 4)
	stop, err := s.Subscribe(ctx, "ramp_10s", ch)
	if err != nil {
		t.Fatalf("Subscribe: %v", err)
	}

	// Drain one value to confirm it's running.
	select {
	case <-ch:
	case <-ctx.Done():
		t.Fatal("timeout waiting for first value")
	}

	stop() // cancel subscription

	// Drain remaining buffered values then confirm channel goes quiet.
	time.Sleep(200 * time.Millisecond)
	for len(ch) > 0 {
		<-ch
	}
	time.Sleep(300 * time.Millisecond)
	if len(ch) > 0 {
		t.Error("channel still receiving after stop()")
	}
}

func TestWriteSetpoint(t *testing.T) {
	s := newStub(t)
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	if err := s.Write(context.Background(), "setpoint", float64(99.0)); err != nil {
		t.Fatalf("Write: %v", err)
	}

	ch := make(chan datasource.Value, 4)
	stop, err := s.Subscribe(ctx, "setpoint", ch)
	if err != nil {
		t.Fatalf("Subscribe: %v", err)
	}
	defer stop()

	select {
	case v := <-ch:
		f, ok := v.Data.(float64)
		if !ok {
			t.Fatalf("Data type = %T, want float64", v.Data)
		}
		if f != 99.0 {
			t.Errorf("setpoint value = %g, want 99.0", f)
		}
	case <-ctx.Done():
		t.Fatal("timeout waiting for setpoint value")
	}
}

func TestWriteReadOnly(t *testing.T) {
	s := newStub(t)
	err := s.Write(context.Background(), "sine_1hz", float64(0))
	if err != datasource.ErrNotWritable {
		t.Errorf("want ErrNotWritable, got %v", err)
	}
}

func TestWriteNotFound(t *testing.T) {
	s := newStub(t)
	err := s.Write(context.Background(), "no:such", float64(0))
	if err != datasource.ErrNotFound {
		t.Errorf("want ErrNotFound, got %v", err)
	}
}

func TestHistory(t *testing.T) {
	s := newStub(t)
	_, err := s.History(context.Background(), "sine_1hz", time.Now().Add(-time.Hour), time.Now(), 100)
	if err != datasource.ErrHistoryUnavailable {
		t.Errorf("want ErrHistoryUnavailable, got %v", err)
	}
}

func TestToggleBool(t *testing.T) {
	s := newStub(t)
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	ch := make(chan datasource.Value, 4)
	stop, err := s.Subscribe(ctx, "toggle_1hz", ch)
	if err != nil {
		t.Fatalf("Subscribe: %v", err)
	}
	defer stop()

	select {
	case v := <-ch:
		if _, ok := v.Data.(bool); !ok {
			t.Errorf("toggle_1hz Data type = %T, want bool", v.Data)
		}
	case <-ctx.Done():
		t.Fatal("timeout")
	}
}
