package temporal

import (
	"context"
	"fmt"
	"math"
	"sort"
	"sync"
	"time"

	"chorus.services/bzzz/pkg/ucxl"
)

// influenceAnalyzerImpl implements the InfluenceAnalyzer interface
type influenceAnalyzerImpl struct {
	mu sync.RWMutex
	
	// Reference to the temporal graph
	graph *temporalGraphImpl
	
	// Cached analysis results
	networkAnalysisCache *InfluenceNetworkAnalysis
	centralityCache      *CentralityMetrics
	lastAnalysisTime     time.Time
	cacheValidDuration   time.Duration
	
	// Analysis parameters
	dampingFactor        float64 // For PageRank calculation
	maxIterations        int     // For iterative algorithms
	convergenceThreshold float64 // For convergence checking
}

// NewInfluenceAnalyzer creates a new influence analyzer
func NewInfluenceAnalyzer(graph *temporalGraphImpl) InfluenceAnalyzer {
	return &influenceAnalyzerImpl{
		graph:                graph,
		cacheValidDuration:   time.Minute * 30,
		dampingFactor:        0.85, // Standard PageRank damping factor
		maxIterations:        100,
		convergenceThreshold: 1e-6,
	}
}

// AnalyzeInfluenceNetwork analyzes the structure of decision influence relationships
func (ia *influenceAnalyzerImpl) AnalyzeInfluenceNetwork(ctx context.Context) (*InfluenceNetworkAnalysis, error) {
	ia.mu.Lock()
	defer ia.mu.Unlock()
	
	// Check if cached analysis is still valid
	if ia.networkAnalysisCache != nil && time.Since(ia.lastAnalysisTime) < ia.cacheValidDuration {
		return ia.networkAnalysisCache, nil
	}
	
	ia.graph.mu.RLock()
	defer ia.graph.mu.RUnlock()
	
	totalNodes := len(ia.graph.nodes)
	totalEdges := 0
	
	// Count total edges
	for _, influences := range ia.graph.influences {
		totalEdges += len(influences)
	}
	
	// Calculate network density
	maxPossibleEdges := totalNodes * (totalNodes - 1)
	networkDensity := 0.0
	if maxPossibleEdges > 0 {
		networkDensity = float64(totalEdges) / float64(maxPossibleEdges)
	}
	
	// Calculate clustering coefficient
	clusteringCoeff := ia.calculateClusteringCoefficient()
	
	// Calculate average path length
	avgPathLength := ia.calculateAveragePathLength()
	
	// Find central nodes
	centralNodes := ia.findCentralNodes()
	
	// Detect communities
	communities := ia.detectCommunities()
	
	analysis := &InfluenceNetworkAnalysis{
		TotalNodes:        totalNodes,
		TotalEdges:        totalEdges,
		NetworkDensity:    networkDensity,
		ClusteringCoeff:   clusteringCoeff,
		AveragePathLength: avgPathLength,
		CentralNodes:      centralNodes,
		Communities:       communities,
		AnalyzedAt:        time.Now(),
	}
	
	// Cache the results
	ia.networkAnalysisCache = analysis
	ia.lastAnalysisTime = time.Now()
	
	return analysis, nil
}

// GetInfluenceStrength calculates influence strength between contexts
func (ia *influenceAnalyzerImpl) GetInfluenceStrength(ctx context.Context, influencer, influenced ucxl.Address) (float64, error) {
	ia.mu.RLock()
	defer ia.mu.RUnlock()
	
	ia.graph.mu.RLock()
	defer ia.graph.mu.RUnlock()
	
	// Get the latest nodes for both addresses
	influencerNode, err := ia.graph.getLatestNodeUnsafe(influencer)
	if err != nil {
		return 0, fmt.Errorf("influencer node not found: %w", err)
	}
	
	influencedNode, err := ia.graph.getLatestNodeUnsafe(influenced)
	if err != nil {
		return 0, fmt.Errorf("influenced node not found: %w", err)
	}
	
	// Check if direct influence exists
	influences, exists := ia.graph.influences[influencerNode.ID]
	if !exists {
		return 0, nil
	}
	
	hasDirectInfluence := false
	for _, influencedID := range influences {
		if influencedID == influencedNode.ID {
			hasDirectInfluence = true
			break
		}
	}
	
	if !hasDirectInfluence {
		return 0, nil
	}
	
	// Calculate influence strength based on multiple factors
	strength := 0.0
	
	// Factor 1: Decision recency (more recent = stronger influence)
	timeDiff := time.Since(influencerNode.Timestamp)
	recencyFactor := math.Max(0, 1.0-timeDiff.Hours()/(7*24)) // Decay over a week
	strength += recencyFactor * 0.3
	
	// Factor 2: Confidence level of the influencer
	confidenceFactor := influencerNode.Confidence
	strength += confidenceFactor * 0.3
	
	// Factor 3: Impact scope (broader scope = stronger influence)
	scopeFactor := 0.0
	switch influencerNode.ImpactScope {
	case ImpactSystem:
		scopeFactor = 1.0
	case ImpactProject:
		scopeFactor = 0.8
	case ImpactModule:
		scopeFactor = 0.6
	case ImpactLocal:
		scopeFactor = 0.4
	}
	strength += scopeFactor * 0.2
	
	// Factor 4: Network position (central nodes have stronger influence)
	centralityFactor := ia.getNodeCentrality(influencerNode.ID)
	strength += centralityFactor * 0.2
	
	return math.Min(strength, 1.0), nil
}

