package eventbus

import (
	"context"
	"errors"
	"testing"
	"time"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)

func TestRetryingSinkRetriesRawUntilSuccess(t *testing.T) {
	delegate := &flakySink{rawFailures: 1}
	sink := NewRetryingSink(delegate, RetryConfig{Attempts: 3})

	err := sink.PublishRaw(context.Background(), envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808})
	if err != nil {
		t.Fatalf("PublishRaw() error = %v", err)
	}
	if delegate.rawCalls != 2 {
		t.Fatalf("raw calls = %d, want 2", delegate.rawCalls)
	}
}

func TestRetryingSinkReturnsLastErrorAfterAttempts(t *testing.T) {
	delegate := &flakySink{rawFailures: 3}
	sink := NewRetryingSink(delegate, RetryConfig{Attempts: 2})

	err := sink.PublishRaw(context.Background(), envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808})
	if !errors.Is(err, errFlakyPublish) {
		t.Fatalf("PublishRaw() error = %v, want %v", err, errFlakyPublish)
	}
	if delegate.rawCalls != 2 {
		t.Fatalf("raw calls = %d, want 2", delegate.rawCalls)
	}
}

func TestRetryingSinkStopsWhenContextCanceledDuringBackoff(t *testing.T) {
	delegate := &flakySink{rawFailures: 10}
	sink := NewRetryingSink(delegate, RetryConfig{Attempts: 5, Backoff: time.Minute})
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	err := sink.PublishRaw(ctx, envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808})
	if !errors.Is(err, context.Canceled) {
		t.Fatalf("PublishRaw() error = %v, want context canceled", err)
	}
	if delegate.rawCalls != 1 {
		t.Fatalf("raw calls = %d, want 1", delegate.rawCalls)
	}
}

var errFlakyPublish = errors.New("publish failed")

type flakySink struct {
	rawFailures     int
	unifiedFailures int
	rawCalls        int
	unifiedCalls    int
}

func (s *flakySink) PublishRaw(context.Context, envelope.FrameEnvelope) error {
	s.rawCalls++
	if s.rawCalls <= s.rawFailures {
		return errFlakyPublish
	}
	return nil
}

func (s *flakySink) PublishUnified(context.Context, envelope.FrameEnvelope) error {
	s.unifiedCalls++
	if s.unifiedCalls <= s.unifiedFailures {
		return errFlakyPublish
	}
	return nil
}

func (s *flakySink) PublishFields(context.Context, envelope.FrameEnvelope) error {
	return nil
}

func (s *flakySink) Close() error {
	return nil
}
