package temporal

import (
	"context"
	"testing"
	"time"

	"chorus.services/bzzz/pkg/ucxl"
	slurpContext "chorus.services/bzzz/pkg/slurp/context"
)

func TestDecisionNavigator_NavigateDecisionHops(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Create a chain of versions
	address := createTestAddress("test/component")
	initialContext := createTestContext("test/component", []string{"go"})
	
	_, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create initial context: %v", err)
	}
	
	// Create 3 more versions
	for i := 2; i <= 4; i++ {
		updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("tech%d", i)})
		decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal)
		
		_, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision)
		if err != nil {
			t.Fatalf("Failed to evolve context to version %d: %v", i, err)
		}
	}
	
	// Test forward navigation from version 1
	v1, err := graph.GetVersionAtDecision(ctx, address, 1)
	if err != nil {
		t.Fatalf("Failed to get version 1: %v", err)
	}
	
	// Navigate 2 hops forward from version 1
	result, err := navigator.NavigateDecisionHops(ctx, address, 2, NavigationForward)
	if err != nil {
		t.Fatalf("Failed to navigate forward: %v", err)
	}
	
	if result.Version != 3 {
		t.Errorf("Expected to navigate to version 3, got version %d", result.Version)
	}
	
	// Test backward navigation from version 4
	result2, err := navigator.NavigateDecisionHops(ctx, address, 2, NavigationBackward)
	if err != nil {
		t.Fatalf("Failed to navigate backward: %v", err)
	}
	
	if result2.Version != 2 {
		t.Errorf("Expected to navigate to version 2, got version %d", result2.Version)
	}
}

func TestDecisionNavigator_GetDecisionTimeline(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Create main context with evolution
	address := createTestAddress("test/main")
	initialContext := createTestContext("test/main", []string{"go"})
	
	_, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create initial context: %v", err)
	}
	
	// Evolve main context
	for i := 2; i <= 3; i++ {
		updatedContext := createTestContext("test/main", []string{"go", fmt.Sprintf("feature%d", i)})
		decision := createTestDecision(fmt.Sprintf("main-dec-%03d", i), fmt.Sprintf("dev%d", i), "Add feature", ImpactModule)
		
		_, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision)
		if err != nil {
			t.Fatalf("Failed to evolve main context to version %d: %v", i, err)
		}
	}
	
	// Create related context
	relatedAddr := createTestAddress("test/related")
	relatedContext := createTestContext("test/related", []string{"go"})
	
	_, err = graph.CreateInitialContext(ctx, relatedAddr, relatedContext, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create related context: %v", err)
	}
	
	// Add influence relationship
	err = graph.AddInfluenceRelationship(ctx, address, relatedAddr)
	if err != nil {
		t.Fatalf("Failed to add influence relationship: %v", err)
	}
	
	// Get decision timeline with related decisions
	timeline, err := navigator.GetDecisionTimeline(ctx, address, true, 5)
	if err != nil {
		t.Fatalf("Failed to get decision timeline: %v", err)
	}
	
	if len(timeline.DecisionSequence) != 3 {
		t.Errorf("Expected 3 decisions in timeline, got %d", len(timeline.DecisionSequence))
	}
	
	// Check ordering
	for i, entry := range timeline.DecisionSequence {
		expectedVersion := i + 1
		if entry.Version != expectedVersion {
			t.Errorf("Expected version %d at index %d, got %d", expectedVersion, i, entry.Version)
		}
	}
	
	// Should have related decisions
	if len(timeline.RelatedDecisions) == 0 {
		t.Error("Expected to find related decisions")
	}
	
	if timeline.AnalysisMetadata == nil {
		t.Error("Expected analysis metadata")
	}
}

func TestDecisionNavigator_FindStaleContexts(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Create contexts with different staleness levels
	addresses := make([]ucxl.Address, 3)
	
	for i := 0; i < 3; i++ {
		addresses[i] = createTestAddress(fmt.Sprintf("test/component%d", i))
		context := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"})
		
		_, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator")
		if err != nil {
			t.Fatalf("Failed to create context %d: %v", i, err)
		}
	}
	
	// Manually set staleness scores for testing
	graph.mu.Lock()
	for _, nodes := range graph.addressToNodes {
		for j, node := range nodes {
			// Set different staleness levels
			node.Staleness = float64(j+1) * 0.3
		}
	}
	graph.mu.Unlock()
	
	// Find stale contexts with threshold 0.5
	staleContexts, err := navigator.FindStaleContexts(ctx, 0.5)
	if err != nil {
		t.Fatalf("Failed to find stale contexts: %v", err)
	}
	
	// Should find contexts with staleness >= 0.5
	expectedStale := 0
	graph.mu.RLock()
	for _, nodes := range graph.addressToNodes {
		for _, node := range nodes {
			if node.Staleness >= 0.5 {
				expectedStale++
			}
		}
	}
	graph.mu.RUnlock()
	
	if len(staleContexts) != expectedStale {
		t.Errorf("Expected %d stale contexts, got %d", expectedStale, len(staleContexts))
	}
	
	// Results should be sorted by staleness score (highest first)
	for i := 1; i < len(staleContexts); i++ {
		if staleContexts[i-1].StalenessScore < staleContexts[i].StalenessScore {
			t.Error("Results should be sorted by staleness score in descending order")
		}
	}
}