// FindInfluentialDecisions finds the most influential decisions in the system
func (ia *influenceAnalyzerImpl) FindInfluentialDecisions(ctx context.Context, limit int) ([]*InfluentialDecision, error) {
	ia.mu.RLock()
	defer ia.mu.RLock()
	
	ia.graph.mu.RLock()
	defer ia.graph.mu.RUnlock()
	
	type nodeScore struct {
		node  *TemporalNode
		score float64
	}
	
	scores := make([]*nodeScore, 0, len(ia.graph.nodes))
	
	// Calculate influence score for each node
	for _, node := range ia.graph.nodes {
		score := ia.calculateInfluenceScore(node)
		scores = append(scores, &nodeScore{node: node, score: score})
	}
	
	// Sort by influence score (highest first)
	sort.Slice(scores, func(i, j int) bool {
		return scores[i].score > scores[j].score
	})
	
	// Convert to InfluentialDecision structs
	influential := make([]*InfluentialDecision, 0)
	maxResults := limit
	if maxResults <= 0 || maxResults > len(scores) {
		maxResults = len(scores)
	}
	
	for i := 0; i < maxResults; i++ {
		nodeScore := scores[i]
		node := nodeScore.node
		
		// Analyze impact of this decision
		impact := ia.analyzeDecisionImpactInternal(node)
		
		decision := &InfluentialDecision{
			Address:           node.UCXLAddress,
			DecisionHop:       node.Version,
			InfluenceScore:    nodeScore.score,
			AffectedContexts:  node.Influences,
			DecisionMetadata:  ia.graph.decisions[node.DecisionID],
			ImpactAnalysis:    impact,
			InfluenceReasons:  ia.getInfluenceReasons(node, nodeScore.score),
		}
		
		influential = append(influential, decision)
	}
	
	return influential, nil
}

// AnalyzeDecisionImpact analyzes the impact of a specific decision
func (ia *influenceAnalyzerImpl) AnalyzeDecisionImpact(ctx context.Context, address ucxl.Address, decisionHop int) (*DecisionImpact, error) {
	ia.mu.RLock()
	defer ia.mu.RUnlock()
	
	ia.graph.mu.RLock()
	defer ia.graph.mu.RUnlock()
	
	// Get the specific decision version
	node, err := ia.graph.GetVersionAtDecision(ctx, address, decisionHop)
	if err != nil {
		return nil, fmt.Errorf("decision not found: %w", err)
	}
	
	return ia.analyzeDecisionImpactInternal(node), nil
}

