package temporal

import (
	"context"
	"testing"
	"time"

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

func TestInfluenceAnalyzer_AnalyzeInfluenceNetwork(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Create a network of 5 contexts
	addresses := make([]ucxl.Address, 5)
	for i := 0; i < 5; 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)
		}
	}
	
	// Create influence relationships
	// 0 -> 1, 0 -> 2, 1 -> 3, 2 -> 3, 3 -> 4
	relationships := [][]int{
		{0, 1}, {0, 2}, {1, 3}, {2, 3}, {3, 4},
	}
	
	for _, rel := range relationships {
		err := graph.AddInfluenceRelationship(ctx, addresses[rel[0]], addresses[rel[1]])
		if err != nil {
			t.Fatalf("Failed to add relationship %d->%d: %v", rel[0], rel[1], err)
		}
	}
	
	// Analyze influence network
	analysis, err := analyzer.AnalyzeInfluenceNetwork(ctx)
	if err != nil {
		t.Fatalf("Failed to analyze influence network: %v", err)
	}
	
	if analysis.TotalNodes != 5 {
		t.Errorf("Expected 5 total nodes, got %d", analysis.TotalNodes)
	}
	
	if analysis.TotalEdges != 5 {
		t.Errorf("Expected 5 total edges, got %d", analysis.TotalEdges)
	}
	
	// Network density should be calculated correctly
	// Density = edges / (nodes * (nodes-1)) = 5 / (5 * 4) = 0.25
	expectedDensity := 5.0 / (5.0 * 4.0)
	if abs(analysis.NetworkDensity-expectedDensity) > 0.01 {
		t.Errorf("Expected network density %.2f, got %.2f", expectedDensity, analysis.NetworkDensity)
	}
	
	if analysis.CentralNodes == nil {
		t.Error("Expected central nodes to be identified")
	}
	
	if analysis.AnalyzedAt.IsZero() {
		t.Error("Expected analyzed timestamp to be set")
	}
}

func TestInfluenceAnalyzer_GetInfluenceStrength(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Create two contexts
	addr1 := createTestAddress("test/influencer")
	addr2 := createTestAddress("test/influenced")
	
	context1 := createTestContext("test/influencer", []string{"go", "core"})
	context1.RAGConfidence = 0.9 // High confidence
	
	context2 := createTestContext("test/influenced", []string{"go", "feature"})
	
	node1, err := graph.CreateInitialContext(ctx, addr1, context1, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create influencer context: %v", err)
	}
	
	_, err = graph.CreateInitialContext(ctx, addr2, context2, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create influenced context: %v", err)
	}
	
	// Set impact scope for higher influence
	node1.ImpactScope = ImpactProject
	
	// Add influence relationship
	err = graph.AddInfluenceRelationship(ctx, addr1, addr2)
	if err != nil {
		t.Fatalf("Failed to add influence relationship: %v", err)
	}
	
	// Calculate influence strength
	strength, err := analyzer.GetInfluenceStrength(ctx, addr1, addr2)
	if err != nil {
		t.Fatalf("Failed to get influence strength: %v", err)
	}
	
	if strength <= 0 {
		t.Error("Expected positive influence strength")
	}
	
	if strength > 1 {
		t.Error("Influence strength should not exceed 1")
	}
	
	// Test non-existent relationship
	addr3 := createTestAddress("test/unrelated")
	context3 := createTestContext("test/unrelated", []string{"go"})
	
	_, err = graph.CreateInitialContext(ctx, addr3, context3, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create unrelated context: %v", err)
	}
	
	strength2, err := analyzer.GetInfluenceStrength(ctx, addr1, addr3)
	if err != nil {
		t.Fatalf("Failed to get influence strength for unrelated: %v", err)
	}
	
	if strength2 != 0 {
		t.Errorf("Expected 0 influence strength for unrelated contexts, got %f", strength2)
	}
}

