package discovery

import (
	"sync"
	"testing"
	"time"

	me "github.com/tech/sendico/pkg/messaging/envelope"
	"go.uber.org/zap"
)

type recordingProducer struct {
	mu     sync.Mutex
	counts map[string]int
}

func newRecordingProducer() *recordingProducer {
	return &recordingProducer{
		counts: make(map[string]int),
	}
}

func (p *recordingProducer) SendMessage(env me.Envelope) error {
	if p == nil || env == nil {
		return nil
	}
	sig := env.GetSignature()
	if sig == nil {
		return nil
	}

	p.mu.Lock()
	p.counts[sig.ToString()]++
	p.mu.Unlock()
	return nil
}

func (p *recordingProducer) count(event string) int {
	if p == nil {
		return 0
	}
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.counts[event]
}

func waitForAtLeast(t *testing.T, timeout time.Duration, countFn func() int, want int, label string) {
	t.Helper()

	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if countFn() >= want {
			return
		}
		time.Sleep(20 * time.Millisecond)
	}
	t.Fatalf("timed out waiting for %s: got=%d want_at_least=%d", label, countFn(), want)
}

func TestHeartbeatIntervalForHealth_Default(t *testing.T) {
	got := heartbeatIntervalForHealth(HealthParams{})
	want := time.Duration(DefaultHealthIntervalSec) * time.Second
	if got != want {
		t.Fatalf("unexpected heartbeat interval: got=%s want=%s", got, want)
	}
}

func TestReannounceIntervalForHealth_Default(t *testing.T) {
	got := heartbeatIntervalForHealth(HealthParams{}) * defaultReannounceHeartbeatFactor
	want := time.Duration(DefaultHealthIntervalSec*defaultReannounceHeartbeatFactor) * time.Second
	if got != want {
		t.Fatalf("unexpected reannounce interval: got=%s want=%s", got, want)
	}
}

func TestAnnouncerPeriodicReannounce(t *testing.T) {
	producer := newRecordingProducer()
	announce := Announcement{
		Service:    "FX_INGESTOR",
		Operations: []string{OperationFXIngest},
		Health: HealthParams{
			IntervalSec: 1,
			TimeoutSec:  2,
		},
	}

	announcer := NewAnnouncer(zap.NewNop(), producer, "fx_ingestor", announce)
	announcer.Start()
	defer announcer.Stop()

	announceEvent := ServiceAnnounceEvent().ToString()
	heartbeatEvent := HeartbeatEvent().ToString()

	waitForAtLeast(t, 1*time.Second, func() int { return producer.count(announceEvent) }, 1, "initial announce")
	waitForAtLeast(t, 1*time.Second, func() int { return producer.count(heartbeatEvent) }, 1, "initial heartbeat")

	// With 1s heartbeat and factor=N, periodic re-announce should happen in ~N seconds.
	timeout := time.Duration(defaultReannounceHeartbeatFactor+2) * time.Second
	waitForAtLeast(t, timeout, func() int { return producer.count(announceEvent) }, 2, "periodic re-announce")
}