// PredictInfluence predicts likely influence relationships
func (ia *influenceAnalyzerImpl) PredictInfluence(ctx context.Context, address ucxl.Address) ([]*PredictedInfluence, error) {
	ia.mu.RLock()
	defer ia.mu.RUnlock()
	
	ia.graph.mu.RLock()
	defer ia.graph.mu.RUnlock()
	
	node, err := ia.graph.getLatestNodeUnsafe(address)
	if err != nil {
		return nil, fmt.Errorf("node not found: %w", err)
	}
	
	predictions := make([]*PredictedInfluence, 0)
	
	// Analyze patterns to predict new influences
	for targetAddress, targetNodes := range ia.graph.addressToNodes {
		if targetAddress == address.String() {
			continue // Skip self
		}
		
		targetNode := targetNodes[len(targetNodes)-1] // Latest version
		
		// Skip if influence already exists
		if ia.hasDirectInfluence(node, targetNode) {
			continue
		}
		
		// Calculate prediction probability based on various factors
		probability := ia.calculateInfluenceProbability(node, targetNode)
		
		if probability > 0.3 { // Threshold for meaningful predictions
			prediction := &PredictedInfluence{
				From:           address,
				To:             targetNode.UCXLAddress,
				Probability:    probability,
				Strength:       probability * 0.8, // Predicted strength slightly lower than probability
				Reasons:        ia.getPredictionReasons(node, targetNode),
				Confidence:     probability * 0.9,
				EstimatedDelay: time.Duration(float64(time.Hour*24) * (1.0 - probability)),
			}
			predictions = append(predictions, prediction)
		}
	}
	
	// Sort by probability (highest first)
	sort.Slice(predictions, func(i, j int) bool {
		return predictions[i].Probability > predictions[j].Probability
	})
	
	// Limit to top 10 predictions
	if len(predictions) > 10 {
		predictions = predictions[:10]
	}
	
	return predictions, nil
}

// GetCentralityMetrics calculates centrality metrics for contexts
func (ia *influenceAnalyzerImpl) GetCentralityMetrics(ctx context.Context) (*CentralityMetrics, error) {
	ia.mu.Lock()
	defer ia.mu.Unlock()
	
	// Check cache
	if ia.centralityCache != nil && time.Since(ia.lastAnalysisTime) < ia.cacheValidDuration {
		return ia.centralityCache, nil
	}
	
	ia.graph.mu.RLock()
	defer ia.graph.mu.RUnlock()
	
	metrics := &CentralityMetrics{
		BetweennessCentrality: make(map[string]float64),
		ClosenessCentrality:   make(map[string]float64),
		DegreeCentrality:      make(map[string]float64),
		EigenvectorCentrality: make(map[string]float64),
		PageRank:              make(map[string]float64),
		CalculatedAt:          time.Now(),
	}
	
	// Calculate degree centrality
	ia.calculateDegreeCentrality(metrics)
	
	// Calculate betweenness centrality
	ia.calculateBetweennessCentrality(metrics)
	
	// Calculate closeness centrality
	ia.calculateClosenessCentrality(metrics)
	
	// Calculate eigenvector centrality
	ia.calculateEigenvectorCentrality(metrics)
	
	// Calculate PageRank
	ia.calculatePageRank(metrics)
	
	// Cache the results
	ia.centralityCache = metrics
	ia.lastAnalysisTime = time.Now()
	
	return metrics, nil
}

// Helper methods

func (ia *influenceAnalyzerImpl) calculateClusteringCoefficient() float64 {
	totalCoeff := 0.0
	nodeCount := 0
	
	for nodeID := range ia.graph.nodes {
		coeff := ia.calculateNodeClusteringCoefficient(nodeID)
		totalCoeff += coeff
		nodeCount++
	}
	
	if nodeCount == 0 {
		return 0
	}
	
	return totalCoeff / float64(nodeCount)
}

func (ia *influenceAnalyzerImpl) calculateNodeClusteringCoefficient(nodeID string) float64 {
	neighbors := ia.getNeighbors(nodeID)
	
	if len(neighbors) < 2 {
		return 0
	}
	
	// Count connections between neighbors
	connections := 0
	for i, neighbor1 := range neighbors {
		for j := i + 1; j < len(neighbors); j++ {
			neighbor2 := neighbors[j]
			if ia.areConnected(neighbor1, neighbor2) {
				connections++
			}
		}
	}
	
	possibleConnections := len(neighbors) * (len(neighbors) - 1) / 2
	if possibleConnections == 0 {
		return 0
	}
	
	return float64(connections) / float64(possibleConnections)
}

func (ia *influenceAnalyzerImpl) calculateAveragePathLength() float64 {
	totalPaths := 0
	totalLength := 0.0
	
	// Sample a subset of node pairs to avoid O(n³) complexity
	nodeIDs := make([]string, 0, len(ia.graph.nodes))
	for nodeID := range ia.graph.nodes {
		nodeIDs = append(nodeIDs, nodeID)
	}
	
	sampleSize := min(100, len(nodeIDs)) // Sample up to 100 nodes
	for i := 0; i < sampleSize; i++ {
		for j := i + 1; j < sampleSize; j++ {
			pathLength := ia.findShortestPathLength(nodeIDs[i], nodeIDs[j])
			if pathLength > 0 {
				totalLength += float64(pathLength)
				totalPaths++
			}
		}
	}
	
	if totalPaths == 0 {
		return 0
	}
	
	return totalLength / float64(totalPaths)
}