func TestInfluenceAnalyzer_FindInfluentialDecisions(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Create contexts with varying influence levels
	addresses := make([]ucxl.Address, 4)
	contexts := make([]*slurpContext.ContextNode, 4)
	
	for i := 0; i < 4; i++ {
		addresses[i] = createTestAddress(fmt.Sprintf("test/component%d", i))
		contexts[i] = createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"})
		
		// Vary confidence levels
		contexts[i].RAGConfidence = 0.6 + float64(i)*0.1
		
		_, err := graph.CreateInitialContext(ctx, addresses[i], contexts[i], "test_creator")
		if err != nil {
			t.Fatalf("Failed to create context %d: %v", i, err)
		}
	}
	
	// Create influence network with component 1 as most influential
	// 1 -> 0, 1 -> 2, 1 -> 3 (component 1 influences all others)
	for i := 0; i < 4; i++ {
		if i != 1 {
			err := graph.AddInfluenceRelationship(ctx, addresses[1], addresses[i])
			if err != nil {
				t.Fatalf("Failed to add influence from 1 to %d: %v", i, err)
			}
		}
	}
	
	// Also add 0 -> 2 (component 0 influences component 2)
	err := graph.AddInfluenceRelationship(ctx, addresses[0], addresses[2])
	if err != nil {
		t.Fatalf("Failed to add influence from 0 to 2: %v", err)
	}
	
	// Find influential decisions
	influential, err := analyzer.FindInfluentialDecisions(ctx, 3)
	if err != nil {
		t.Fatalf("Failed to find influential decisions: %v", err)
	}
	
	if len(influential) == 0 {
		t.Fatal("Expected to find influential decisions")
	}
	
	// Results should be sorted by influence score (highest first)
	for i := 1; i < len(influential); i++ {
		if influential[i-1].InfluenceScore < influential[i].InfluenceScore {
			t.Error("Results should be sorted by influence score in descending order")
		}
	}
	
	// Component 1 should be most influential (influences 3 others)
	mostInfluential := influential[0]
	if mostInfluential.Address.String() != addresses[1].String() {
		t.Errorf("Expected component 1 to be most influential, got %s", mostInfluential.Address.String())
	}
	
	// Check that influence reasons are provided
	if len(mostInfluential.InfluenceReasons) == 0 {
		t.Error("Expected influence reasons to be provided")
	}
	
	// Check that impact analysis is provided
	if mostInfluential.ImpactAnalysis == nil {
		t.Error("Expected impact analysis to be provided")
	}
}

func TestInfluenceAnalyzer_AnalyzeDecisionImpact(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Create a context and evolve it
	address := createTestAddress("test/core-service")
	initialContext := createTestContext("test/core-service", []string{"go", "core"})
	
	_, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator")
	if err != nil {
		t.Fatalf("Failed to create initial context: %v", err)
	}
	
	// Create dependent contexts
	dependentAddrs := make([]ucxl.Address, 3)
	for i := 0; i < 3; i++ {
		dependentAddrs[i] = createTestAddress(fmt.Sprintf("test/dependent%d", i))
		dependentContext := createTestContext(fmt.Sprintf("test/dependent%d", i), []string{"go"})
		
		_, err := graph.CreateInitialContext(ctx, dependentAddrs[i], dependentContext, "test_creator")
		if err != nil {
			t.Fatalf("Failed to create dependent context %d: %v", i, err)
		}
		
		// Add influence relationship
		err = graph.AddInfluenceRelationship(ctx, address, dependentAddrs[i])
		if err != nil {
			t.Fatalf("Failed to add influence to dependent %d: %v", i, err)
		}
	}
	
	// Evolve the core service with an architectural change
	updatedContext := createTestContext("test/core-service", []string{"go", "core", "microservice"})
	decision := createTestDecision("arch-001", "architect", "Split into microservices", ImpactSystem)
	
	evolvedNode, err := graph.EvolveContext(ctx, address, updatedContext, ReasonArchitectureChange, decision)
	if err != nil {
		t.Fatalf("Failed to evolve core service: %v", err)
	}
	
	// Analyze decision impact
	impact, err := analyzer.AnalyzeDecisionImpact(ctx, address, evolvedNode.Version)
	if err != nil {
		t.Fatalf("Failed to analyze decision impact: %v", err)
	}
	
	if impact.Address.String() != address.String() {
		t.Errorf("Expected impact address %s, got %s", address.String(), impact.Address.String())
	}
	
	if impact.DecisionHop != evolvedNode.Version {
		t.Errorf("Expected decision hop %d, got %d", evolvedNode.Version, impact.DecisionHop)
	}
	
	// Should have direct impact on dependent services
	if len(impact.DirectImpact) != 3 {
		t.Errorf("Expected 3 direct impacts, got %d", len(impact.DirectImpact))
	}
	
	// Impact strength should be positive
	if impact.ImpactStrength <= 0 {
		t.Error("Expected positive impact strength")
	}
	
	// Should have impact categories
	if len(impact.ImpactCategories) == 0 {
		t.Error("Expected impact categories to be identified")
	}
	
	// Should have mitigation actions
	if len(impact.MitigationActions) == 0 {
		t.Error("Expected mitigation actions to be suggested")
	}
}

