package integration

import (
	"context"
	"sync"
	"testing"
	"time"

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
)

type blockingOutboxPublisher struct {
	entered chan struct{}
	release chan struct{}

	mu          sync.Mutex
	inFlight    int
	maxInFlight int
	count       int
}

func newBlockingOutboxPublisher() *blockingOutboxPublisher {
	return &blockingOutboxPublisher{
		entered: make(chan struct{}, 2),
		release: make(chan struct{}),
	}
}

func (p *blockingOutboxPublisher) PublishOutboxEvent(ctx context.Context, _ *roomeventsv1.EventEnvelope) error {
	p.mu.Lock()
	p.inFlight++
	if p.inFlight > p.maxInFlight {
		p.maxInFlight = p.inFlight
	}
	p.count++
	p.mu.Unlock()

	p.entered <- struct{}{}
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-p.release:
	}

	p.mu.Lock()
	p.inFlight--
	p.mu.Unlock()
	return nil
}

func (p *blockingOutboxPublisher) snapshot() (int, int) {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.count, p.maxInFlight
}

func TestShardedOutboxPublisherSerializesSameRoom(t *testing.T) {
	inner := newBlockingOutboxPublisher()
	publisher := NewShardedOutboxPublisher(inner, 8)
	envelope := &roomeventsv1.EventEnvelope{RoomId: "room-same"}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// 两个同房间事件同时进入时，分片锁必须让第二条等第一条完成后再调用真实 IM 发布器。
	errs := make(chan error, 2)
	go func() { errs <- publisher.PublishOutboxEvent(ctx, envelope) }()
	<-inner.entered
	go func() { errs <- publisher.PublishOutboxEvent(ctx, envelope) }()

	select {
	case <-inner.entered:
		t.Fatal("same room event entered inner publisher before the first event finished")
	case <-time.After(50 * time.Millisecond):
	}

	inner.release <- struct{}{}
	if err := <-errs; err != nil {
		t.Fatalf("first publish failed: %v", err)
	}
	<-inner.entered
	inner.release <- struct{}{}
	if err := <-errs; err != nil {
		t.Fatalf("second publish failed: %v", err)
	}

	if count, maxInFlight := inner.snapshot(); count != 2 || maxInFlight != 1 {
		t.Fatalf("same room publishes must be serialized, count=%d max_in_flight=%d", count, maxInFlight)
	}
}