func (ia *influenceAnalyzerImpl) findCentralNodes() []CentralNode {
	centralNodes := make([]CentralNode, 0)
	
	// Calculate various centrality measures for each node
	for nodeID, node := range ia.graph.nodes {
		degreeCentrality := ia.calculateNodeDegreeCentrality(nodeID)
		betweennessCentrality := ia.calculateNodeBetweennessCentrality(nodeID)
		closenessCentrality := ia.calculateNodeClosenessCentrality(nodeID)
		pageRank := ia.calculateNodePageRank(nodeID)
		
		// Calculate overall influence score
		influenceScore := (degreeCentrality + betweennessCentrality + closenessCentrality + pageRank) / 4.0
		
		if influenceScore > 0.5 { // Threshold for "central"
			centralNode := CentralNode{
				Address:               node.UCXLAddress,
				BetweennessCentrality: betweennessCentrality,
				ClosenessCentrality:   closenessCentrality,
				DegreeCentrality:      degreeCentrality,
				PageRank:              pageRank,
				InfluenceScore:        influenceScore,
			}
			centralNodes = append(centralNodes, centralNode)
		}
	}
	
	// Sort by influence score
	sort.Slice(centralNodes, func(i, j int) bool {
		return centralNodes[i].InfluenceScore > centralNodes[j].InfluenceScore
	})
	
	// Limit to top 20
	if len(centralNodes) > 20 {
		centralNodes = centralNodes[:20]
	}
	
	return centralNodes
}

func (ia *influenceAnalyzerImpl) detectCommunities() []Community {
	// Simple community detection based on modularity
	communities := make([]Community, 0)
	
	// Use a simple approach: group nodes with high interconnectivity
	visited := make(map[string]bool)
	communityID := 0
	
	for nodeID := range ia.graph.nodes {
		if visited[nodeID] {
			continue
		}
		
		// Start a new community
		community := ia.expandCommunity(nodeID, visited)
		if len(community) >= 3 { // Minimum community size
			communityStruct := Community{
				ID:          fmt.Sprintf("community-%d", communityID),
				Nodes:       community,
				Modularity:  ia.calculateCommunityModularity(community),
				Density:     ia.calculateCommunityDensity(community),
				Description: fmt.Sprintf("Community of %d related decisions", len(community)),
				Tags:        []string{"auto-detected"},
			}
			communities = append(communities, communityStruct)
			communityID++
		}
	}
	
	return communities
}

func (ia *influenceAnalyzerImpl) calculateInfluenceScore(node *TemporalNode) float64 {
	score := 0.0
	
	// Factor 1: Number of direct influences (30%)
	directInfluences := len(ia.graph.influences[node.ID])
	score += float64(directInfluences) * 0.3
	
	// Factor 2: Network centrality (25%)
	centrality := ia.getNodeCentrality(node.ID)
	score += centrality * 0.25
	
	// Factor 3: Decision confidence (20%)
	score += node.Confidence * 0.2
	
	// Factor 4: Impact scope (15%)
	scopeWeight := 0.0
	switch node.ImpactScope {
	case ImpactSystem:
		scopeWeight = 1.0
	case ImpactProject:
		scopeWeight = 0.8
	case ImpactModule:
		scopeWeight = 0.6
	case ImpactLocal:
		scopeWeight = 0.4
	}
	score += scopeWeight * 0.15
	
	// Factor 5: Recency (10%)
	timeSinceDecision := time.Since(node.Timestamp)
	recencyScore := math.Max(0, 1.0-timeSinceDecision.Hours()/(30*24)) // Decay over 30 days
	score += recencyScore * 0.1
	
	return score
}