func TestInfluenceAnalyzer_PredictInfluence(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Create contexts with similar technologies
	addr1 := createTestAddress("test/service1")
	addr2 := createTestAddress("test/service2")
	addr3 := createTestAddress("test/service3")
	
	// Services 1 and 2 share technologies (higher prediction probability)
	context1 := createTestContext("test/service1", []string{"go", "grpc", "postgres"})
	context2 := createTestContext("test/service2", []string{"go", "grpc", "redis"})
	context3 := createTestContext("test/service3", []string{"python", "flask"}) // Different tech stack
	
	contexts := []*slurpContext.ContextNode{context1, context2, context3}
	addresses := []ucxl.Address{addr1, addr2, addr3}
	
	for i, context := range contexts {
		_, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator")
		if err != nil {
			t.Fatalf("Failed to create context %d: %v", i, err)
		}
	}
	
	// Predict influence from service1
	predictions, err := analyzer.PredictInfluence(ctx, addr1)
	if err != nil {
		t.Fatalf("Failed to predict influence: %v", err)
	}
	
	// Should predict influence to service2 (similar tech stack)
	foundService2 := false
	foundService3 := false
	
	for _, prediction := range predictions {
		if prediction.To.String() == addr2.String() {
			foundService2 = true
			// Should have higher probability due to technology similarity
			if prediction.Probability <= 0.3 {
				t.Errorf("Expected higher prediction probability for similar service, got %f", prediction.Probability)
			}
		}
		if prediction.To.String() == addr3.String() {
			foundService3 = true
		}
	}
	
	if !foundService2 && len(predictions) > 0 {
		t.Error("Expected to predict influence to service with similar technology stack")
	}
	
	// Predictions should include reasons
	for _, prediction := range predictions {
		if len(prediction.Reasons) == 0 {
			t.Error("Expected prediction reasons to be provided")
		}
		
		if prediction.Confidence <= 0 || prediction.Confidence > 1 {
			t.Errorf("Expected confidence between 0 and 1, got %f", prediction.Confidence)
		}
		
		if prediction.EstimatedDelay <= 0 {
			t.Error("Expected positive estimated delay")
		}
	}
}

func TestInfluenceAnalyzer_GetCentralityMetrics(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Create a small network for centrality testing
	addresses := make([]ucxl.Address, 4)
	for i := 0; i < 4; i++ {
		addresses[i] = createTestAddress(fmt.Sprintf("test/node%d", i))
		context := createTestContext(fmt.Sprintf("test/node%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)
		}
	}
	
	// Create star topology with node 0 at center
	// 0 -> 1, 0 -> 2, 0 -> 3
	for i := 1; i < 4; i++ {
		err := graph.AddInfluenceRelationship(ctx, addresses[0], addresses[i])
		if err != nil {
			t.Fatalf("Failed to add influence 0->%d: %v", i, err)
		}
	}
	
	// Calculate centrality metrics
	metrics, err := analyzer.GetCentralityMetrics(ctx)
	if err != nil {
		t.Fatalf("Failed to get centrality metrics: %v", err)
	}
	
	if len(metrics.DegreeCentrality) != 4 {
		t.Errorf("Expected degree centrality for 4 nodes, got %d", len(metrics.DegreeCentrality))
	}
	
	if len(metrics.BetweennessCentrality) != 4 {
		t.Errorf("Expected betweenness centrality for 4 nodes, got %d", len(metrics.BetweennessCentrality))
	}
	
	if len(metrics.ClosenessCentrality) != 4 {
		t.Errorf("Expected closeness centrality for 4 nodes, got %d", len(metrics.ClosenessCentrality))
	}
	
	if len(metrics.PageRank) != 4 {
		t.Errorf("Expected PageRank for 4 nodes, got %d", len(metrics.PageRank))
	}
	
	// Node 0 should have highest degree centrality (connected to all others)
	node0ID := ""
	graph.mu.RLock()
	for _, nodes := range graph.addressToNodes {
		for _, node := range nodes {
			if node.UCXLAddress.String() == addresses[0].String() {
				node0ID = node.ID
				break
			}
		}
	}
	graph.mu.RUnlock()
	
	if node0ID != "" {
		node0Centrality := metrics.DegreeCentrality[node0ID]
		
		// Check that other nodes have lower centrality
		for nodeID, centrality := range metrics.DegreeCentrality {
			if nodeID != node0ID && centrality >= node0Centrality {
				t.Error("Expected central node to have highest degree centrality")
			}
		}
	}
	
	if metrics.CalculatedAt.IsZero() {
		t.Error("Expected calculated timestamp to be set")
	}
}

func TestInfluenceAnalyzer_CachingAndPerformance(t *testing.T) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph).(*influenceAnalyzerImpl)
	ctx := context.Background()
	
	// Create small network
	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)
		}
	}
	
	err := graph.AddInfluenceRelationship(ctx, addresses[0], addresses[1])
	if err != nil {
		t.Fatalf("Failed to add influence relationship: %v", err)
	}
	
	// First call should populate cache
	start1 := time.Now()
	analysis1, err := analyzer.AnalyzeInfluenceNetwork(ctx)
	if err != nil {
		t.Fatalf("Failed to analyze influence network (first call): %v", err)
	}
	duration1 := time.Since(start1)
	
	// Second call should use cache and be faster
	start2 := time.Now()
	analysis2, err := analyzer.AnalyzeInfluenceNetwork(ctx)
	if err != nil {
		t.Fatalf("Failed to analyze influence network (second call): %v", err)
	}
	duration2 := time.Since(start2)
	
	// Results should be identical
	if analysis1.TotalNodes != analysis2.TotalNodes {
		t.Error("Cached results should be identical to original")
	}
	
	if analysis1.TotalEdges != analysis2.TotalEdges {
		t.Error("Cached results should be identical to original")
	}
	
	// Second call should be faster (cached)
	// Note: In practice, this test might be flaky due to small network size
	// and timing variations, but it demonstrates the caching concept
	if duration2 > duration1 {
		t.Logf("Warning: Second call took longer (%.2fms vs %.2fms), cache may not be working optimally", 
			duration2.Seconds()*1000, duration1.Seconds()*1000)
	}
}