func TestDecisionNavigator_BookmarkManagement(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Create context with multiple versions
	address := createTestAddress("test/component")
	initialContext := createTestContext("test/component", []string{"go"})
	
	_, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create initial context: %v", err)
	}
	
	// Create more versions
	for i := 2; i <= 5; i++ {
		updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("feature%d", i)})
		decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal)
		
		_, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision)
		if err != nil {
			t.Fatalf("Failed to evolve context to version %d: %v", i, err)
		}
	}
	
	// Create bookmarks
	bookmarkNames := []string{"Initial Release", "Major Feature", "Bug Fix", "Performance Improvement"}
	for i, name := range bookmarkNames {
		err := navigator.BookmarkDecision(ctx, address, i+1, name)
		if err != nil {
			t.Fatalf("Failed to create bookmark %s: %v", name, err)
		}
	}
	
	// List bookmarks
	bookmarks, err := navigator.ListBookmarks(ctx)
	if err != nil {
		t.Fatalf("Failed to list bookmarks: %v", err)
	}
	
	if len(bookmarks) != len(bookmarkNames) {
		t.Errorf("Expected %d bookmarks, got %d", len(bookmarkNames), len(bookmarks))
	}
	
	// Verify bookmark details
	for _, bookmark := range bookmarks {
		if bookmark.Address.String() != address.String() {
			t.Errorf("Expected bookmark address %s, got %s", address.String(), bookmark.Address.String())
		}
		
		if bookmark.DecisionHop < 1 || bookmark.DecisionHop > 4 {
			t.Errorf("Expected decision hop between 1-4, got %d", bookmark.DecisionHop)
		}
		
		if bookmark.Metadata == nil {
			t.Error("Expected bookmark metadata")
		}
	}
	
	// Bookmarks should be sorted by creation time (newest first)
	for i := 1; i < len(bookmarks); i++ {
		if bookmarks[i-1].CreatedAt.Before(bookmarks[i].CreatedAt) {
			t.Error("Bookmarks should be sorted by creation time (newest first)")
		}
	}
}

func TestDecisionNavigator_ValidationAndErrorHandling(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Test: Navigate decision hops on non-existent address
	nonExistentAddr := createTestAddress("non/existent")
	_, err := navigator.NavigateDecisionHops(ctx, nonExistentAddr, 1, NavigationForward)
	if err == nil {
		t.Error("Expected error when navigating on non-existent address")
	}
	
	// Test: Create bookmark for non-existent decision
	err = navigator.BookmarkDecision(ctx, nonExistentAddr, 1, "Test Bookmark")
	if err == nil {
		t.Error("Expected error when bookmarking non-existent decision")
	}
	
	// Create valid context for path validation tests
	address := createTestAddress("test/component")
	initialContext := createTestContext("test/component", []string{"go"})
	
	_, err = graph.CreateInitialContext(ctx, address, initialContext, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create initial context: %v", err)
	}
	
	// Test: Validate empty decision path
	err = navigator.ValidateDecisionPath(ctx, []*DecisionStep{})
	if err == nil {
		t.Error("Expected error when validating empty decision path")
	}
	
	// Test: Validate path with nil temporal node
	invalidPath := []*DecisionStep{
		{
			Address:      address,
			TemporalNode: nil,
			HopDistance:  0,
			Relationship: "test",
		},
	}
	
	err = navigator.ValidateDecisionPath(ctx, invalidPath)
	if err == nil {
		t.Error("Expected error when validating path with nil temporal node")
	}
	
	// Test: Get navigation history for non-existent session
	_, err = navigator.GetNavigationHistory(ctx, "non-existent-session")
	if err == nil {
		t.Error("Expected error when getting history for non-existent session")
	}
}

func BenchmarkDecisionNavigator_GetDecisionTimeline(b *testing.B) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Setup: Create context with many versions
	address := createTestAddress("test/component")
	initialContext := createTestContext("test/component", []string{"go"})
	
	_, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator")
	if err != nil {
		b.Fatalf("Failed to create initial context: %v", err)
	}
	
	// Create 100 versions
	for i := 2; i <= 100; i++ {
		updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("feature%d", i)})
		decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal)
		
		_, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision)
		if err != nil {
			b.Fatalf("Failed to evolve context to version %d: %v", i, err)
		}
	}
	
	b.ResetTimer()
	
	for i := 0; i < b.N; i++ {
		_, err := navigator.GetDecisionTimeline(ctx, address, true, 10)
		if err != nil {
			b.Fatalf("Failed to get decision timeline: %v", err)
		}
	}
}

func BenchmarkDecisionNavigator_FindStaleContexts(b *testing.B) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	navigator := NewDecisionNavigator(graph)
	ctx := context.Background()
	
	// Setup: Create many contexts
	for i := 0; i < 1000; i++ {
		address := createTestAddress(fmt.Sprintf("test/component%d", i))
		context := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"})
		
		_, err := graph.CreateInitialContext(ctx, address, context, "test_creator")
		if err != nil {
			b.Fatalf("Failed to create context %d: %v", i, err)
		}
	}
	
	// Set random staleness scores
	graph.mu.Lock()
	for _, nodes := range graph.addressToNodes {
		for _, node := range nodes {
			node.Staleness = 0.3 + (float64(node.Version)*0.1) // Varying staleness
		}
	}
	graph.mu.Unlock()
	
	b.ResetTimer()
	
	for i := 0; i < b.N; i++ {
		_, err := navigator.FindStaleContexts(ctx, 0.5)
		if err != nil {
			b.Fatalf("Failed to find stale contexts: %v", err)
		}
	}
}