func (ia *influenceAnalyzerImpl) analyzeDecisionImpactInternal(node *TemporalNode) *DecisionImpact {
	// Find direct and indirect impact
	directImpact := make([]ucxl.Address, len(node.Influences))
	copy(directImpact, node.Influences)
	
	// Find indirect impact (influenced nodes that are influenced by direct impact)
	indirectImpact := make([]ucxl.Address, 0)
	impactRadius := 1
	
	visited := make(map[string]bool)
	queue := []string{node.ID}
	visited[node.ID] = true
	
	for len(queue) > 0 && impactRadius <= 3 { // Limit to 3 hops
		levelSize := len(queue)
		for i := 0; i < levelSize; i++ {
			currentID := queue[0]
			queue = queue[1:]
			
			if influences, exists := ia.graph.influences[currentID]; exists {
				for _, influencedID := range influences {
					if !visited[influencedID] {
						visited[influencedID] = true
						queue = append(queue, influencedID)
						
						if influencedNode, exists := ia.graph.nodes[influencedID]; exists {
							if impactRadius > 1 { // Indirect impact
								indirectImpact = append(indirectImpact, influencedNode.UCXLAddress)
							}
						}
					}
				}
			}
		}
		impactRadius++
	}
	
	// Calculate impact strength
	impactStrength := float64(len(directImpact))*1.0 + float64(len(indirectImpact))*0.5
	impactStrength = math.Min(impactStrength/10.0, 1.0) // Normalize to 0-1
	
	// Estimate propagation time
	propagationTime := time.Duration(impactRadius) * time.Hour * 24
	
	return &DecisionImpact{
		Address:           node.UCXLAddress,
		DecisionHop:       node.Version,
		DirectImpact:      directImpact,
		IndirectImpact:    indirectImpact,
		ImpactRadius:      impactRadius - 1,
		ImpactStrength:    impactStrength,
		PropagationTime:   propagationTime,
		ImpactCategories:  []string{"influence_network", "decision_cascade"},
		MitigationActions: []string{"review_affected_contexts", "validate_assumptions"},
	}
}

func (ia *influenceAnalyzerImpl) getInfluenceReasons(node *TemporalNode, score float64) []string {
	reasons := make([]string, 0)
	
	if len(node.Influences) > 3 {
		reasons = append(reasons, "influences many contexts")
	}
	
	if node.Confidence > 0.8 {
		reasons = append(reasons, "high confidence decision")
	}
	
	if node.ImpactScope == ImpactSystem || node.ImpactScope == ImpactProject {
		reasons = append(reasons, "broad impact scope")
	}
	
	if score > 0.8 {
		reasons = append(reasons, "high network centrality")
	}
	
	return reasons
}

func (ia *influenceAnalyzerImpl) hasDirectInfluence(from, to *TemporalNode) bool {
	influences, exists := ia.graph.influences[from.ID]
	if !exists {
		return false
	}
	
	for _, influencedID := range influences {
		if influencedID == to.ID {
			return true
		}
	}
	return false
}

func (ia *influenceAnalyzerImpl) calculateInfluenceProbability(from, to *TemporalNode) float64 {
	probability := 0.0
	
	// Factor 1: Technology similarity
	techSimilarity := ia.calculateTechnologySimilarity(from, to)
	probability += techSimilarity * 0.3
	
	// Factor 2: Temporal proximity
	timeDiff := math.Abs(from.Timestamp.Sub(to.Timestamp).Hours())
	temporalProximity := math.Max(0, 1.0-timeDiff/(7*24)) // Closer in time = higher probability
	probability += temporalProximity * 0.2
	
	// Factor 3: Common influencers
	commonInfluencers := ia.countCommonInfluencers(from, to)
	probability += math.Min(float64(commonInfluencers)/3.0, 1.0) * 0.3
	
	// Factor 4: Network distance
	distance := ia.findShortestPathLength(from.ID, to.ID)
	if distance > 0 && distance <= 3 {
		networkProximity := 1.0 - float64(distance-1)/2.0
		probability += networkProximity * 0.2
	}
	
	return math.Min(probability, 1.0)
}

func (ia *influenceAnalyzerImpl) getPredictionReasons(from, to *TemporalNode) []string {
	reasons := make([]string, 0)
	
	if ia.calculateTechnologySimilarity(from, to) > 0.7 {
		reasons = append(reasons, "similar technologies")
	}
	
	if ia.countCommonInfluencers(from, to) > 2 {
		reasons = append(reasons, "common influencers")
	}
	
	distance := ia.findShortestPathLength(from.ID, to.ID)
	if distance > 0 && distance <= 2 {
		reasons = append(reasons, "close in network")
	}
	
	timeDiff := math.Abs(from.Timestamp.Sub(to.Timestamp).Hours())
	if timeDiff < 24 {
		reasons = append(reasons, "recent decisions")
	}
	
	return reasons
}

// Additional helper methods for centrality calculations

func (ia *influenceAnalyzerImpl) calculateDegreeCentrality(metrics *CentralityMetrics) {
	totalNodes := float64(len(ia.graph.nodes))
	
	for nodeID := range ia.graph.nodes {
		degree := float64(len(ia.graph.influences[nodeID]) + len(ia.graph.influencedBy[nodeID]))
		centrality := degree / (totalNodes - 1) // Normalized degree centrality
		metrics.DegreeCentrality[nodeID] = centrality
	}
}

