package streaming

import (
	"context"
	"sync"
	"testing"

	"github.com/nexuscrm/backend/internal/domain/ports"
	domainEvents "github.com/nexuscrm/shared/pkg/events"
)

// mockEventPublisher implements ports.EventPublisher for testing
type mockEventPublisher struct {
	handlers map[domainEvents.EventType][]ports.EventHandler
	mu       sync.Mutex
}

func newMockEventPublisher() *mockEventPublisher {
	return &mockEventPublisher{
		handlers: make(map[domainEvents.EventType][]ports.EventHandler),
	}
}

func (m *mockEventPublisher) Subscribe(eventType domainEvents.EventType, handler ports.EventHandler) func() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.handlers[eventType] = append(m.handlers[eventType], handler)
	return func() {} // Noop unsubscribe
}

func (m *mockEventPublisher) Publish(ctx context.Context, eventType domainEvents.EventType, payload interface{}) error {
	m.mu.Lock()
	handlers := m.handlers[eventType]
	m.mu.Unlock()

	for _, h := range handlers {
		if err := h(ctx, payload); err != nil {
			return err
		}
	}
	return nil
}

func (m *mockEventPublisher) PublishAsyncWithContext(ctx context.Context, eventType domainEvents.EventType, payload interface{}) {
	// For testing, just publish synchronously
	_ = m.Publish(ctx, eventType, payload)
}

// mockBroadcaster records broadcast calls
type mockBroadcaster struct {
	broadcasts []struct {
		channel string
		data    interface{}
	}
	mu sync.Mutex
}

func (m *mockBroadcaster) Broadcast(channel string, data interface{}) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.broadcasts = append(m.broadcasts, struct {
		channel string
		data    interface{}
	}{channel, data})
}

func TestEventBridge_Start(t *testing.T) {
	pub := newMockEventPublisher()
	bc := &mockBroadcaster{}

	bridge := NewEventBridge(pub, bc)
	bridge.Start()

	// Should have subscribed to 12 event types
	if len(pub.handlers) != 12 {
		t.Errorf("Expected 12 subscriptions, got %d", len(pub.handlers))
	}
}

func TestEventBridge_BridgesEvents(t *testing.T) {
	pub := newMockEventPublisher()
	bc := &mockBroadcaster{}

	bridge := NewEventBridge(pub, bc)
	bridge.Start()

	// Publish an event
	ctx := context.Background()
	pub.Publish(ctx, domainEvents.RecordCreated, map[string]string{"id": "123"})

	bc.mu.Lock()
	count := len(bc.broadcasts)
	bc.mu.Unlock()

	if count != 1 {
		t.Errorf("Expected 1 broadcast, got %d", count)
	}
}

func TestEventBridge_Stop(t *testing.T) {
	pub := newMockEventPublisher()
	bc := &mockBroadcaster{}

	bridge := NewEventBridge(pub, bc)
	bridge.Start()
	bridge.Stop()

	// Should not panic
	if bridge.unsubFuncs != nil {
		t.Error("Expected unsubFuncs to be nil after Stop")
	}
}

func TestEventBridge_ConcurrentPublish(t *testing.T) {
	pub := newMockEventPublisher()
	bc := &mockBroadcaster{}

	bridge := NewEventBridge(pub, bc)
	bridge.Start()

	// Publish multiple events concurrently
	ctx := context.Background()
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			pub.Publish(ctx, domainEvents.RecordCreated, map[string]int{"idx": idx})
		}(i)
	}
	wg.Wait()

	bc.mu.Lock()
	count := len(bc.broadcasts)
	bc.mu.Unlock()

	if count != 10 {
		t.Errorf("Expected 10 broadcasts, got %d", count)
	}
}

func TestEventBridge_MultipleEventTypes(t *testing.T) {
	pub := newMockEventPublisher()
	bc := &mockBroadcaster{}

	bridge := NewEventBridge(pub, bc)
	bridge.Start()

	ctx := context.Background()
	// Publish different event types
	pub.Publish(ctx, domainEvents.RecordCreated, map[string]string{"action": "create"})
	pub.Publish(ctx, domainEvents.RecordUpdated, map[string]string{"action": "update"})
	pub.Publish(ctx, domainEvents.RecordDeleted, map[string]string{"action": "delete"})

	bc.mu.Lock()
	count := len(bc.broadcasts)
	bc.mu.Unlock()

	if count != 3 {
		t.Errorf("Expected 3 broadcasts for different event types, got %d", count)
	}
}

func TestEventBridge_BroadcastChannel(t *testing.T) {
	pub := newMockEventPublisher()
	bc := &mockBroadcaster{}

	bridge := NewEventBridge(pub, bc)
	bridge.Start()

	ctx := context.Background()
	pub.Publish(ctx, domainEvents.RecordCreated, map[string]string{"id": "test-123"})

	bc.mu.Lock()
	defer bc.mu.Unlock()

	if len(bc.broadcasts) != 1 {
		t.Fatalf("Expected 1 broadcast, got %d", len(bc.broadcasts))
	}

	// Verify the broadcast was sent to a channel
	if bc.broadcasts[0].channel == "" {
		t.Error("Expected broadcast to have a non-empty channel")
	}
}
