package scraper

import (
	"context"
	"testing"
	"time"
)

type recordingScraper struct {
	calls chan struct{}
	block bool
}

func newRecordingScraper(block bool) *recordingScraper {
	return &recordingScraper{calls: make(chan struct{}, 20), block: block}
}

func (s *recordingScraper) Scrape(ctx context.Context) error {
	s.calls <- struct{}{}
	if s.block {
		<-ctx.Done()
	}
	return nil
}

func TestManagerOwnsPeriodicScrapeScheduling(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	recorder := newRecordingScraper(false)
	configured, err := NewManager(&ManagerConfig{Interval: 10 * time.Millisecond}, recorder)
	if err != nil {
		t.Fatalf("NewManager() error = %v", err)
	}
	if !configured.NeedLeaderElection() {
		t.Fatal("scraper manager must require leader election")
	}
	done := make(chan error, 1)
	go func() { done <- configured.Start(ctx) }()
	waitForCall(t, recorder.calls)
	waitForCall(t, recorder.calls)
	cancel()
	if err := <-done; err != nil {
		t.Fatalf("Manager.Start() error = %v", err)
	}
}

func TestManagerRunsScrapersIndependently(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	slow := newRecordingScraper(true)
	fast := newRecordingScraper(false)
	configured, err := NewManager(&ManagerConfig{Interval: 10 * time.Millisecond}, slow, fast)
	if err != nil {
		t.Fatalf("NewManager() error = %v", err)
	}
	done := make(chan error, 1)
	go func() { done <- configured.Start(ctx) }()
	waitForCall(t, slow.calls)
	waitForCall(t, fast.calls)
	waitForCall(t, fast.calls)
	select {
	case <-slow.calls:
		t.Fatal("blocked scraper unexpectedly started another scrape")
	case <-time.After(20 * time.Millisecond):
	}
	cancel()
	select {
	case err := <-done:
		if err != nil {
			t.Fatalf("Manager.Start() error = %v", err)
		}
	case <-time.After(time.Second):
		t.Fatal("Manager.Start() did not stop")
	}
}

func TestNewManagerRejectsInvalidConfig(t *testing.T) {
	if _, err := NewManager(nil); err == nil {
		t.Fatal("nil config must be rejected")
	}
	if _, err := NewManager(&ManagerConfig{}); err == nil {
		t.Fatal("non-positive interval must be rejected")
	}
	if _, err := NewManager(&ManagerConfig{Interval: time.Second}, nil); err == nil {
		t.Fatal("nil scraper must be rejected")
	}
}

func waitForCall(t *testing.T, calls <-chan struct{}) {
	t.Helper()
	select {
	case <-calls:
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for Scrape()")
	}
}
