package trace_test

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

	"github.com/stretchr/testify/assert"
	"github.com/yaoapp/yao/trace"
	"github.com/yaoapp/yao/trace/types"
)

func TestSubscription(t *testing.T) {
	drivers := trace.GetTestDrivers()

	for _, d := range drivers {
		t.Run(d.Name, func(t *testing.T) {
			ctx := context.Background()

			traceID, manager, err := trace.New(ctx, d.DriverType, nil, d.DriverOptions...)
			assert.NoError(t, err)
			defer trace.Release(traceID)
			defer trace.Remove(ctx, d.DriverType, traceID, d.DriverOptions...)

			// Subscribe to updates
			updates, cancel, err := manager.Subscribe()
			assert.NoError(t, err)
			assert.NotNil(t, updates)
			defer cancel()

			// Collect updates in background
			var receivedUpdates []*types.TraceUpdate
			var updatesMu sync.Mutex
			done := make(chan bool)

			go func() {
				timeout := time.After(2 * time.Second)
				for {
					select {
					case update, ok := <-updates:
						if !ok {
							// Channel closed
							done <- true
							return
						}

						updatesMu.Lock()
						receivedUpdates = append(receivedUpdates, update)
						updatesMu.Unlock()

						// Check for trace completion
						if update.Type == types.UpdateTypeComplete {
							done <- true
							return
						}
					case <-timeout:
						done <- true
						return
					}
				}
			}()

			// Perform operations
			manager.Info("Test operation")
			_, err = manager.Add("test node", types.TraceNodeOption{Label: "Test"})
			assert.NoError(t, err)

			err = manager.Complete(map[string]any{"test": "data"})
			assert.NoError(t, err)

			// Create space and set value
			space, err := manager.CreateSpace(types.TraceSpaceOption{Label: "Test Space"})
			assert.NoError(t, err)

			err = manager.SetSpaceValue(space.ID, "key", "value")
			assert.NoError(t, err)

			// Mark trace complete
			err = manager.MarkComplete()
			assert.NoError(t, err)

			// Wait for completion or timeout
			<-done

			// Verify we received updates
			updatesMu.Lock()
			defer updatesMu.Unlock()

			assert.NotEmpty(t, receivedUpdates)

			// Check for specific event types
			eventTypes := make(map[string]bool)
			for _, update := range receivedUpdates {
				eventTypes[update.Type] = true
			}

			assert.True(t, eventTypes[types.UpdateTypeInit], "Should receive init event")
			assert.True(t, eventTypes[types.UpdateTypeNodeStart], "Should receive node_start event")
			assert.True(t, eventTypes[types.UpdateTypeComplete], "Should receive complete event")
		})
	}
}

func TestSubscribeFrom(t *testing.T) {
	drivers := trace.GetTestDrivers()

	for _, d := range drivers {
		t.Run(d.Name, func(t *testing.T) {
			ctx := context.Background()

			traceID, manager, err := trace.New(ctx, d.DriverType, nil, d.DriverOptions...)
			assert.NoError(t, err)
			defer trace.Release(traceID)
			defer trace.Remove(ctx, d.DriverType, traceID, d.DriverOptions...)

			// Real scenario: User starts a trace, performs some operations
			_, err = manager.Add("Step 1", types.TraceNodeOption{Label: "Processing"})
			assert.NoError(t, err)
			manager.Info("Processing step 1")
			err = manager.Complete("step1 result")
			assert.NoError(t, err)

			// Wait to ensure different timestamp (simulate time passing)
			// Use a longer sleep to account for CI environment variability
			time.Sleep(1100 * time.Millisecond)

			// Record timestamp (simulate user noting current time before refresh)
			// Subtract 1ms to ensure we capture events that happen "now"
			// This accounts for millisecond precision and timing variability in CI
			resumeTimestamp := time.Now().UnixMilli() - 1

			// Wait to ensure next operations have a clearly different timestamp
			// Use longer sleep for CI reliability
			time.Sleep(500 * time.Millisecond)

			// Continue with more operations
			_, err = manager.Add("Step 2", types.TraceNodeOption{Label: "Finalizing"})
			assert.NoError(t, err)
			manager.Info("Processing step 2")
			err = manager.Complete("step2 result")
			assert.NoError(t, err)

			// Mark trace complete
			err = manager.MarkComplete()
			assert.NoError(t, err)

			// Real scenario: User refreshes page and resumes from last known timestamp
			// This should replay events from resumeTimestamp onwards
			updates, cancelSub, err := manager.SubscribeFrom(resumeTimestamp)
			assert.NoError(t, err)
			assert.NotNil(t, updates)
			defer cancelSub()

			// Collect updates
			var receivedUpdates []*types.TraceUpdate
			timeout := time.After(1 * time.Second)
			foundStep2 := false

		collectLoop:
			for {
				select {
				case update, ok := <-updates:
					if !ok {
						// Channel closed
						break collectLoop
					}
					receivedUpdates = append(receivedUpdates, update)
					// Check if we received step 2 events
					if update.Type == types.UpdateTypeNodeStart {
						if data, ok := update.Data.(*types.NodeStartData); ok {
							if data.Node != nil && data.Node.Label == "Finalizing" {
								foundStep2 = true
							}
						}
					}
					// Stop after receiving trace_complete
					if update.Type == types.UpdateTypeComplete {
						break collectLoop
					}
				case <-timeout:
					break collectLoop
				}
			}

			// Verify we received events from step 2 onwards
			assert.NotEmpty(t, receivedUpdates, "Should receive events from resume point")
			assert.True(t, foundStep2, "Should receive Step 2 events")

			// All events should be at or after the resume timestamp
			for _, update := range receivedUpdates {
				assert.GreaterOrEqual(t, update.Timestamp, resumeTimestamp,
					"Event timestamp %d should be >= resume timestamp %d (event type: %s)",
					update.Timestamp, resumeTimestamp, update.Type)
			}
		})
	}
}