func (ia *influenceAnalyzerImpl) calculateBetweennessCentrality(metrics *CentralityMetrics) {
	// Simplified betweenness centrality calculation
	for nodeID := range ia.graph.nodes {
		betweenness := ia.calculateNodeBetweennessCentrality(nodeID)
		metrics.BetweennessCentrality[nodeID] = betweenness
	}
}

func (ia *influenceAnalyzerImpl) calculateClosenessCentrality(metrics *CentralityMetrics) {
	for nodeID := range ia.graph.nodes {
		closeness := ia.calculateNodeClosenessCentrality(nodeID)
		metrics.ClosenessCentrality[nodeID] = closeness
	}
}

func (ia *influenceAnalyzerImpl) calculateEigenvectorCentrality(metrics *CentralityMetrics) {
	// Simplified eigenvector centrality using power iteration
	nodeIDs := make([]string, 0, len(ia.graph.nodes))
	for nodeID := range ia.graph.nodes {
		nodeIDs = append(nodeIDs, nodeID)
	}
	
	n := len(nodeIDs)
	if n == 0 {
		return
	}
	
	// Initialize eigenvector
	eigenvector := make([]float64, n)
	for i := range eigenvector {
		eigenvector[i] = 1.0 / float64(n)
	}
	
	// Power iteration
	for iter := 0; iter < ia.maxIterations; iter++ {
		newEigenvector := make([]float64, n)
		
		for i, nodeID := range nodeIDs {
			sum := 0.0
			if influencedBy, exists := ia.graph.influencedBy[nodeID]; exists {
				for _, influencerID := range influencedBy {
					for j, otherNodeID := range nodeIDs {
						if otherNodeID == influencerID {
							sum += eigenvector[j]
							break
						}
					}
				}
			}
			newEigenvector[i] = sum
		}
		
		// Normalize
		norm := 0.0
		for _, val := range newEigenvector {
			norm += val * val
		}
		norm = math.Sqrt(norm)
		
		if norm > 0 {
			for i := range newEigenvector {
				newEigenvector[i] /= norm
			}
		}
		
		// Check convergence
		diff := 0.0
		for i := range eigenvector {
			diff += math.Abs(eigenvector[i] - newEigenvector[i])
		}
		
		eigenvector = newEigenvector
		
		if diff < ia.convergenceThreshold {
			break
		}
	}
	
	// Store results
	for i, nodeID := range nodeIDs {
		metrics.EigenvectorCentrality[nodeID] = eigenvector[i]
	}
}

func (ia *influenceAnalyzerImpl) calculatePageRank(metrics *CentralityMetrics) {
	nodeIDs := make([]string, 0, len(ia.graph.nodes))
	for nodeID := range ia.graph.nodes {
		nodeIDs = append(nodeIDs, nodeID)
	}
	
	n := len(nodeIDs)
	if n == 0 {
		return
	}
	
	// Initialize PageRank values
	pageRank := make([]float64, n)
	newPageRank := make([]float64, n)
	
	for i := range pageRank {
		pageRank[i] = 1.0 / float64(n)
	}
	
	// PageRank iteration
	for iter := 0; iter < ia.maxIterations; iter++ {
		for i := range newPageRank {
			newPageRank[i] = (1.0 - ia.dampingFactor) / float64(n)
		}
		
		for i, nodeID := range nodeIDs {
			if influences, exists := ia.graph.influences[nodeID]; exists && len(influences) > 0 {
				contribution := ia.dampingFactor * pageRank[i] / float64(len(influences))
				
				for _, influencedID := range influences {
					for j, otherNodeID := range nodeIDs {
						if otherNodeID == influencedID {
							newPageRank[j] += contribution
							break
						}
					}
				}
			}
		}
		
		// Check convergence
		diff := 0.0
		for i := range pageRank {
			diff += math.Abs(pageRank[i] - newPageRank[i])
		}
		
		copy(pageRank, newPageRank)
		
		if diff < ia.convergenceThreshold {
			break
		}
	}
	
	// Store results
	for i, nodeID := range nodeIDs {
		metrics.PageRank[nodeID] = pageRank[i]
	}
}

// More helper methods

