package acp_test

import (
	"context"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/acp"
)

func TestTurnBus_FanoutToMultipleSubscribers(t *testing.T) {
	t.Parallel()
	bus := acp.NewTurnBus(nil)

	var wg sync.WaitGroup
	wg.Add(2)
	var a, b atomic.Int32
	bus.Subscribe("a", func(_ context.Context, _ acp.TurnEvent) { a.Add(1); wg.Done() })
	bus.Subscribe("b", func(_ context.Context, _ acp.TurnEvent) { b.Add(1); wg.Done() })

	bus.Publish(context.Background(), acp.TurnEvent{SessionID: uuid.New(), Kind: acp.TurnCompletedEvent})

	done := make(chan struct{})
	go func() { wg.Wait(); close(done) }()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("subscribers not invoked in time")
	}
	assert.Equal(t, int32(1), a.Load())
	assert.Equal(t, int32(1), b.Load())
}

func TestTurnBus_PanicIsolatedAndNonBlocking(t *testing.T) {
	t.Parallel()
	bus := acp.NewTurnBus(nil)

	var good atomic.Int32
	var wg sync.WaitGroup
	wg.Add(1)
	bus.Subscribe("panicky", func(_ context.Context, _ acp.TurnEvent) { panic("boom") })
	bus.Subscribe("good", func(_ context.Context, _ acp.TurnEvent) { good.Add(1); wg.Done() })

	// Publish 必须立即返回（非阻塞），且 panicky 订阅者的 panic 被 recover 隔离，
	// 不影响 good 订阅者执行。
	bus.Publish(context.Background(), acp.TurnEvent{Kind: acp.SessionIdleEvent})

	done := make(chan struct{})
	go func() { wg.Wait(); close(done) }()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("good subscriber not invoked despite panicky peer")
	}
	assert.Equal(t, int32(1), good.Load())
}

func TestTurnBus_PublishDoesNotBlockOnSlowSubscriber(t *testing.T) {
	t.Parallel()
	bus := acp.NewTurnBus(nil)
	release := make(chan struct{})
	bus.Subscribe("slow", func(_ context.Context, _ acp.TurnEvent) { <-release })

	start := time.Now()
	bus.Publish(context.Background(), acp.TurnEvent{Kind: acp.SessionIdleEvent})
	// Publish 应远快于订阅者的阻塞时长。
	require.Less(t, time.Since(start), 500*time.Millisecond, "Publish must not block on slow subscriber")
	close(release)
}