func TestIsComplete(t *testing.T) {
	drivers := trace.GetTestDrivers()

	for _, d := range drivers {
		t.Run(d.Name, func(t *testing.T) {
			ctx := context.Background()

			traceID, manager, err := trace.New(ctx, d.DriverType, nil, d.DriverOptions...)
			assert.NoError(t, err)
			defer trace.Release(traceID)
			defer trace.Remove(ctx, d.DriverType, traceID, d.DriverOptions...)

			// Initially not complete
			assert.False(t, manager.IsComplete())

			// Mark complete
			err = manager.MarkComplete()
			assert.NoError(t, err)

			// Now should be complete
			assert.True(t, manager.IsComplete())
		})
	}
}

func TestMultipleSubscribers(t *testing.T) {
	drivers := trace.GetTestDrivers()

	for _, d := range drivers {
		t.Run(d.Name, func(t *testing.T) {
			ctx := context.Background()

			traceID, manager, err := trace.New(ctx, d.DriverType, nil, d.DriverOptions...)
			assert.NoError(t, err)
			defer trace.Release(traceID)
			defer trace.Remove(ctx, d.DriverType, traceID, d.DriverOptions...)

			// Create multiple subscribers
			sub1, cancel1, err := manager.Subscribe()
			assert.NoError(t, err)
			defer cancel1()

			sub2, cancel2, err := manager.Subscribe()
			assert.NoError(t, err)
			defer cancel2()

			sub3, cancel3, err := manager.Subscribe()
			assert.NoError(t, err)
			defer cancel3()

			// Collect updates from all subscribers
			var wg sync.WaitGroup
			counts := make([]int, 3)
			var mu sync.Mutex

			for i, sub := range []<-chan *types.TraceUpdate{sub1, sub2, sub3} {
				wg.Add(1)
				go func(idx int, ch <-chan *types.TraceUpdate) {
					defer wg.Done()
					timeout := time.After(1 * time.Second)
					for {
						select {
						case update := <-ch:
							if update != nil {
								mu.Lock()
								counts[idx]++
								mu.Unlock()
								if update.Type == types.UpdateTypeComplete {
									return
								}
							}
						case <-timeout:
							return
						}
					}
				}(i, sub)
			}

			// Perform operations
			_, err = manager.Add("test", types.TraceNodeOption{Label: "Test"})
			assert.NoError(t, err)
			err = manager.Complete(nil)
			assert.NoError(t, err)
			err = manager.MarkComplete()
			assert.NoError(t, err)

			// Wait for all subscribers
			wg.Wait()

			// All subscribers should receive updates
			mu.Lock()
			defer mu.Unlock()
			for i, count := range counts {
				assert.Greater(t, count, 0, "Subscriber %d should receive updates", i+1)
			}
		})
	}
}