func (ia *influenceAnalyzerImpl) getNeighbors(nodeID string) []string {
	neighbors := make([]string, 0)
	
	if influences, exists := ia.graph.influences[nodeID]; exists {
		neighbors = append(neighbors, influences...)
	}
	
	if influencedBy, exists := ia.graph.influencedBy[nodeID]; exists {
		neighbors = append(neighbors, influencedBy...)
	}
	
	return neighbors
}

func (ia *influenceAnalyzerImpl) areConnected(nodeID1, nodeID2 string) bool {
	if influences, exists := ia.graph.influences[nodeID1]; exists {
		for _, influenced := range influences {
			if influenced == nodeID2 {
				return true
			}
		}
	}
	
	if influences, exists := ia.graph.influences[nodeID2]; exists {
		for _, influenced := range influences {
			if influenced == nodeID1 {
				return true
			}
		}
	}
	
	return false
}

func (ia *influenceAnalyzerImpl) findShortestPathLength(fromID, toID string) int {
	if fromID == toID {
		return 0
	}
	
	visited := make(map[string]bool)
	queue := []struct {
		nodeID string
		depth  int
	}{{fromID, 0}}
	
	for len(queue) > 0 {
		current := queue[0]
		queue = queue[1:]
		
		if visited[current.nodeID] {
			continue
		}
		visited[current.nodeID] = true
		
		if current.nodeID == toID {
			return current.depth
		}
		
		// Add neighbors
		neighbors := ia.getNeighbors(current.nodeID)
		for _, neighbor := range neighbors {
			if !visited[neighbor] {
				queue = append(queue, struct {
					nodeID string
					depth  int
				}{neighbor, current.depth + 1})
			}
		}
	}
	
	return -1 // No path found
}

func (ia *influenceAnalyzerImpl) getNodeCentrality(nodeID string) float64 {
	// Simple centrality based on degree
	influences := len(ia.graph.influences[nodeID])
	influencedBy := len(ia.graph.influencedBy[nodeID])
	totalNodes := len(ia.graph.nodes)
	
	if totalNodes <= 1 {
		return 0
	}
	
	return float64(influences+influencedBy) / float64(totalNodes-1)
}

func (ia *influenceAnalyzerImpl) calculateNodeDegreeCentrality(nodeID string) float64 {
	return ia.getNodeCentrality(nodeID)
}

func (ia *influenceAnalyzerImpl) calculateNodeBetweennessCentrality(nodeID string) float64 {
	// Simplified betweenness: count how many shortest paths pass through this node
	// This is computationally expensive, so we use an approximation
	throughCount := 0
	totalPaths := 0
	
	// Sample a subset of node pairs
	nodeIDs := make([]string, 0, len(ia.graph.nodes))
	for id := range ia.graph.nodes {
		if id != nodeID {
			nodeIDs = append(nodeIDs, id)
		}
	}
	
	sampleSize := min(20, len(nodeIDs))
	for i := 0; i < sampleSize; i++ {
		for j := i + 1; j < sampleSize; j++ {
			if ia.isOnShortestPath(nodeIDs[i], nodeIDs[j], nodeID) {
				throughCount++
			}
			totalPaths++
		}
	}
	
	if totalPaths == 0 {
		return 0
	}
	
	return float64(throughCount) / float64(totalPaths)
}

func (ia *influenceAnalyzerImpl) calculateNodeClosenessCentrality(nodeID string) float64 {
	totalDistance := 0
	reachableNodes := 0
	
	// Calculate distances to all other nodes
	for otherNodeID := range ia.graph.nodes {
		if otherNodeID != nodeID {
			distance := ia.findShortestPathLength(nodeID, otherNodeID)
			if distance > 0 {
				totalDistance += distance
				reachableNodes++
			}
		}
	}
	
	if reachableNodes == 0 || totalDistance == 0 {
		return 0
	}
	
	return float64(reachableNodes) / float64(totalDistance)
}

func (ia *influenceAnalyzerImpl) calculateNodePageRank(nodeID string) float64 {
	// This is already calculated in calculatePageRank, so we'll use a simple approximation
	influences := len(ia.graph.influences[nodeID])
	influencedBy := len(ia.graph.influencedBy[nodeID])
	
	// Simple approximation based on in-degree with damping
	totalNodes := float64(len(ia.graph.nodes))
	if totalNodes == 0 {
		return 0
	}
	
	return ((1.0-ia.dampingFactor)/totalNodes + ia.dampingFactor*float64(influencedBy)/totalNodes)
}

