package uisession

import (
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

// TestSubscribeConcurrentWithClearAtomicHandshake mirrors the Select race
// but with Clear: the snapshot either reflects the post-Clear state OR a
// Cleared Change follows. Never lost.
//
// Codex5 P6: the deadlock guard is scoped to the pre-state branch only.
// The post-state branch (snapshot already reflects the racing Clear) uses
// a non-blocking drain because the channel may legitimately be empty — a
// 1s wait there would be a real 1s slowdown, not a deadlock guard.
func TestSubscribeConcurrentWithClearAtomicHandshake(t *testing.T) {
	now := time.Date(2026, 7, 16, 0, 0, 0, 0, time.UTC)
	store := NewStore(Options{Now: func() time.Time { return now }})
	expiresAt := now.Add(time.Hour)

	const sessionID = "session-concurrent-clear"
	_, err := store.Select(sessionID, expiresAt, "server-1")
	require.NoError(t, err)

	barrier := make(chan struct{})
	var sub Subscription
	var subscribeErr error
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		<-barrier
		store.Clear(sessionID)
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		sub, subscribeErr = store.Subscribe(sessionID, expiresAt)
	}()

	close(barrier)
	wg.Wait()

	require.NoError(t, subscribeErr)

	if sub.Snapshot.State == SubscriptionStateCleared && sub.Snapshot.ActiveServerID == "" {
		select {
		case _, ok := <-sub.Changes:
			require.False(t, ok,
				"post-Clear snapshot MUST NOT also emit a racing Change — at-most-once delivery")
		default:
			// Empty channel is the expected post-state outcome.
		}
	} else {
		select {
		case change, ok := <-sub.Changes:
			require.True(t, ok, "snapshot is selected/older: a Cleared Change MUST follow")
			require.Equal(t, ChangeKindCleared, change.Kind)
			require.Equal(t, uint64(2), change.Generation)
		case <-time.After(time.Second):
			t.Fatal("Clear Change lost — atomic handshake violated")
		}
	}

	if sub.Release != nil {
		sub.Release()
	}
}