func BenchmarkInfluenceAnalyzer_AnalyzeInfluenceNetwork(b *testing.B) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Setup: Create network of 50 contexts
	addresses := make([]ucxl.Address, 50)
	for i := 0; i < 50; 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 {
			b.Fatalf("Failed to create context %d: %v", i, err)
		}
		
		// Add some influence relationships
		if i > 0 {
			err = graph.AddInfluenceRelationship(ctx, addresses[i-1], addresses[i])
			if err != nil {
				b.Fatalf("Failed to add influence relationship: %v", err)
			}
		}
		
		// Add some random cross-connections
		if i > 10 && i%5 == 0 {
			err = graph.AddInfluenceRelationship(ctx, addresses[i-10], addresses[i])
			if err != nil {
				b.Fatalf("Failed to add cross-connection: %v", err)
			}
		}
	}
	
	b.ResetTimer()
	
	for i := 0; i < b.N; i++ {
		_, err := analyzer.AnalyzeInfluenceNetwork(ctx)
		if err != nil {
			b.Fatalf("Failed to analyze influence network: %v", err)
		}
	}
}

func BenchmarkInfluenceAnalyzer_GetCentralityMetrics(b *testing.B) {
	storage := newMockStorage()
	graph := NewTemporalGraph(storage).(*temporalGraphImpl)
	analyzer := NewInfluenceAnalyzer(graph)
	ctx := context.Background()
	
	// Setup: Create dense network
	addresses := make([]ucxl.Address, 20)
	for i := 0; i < 20; i++ {
		addresses[i] = createTestAddress(fmt.Sprintf("test/node%d", i))
		context := createTestContext(fmt.Sprintf("test/node%d", i), []string{"go"})
		
		_, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator")
		if err != nil {
			b.Fatalf("Failed to create context %d: %v", i, err)
		}
	}
	
	// Create dense connections
	for i := 0; i < 20; i++ {
		for j := i + 1; j < 20; j++ {
			if j-i <= 3 { // Connect to next 3 nodes
				err := graph.AddInfluenceRelationship(ctx, addresses[i], addresses[j])
				if err != nil {
					b.Fatalf("Failed to add influence %d->%d: %v", i, j, err)
				}
			}
		}
	}
	
	b.ResetTimer()
	
	for i := 0; i < b.N; i++ {
		_, err := analyzer.GetCentralityMetrics(ctx)
		if err != nil {
			b.Fatalf("Failed to get centrality metrics: %v", err)
		}
	}
}

// Helper function for float comparison
func abs(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}