func (ia *influenceAnalyzerImpl) expandCommunity(startNodeID string, visited map[string]bool) []ucxl.Address {
	community := make([]ucxl.Address, 0)
	queue := []string{startNodeID}
	visited[startNodeID] = true
	
	for len(queue) > 0 {
		nodeID := queue[0]
		queue = queue[1:]
		
		if node, exists := ia.graph.nodes[nodeID]; exists {
			community = append(community, node.UCXLAddress)
		}
		
		// Add strongly connected neighbors
		neighbors := ia.getNeighbors(nodeID)
		for _, neighborID := range neighbors {
			if !visited[neighborID] && ia.isStronglyConnected(nodeID, neighborID) {
				visited[neighborID] = true
				queue = append(queue, neighborID)
			}
		}
	}
	
	return community
}

func (ia *influenceAnalyzerImpl) isStronglyConnected(nodeID1, nodeID2 string) bool {
	// Check if nodes have bidirectional influence or share many common neighbors
	commonNeighbors := 0
	neighbors1 := ia.getNeighbors(nodeID1)
	neighbors2 := ia.getNeighbors(nodeID2)
	
	for _, n1 := range neighbors1 {
		for _, n2 := range neighbors2 {
			if n1 == n2 {
				commonNeighbors++
			}
		}
	}
	
	return commonNeighbors >= 2 || (ia.areConnected(nodeID1, nodeID2) && commonNeighbors >= 1)
}

func (ia *influenceAnalyzerImpl) calculateCommunityModularity(community []ucxl.Address) float64 {
	// Simplified modularity calculation
	if len(community) < 2 {
		return 0
	}
	
	// Count internal edges
	internalEdges := 0
	for _, addr1 := range community {
		for _, addr2 := range community {
			if addr1.String() != addr2.String() {
				// Find nodes for these addresses
				node1 := ia.findNodeByAddress(addr1)
				node2 := ia.findNodeByAddress(addr2)
				if node1 != nil && node2 != nil && ia.areConnected(node1.ID, node2.ID) {
					internalEdges++
				}
			}
		}
	}
	
	// Simple modularity approximation
	maxPossibleEdges := len(community) * (len(community) - 1)
	if maxPossibleEdges == 0 {
		return 0
	}
	
	return float64(internalEdges) / float64(maxPossibleEdges)
}

func (ia *influenceAnalyzerImpl) calculateCommunityDensity(community []ucxl.Address) float64 {
	return ia.calculateCommunityModularity(community) // Same calculation for simplicity
}

func (ia *influenceAnalyzerImpl) findNodeByAddress(address ucxl.Address) *TemporalNode {
	addressKey := address.String()
	if nodes, exists := ia.graph.addressToNodes[addressKey]; exists && len(nodes) > 0 {
		return nodes[len(nodes)-1] // Return latest version
	}
	return nil
}

func (ia *influenceAnalyzerImpl) calculateTechnologySimilarity(node1, node2 *TemporalNode) float64 {
	if node1.Context == nil || node2.Context == nil {
		return 0
	}
	
	tech1 := make(map[string]bool)
	for _, tech := range node1.Context.Technologies {
		tech1[tech] = true
	}
	
	tech2 := make(map[string]bool)
	for _, tech := range node2.Context.Technologies {
		tech2[tech] = true
	}
	
	intersection := 0
	union := len(tech1)
	
	for tech := range tech2 {
		if tech1[tech] {
			intersection++
		} else {
			union++
		}
	}
	
	if union == 0 {
		return 0
	}
	
	return float64(intersection) / float64(union) // Jaccard similarity
}

func (ia *influenceAnalyzerImpl) countCommonInfluencers(node1, node2 *TemporalNode) int {
	influencers1 := make(map[string]bool)
	if influencedBy1, exists := ia.graph.influencedBy[node1.ID]; exists {
		for _, influencer := range influencedBy1 {
			influencers1[influencer] = true
		}
	}
	
	common := 0
	if influencedBy2, exists := ia.graph.influencedBy[node2.ID]; exists {
		for _, influencer := range influencedBy2 {
			if influencers1[influencer] {
				common++
			}
		}
	}
	
	return common
}

func (ia *influenceAnalyzerImpl) isOnShortestPath(fromID, toID, throughID string) bool {
	// Check if throughID is on any shortest path from fromID to toID
	directDistance := ia.findShortestPathLength(fromID, toID)
	if directDistance <= 0 {
		return false
	}
	
	throughDistance := ia.findShortestPathLength(fromID, throughID) + ia.findShortestPathLength(throughID, toID)
	
	return throughDistance == directDistance
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}