package temporal

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

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

// decisionNavigatorImpl implements the DecisionNavigator interface
type decisionNavigatorImpl struct {
	mu sync.RWMutex
	
	// Reference to the temporal graph
	graph *temporalGraphImpl
	
	// Navigation state
	navigationSessions map[string]*NavigationSession
	bookmarks          map[string]*DecisionBookmark
	
	// Configuration
	maxNavigationHistory int
}

// NavigationSession represents a navigation session
type NavigationSession struct {
	ID               string           `json:"id"`
	UserID           string           `json:"user_id"`
	StartedAt        time.Time        `json:"started_at"`
	LastActivity     time.Time        `json:"last_activity"`
	CurrentPosition  ucxl.Address     `json:"current_position"`
	History          []*DecisionStep  `json:"history"`
	Bookmarks        []string         `json:"bookmarks"`
	Preferences      *NavPreferences  `json:"preferences"`
}

// NavPreferences represents navigation preferences
type NavPreferences struct {
	MaxHops              int     `json:"max_hops"`
	PreferRecentDecisions bool    `json:"prefer_recent_decisions"`
	FilterByConfidence   float64 `json:"filter_by_confidence"`
	IncludeStaleContexts bool    `json:"include_stale_contexts"`
}

// NewDecisionNavigator creates a new decision navigator
func NewDecisionNavigator(graph *temporalGraphImpl) DecisionNavigator {
	return &decisionNavigatorImpl{
		graph:                graph,
		navigationSessions:   make(map[string]*NavigationSession),
		bookmarks:           make(map[string]*DecisionBookmark),
		maxNavigationHistory: 100,
	}
}

// NavigateDecisionHops navigates by decision distance, not time
func (dn *decisionNavigatorImpl) NavigateDecisionHops(ctx context.Context, address ucxl.Address, 
	hops int, direction NavigationDirection) (*TemporalNode, error) {
	
	dn.mu.RLock()
	defer dn.mu.RUnlock()
	
	// Get starting node
	startNode, err := dn.graph.getLatestNodeUnsafe(address)
	if err != nil {
		return nil, fmt.Errorf("failed to get starting node: %w", err)
	}
	
	// Navigate by hops
	currentNode := startNode
	for i := 0; i < hops; i++ {
		nextNode, err := dn.navigateOneHop(currentNode, direction)
		if err != nil {
			return nil, fmt.Errorf("failed to navigate hop %d: %w", i+1, err)
		}
		currentNode = nextNode
	}
	
	return currentNode, nil
}

// GetDecisionTimeline gets timeline ordered by decision sequence
func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, address ucxl.Address, 
	includeRelated bool, maxHops int) (*DecisionTimeline, error) {
	
	dn.mu.RLock()
	defer dn.mu.RUnlock()
	
	// Get evolution history for the primary address
	history, err := dn.graph.GetEvolutionHistory(ctx, address)
	if err != nil {
		return nil, fmt.Errorf("failed to get evolution history: %w", err)
	}
	
	// Build decision timeline entries
	decisionSequence := make([]*DecisionTimelineEntry, len(history))
	for i, node := range history {
		entry := &DecisionTimelineEntry{
			Version:             node.Version,
			DecisionHop:         node.Version, // Version number as decision hop
			ChangeReason:        node.ChangeReason,
			DecisionMaker:       dn.getDecisionMaker(node),
			DecisionRationale:   dn.getDecisionRationale(node),
			ConfidenceEvolution: node.Confidence,
			Timestamp:           node.Timestamp,
			InfluencesCount:     len(node.Influences),
			InfluencedByCount:   len(node.InfluencedBy),
			ImpactScope:         node.ImpactScope,
			Metadata:            make(map[string]interface{}),
		}
		decisionSequence[i] = entry
	}
	
	// Get related decisions if requested
	relatedDecisions := make([]*RelatedDecision, 0)
	if includeRelated && maxHops > 0 {
		relatedPaths, err := dn.graph.FindRelatedDecisions(ctx, address, maxHops)
		if err == nil {
			for _, path := range relatedPaths {
				if len(path.Steps) > 0 {
					lastStep := path.Steps[len(path.Steps)-1]
					related := &RelatedDecision{
						Address:               path.To,
						DecisionHops:          path.TotalHops,
						LatestVersion:         lastStep.TemporalNode.Version,
						ChangeReason:          lastStep.TemporalNode.ChangeReason,
						DecisionMaker:         dn.getDecisionMaker(lastStep.TemporalNode),
						Confidence:            lastStep.TemporalNode.Confidence,
						LastDecisionTimestamp: lastStep.TemporalNode.Timestamp,
						RelationshipType:      lastStep.Relationship,
					}
					relatedDecisions = append(relatedDecisions, related)
				}
			}
		}
	}
	
	// Calculate timeline analysis
	analysis := dn.analyzeTimeline(decisionSequence, relatedDecisions)
	
	// Calculate time span
	var timeSpan time.Duration
	if len(history) > 1 {
		timeSpan = history[len(history)-1].Timestamp.Sub(history[0].Timestamp)
	}
	
	timeline := &DecisionTimeline{
		PrimaryAddress:   address,
		DecisionSequence: decisionSequence,
		RelatedDecisions: relatedDecisions,
		TotalDecisions:   len(decisionSequence),
		TimeSpan:         timeSpan,
		AnalysisMetadata: analysis,
	}
	
	return timeline, nil
}

// FindStaleContexts finds contexts that may be outdated based on decisions
func (dn *decisionNavigatorImpl) FindStaleContexts(ctx context.Context, stalenessThreshold float64) ([]*StaleContext, error) {
	dn.mu.RLock()
	defer dn.mu.RUnlock()
	
	staleContexts := make([]*StaleContext, 0)
	
	// Check all nodes for staleness
	for _, node := range dn.graph.nodes {
		if node.Staleness >= stalenessThreshold {
			staleness := &StaleContext{
				UCXLAddress:    node.UCXLAddress,
				TemporalNode:   node,
				StalenessScore: node.Staleness,
				LastUpdated:    node.Timestamp,
				Reasons:        dn.getStalenessReasons(node),
				SuggestedActions: dn.getSuggestedActions(node),
				RelatedChanges: dn.getRelatedChanges(node),
				Priority:       dn.calculateStalePriority(node),
			}
			staleContexts = append(staleContexts, staleness)
		}
	}
	
	// Sort by staleness score (highest first)
	sort.Slice(staleContexts, func(i, j int) bool {
		return staleContexts[i].StalenessScore > staleContexts[j].StalenessScore
	})
	
	return staleContexts, nil
}

// ValidateDecisionPath validates that a decision path is reachable
func (dn *decisionNavigatorImpl) ValidateDecisionPath(ctx context.Context, path []*DecisionStep) error {
	if len(path) == 0 {
		return fmt.Errorf("empty decision path")
	}
	
	dn.mu.RLock()
	defer dn.mu.RUnlock()
	
	// Validate each step in the path
	for i, step := range path {
		// Check if the temporal node exists
		if step.TemporalNode == nil {
			return fmt.Errorf("step %d has nil temporal node", i)
		}
		
		nodeID := step.TemporalNode.ID
		if _, exists := dn.graph.nodes[nodeID]; !exists {
			return fmt.Errorf("step %d references non-existent node %s", i, nodeID)
		}
		
		// Validate hop distance
		if step.HopDistance != i {
			return fmt.Errorf("step %d has incorrect hop distance: expected %d, got %d", 
				i, i, step.HopDistance)
		}
		
		// Validate relationship to next step
		if i < len(path)-1 {
			nextStep := path[i+1]
			if !dn.validateStepRelationship(step, nextStep) {
				return fmt.Errorf("invalid relationship between step %d and %d", i, i+1)
			}
		}
	}
	
	return nil
}

// GetNavigationHistory gets navigation history for a session
func (dn *decisionNavigatorImpl) GetNavigationHistory(ctx context.Context, sessionID string) ([]*DecisionStep, error) {
	dn.mu.RLock()
	defer dn.mu.RUnlock()
	
	session, exists := dn.navigationSessions[sessionID]
	if !exists {
		return nil, fmt.Errorf("navigation session %s not found", sessionID)
	}
	
	// Return a copy of the history
	history := make([]*DecisionStep, len(session.History))
	copy(history, session.History)
	
	return history, nil
}

// ResetNavigation resets navigation state to latest versions
func (dn *decisionNavigatorImpl) ResetNavigation(ctx context.Context, address ucxl.Address) error {
	dn.mu.Lock()
	defer dn.mu.Unlock()
	
	// Clear any navigation sessions for this address
	for sessionID, session := range dn.navigationSessions {
		if session.CurrentPosition.String() == address.String() {
			// Reset to latest version
			latestNode, err := dn.graph.getLatestNodeUnsafe(address)
			if err != nil {
				return fmt.Errorf("failed to get latest node: %w", err)
			}
			
			session.CurrentPosition = address
			session.History = []*DecisionStep{}
			session.LastActivity = time.Now()
		}
	}
	
	return nil
}

// BookmarkDecision creates a bookmark for a specific decision point
func (dn *decisionNavigatorImpl) BookmarkDecision(ctx context.Context, address ucxl.Address, hop int, name string) error {
	dn.mu.Lock()
	defer dn.mu.Unlock()
	
	// Validate the decision point exists
	node, err := dn.graph.GetVersionAtDecision(ctx, address, hop)
	if err != nil {
		return fmt.Errorf("decision point not found: %w", err)
	}
	
	// Create bookmark
	bookmarkID := fmt.Sprintf("%s-%d-%d", address.String(), hop, time.Now().Unix())
	bookmark := &DecisionBookmark{
		ID:          bookmarkID,
		Name:        name,
		Description: fmt.Sprintf("Decision at hop %d for %s", hop, address.String()),
		Address:     address,
		DecisionHop: hop,
		CreatedBy:   "system", // Could be passed as parameter
		CreatedAt:   time.Now(),
		Tags:        []string{},
		Metadata:    make(map[string]interface{}),
	}
	
	// Add context information to metadata
	bookmark.Metadata["change_reason"] = node.ChangeReason
	bookmark.Metadata["decision_id"] = node.DecisionID
	bookmark.Metadata["confidence"] = node.Confidence
	
	dn.bookmarks[bookmarkID] = bookmark
	
	return nil
}

// ListBookmarks lists all bookmarks for navigation
func (dn *decisionNavigatorImpl) ListBookmarks(ctx context.Context) ([]*DecisionBookmark, error) {
	dn.mu.RLock()
	defer dn.mu.RUnlock()
	
	bookmarks := make([]*DecisionBookmark, 0, len(dn.bookmarks))
	for _, bookmark := range dn.bookmarks {
		bookmarks = append(bookmarks, bookmark)
	}
	
	// Sort by creation time (newest first)
	sort.Slice(bookmarks, func(i, j int) bool {
		return bookmarks[i].CreatedAt.After(bookmarks[j].CreatedAt)
	})
	
	return bookmarks, nil
}

// Helper methods

func (dn *decisionNavigatorImpl) navigateOneHop(currentNode *TemporalNode, direction NavigationDirection) (*TemporalNode, error) {
	switch direction {
	case NavigationForward:
		return dn.navigateForward(currentNode)
	case NavigationBackward:
		return dn.navigateBackward(currentNode)
	default:
		return nil, fmt.Errorf("invalid navigation direction: %s", direction)
	}
}

func (dn *decisionNavigatorImpl) navigateForward(currentNode *TemporalNode) (*TemporalNode, error) {
	// Forward navigation means going to a newer decision
	addressKey := currentNode.UCXLAddress.String()
	nodes, exists := dn.graph.addressToNodes[addressKey]
	if !exists {
		return nil, fmt.Errorf("no nodes found for address")
	}
	
	// Find current node in the list and get the next one
	for i, node := range nodes {
		if node.ID == currentNode.ID && i < len(nodes)-1 {
			return nodes[i+1], nil
		}
	}
	
	return nil, fmt.Errorf("no forward navigation possible")
}

func (dn *decisionNavigatorImpl) navigateBackward(currentNode *TemporalNode) (*TemporalNode, error) {
	// Backward navigation means going to an older decision
	if currentNode.ParentNode == nil {
		return nil, fmt.Errorf("no backward navigation possible: no parent node")
	}
	
	parentNode, exists := dn.graph.nodes[*currentNode.ParentNode]
	if !exists {
		return nil, fmt.Errorf("parent node not found: %s", *currentNode.ParentNode)
	}
	
	return parentNode, nil
}

func (dn *decisionNavigatorImpl) getDecisionMaker(node *TemporalNode) string {
	if decision, exists := dn.graph.decisions[node.DecisionID]; exists {
		return decision.Maker
	}
	return "unknown"
}

func (dn *decisionNavigatorImpl) getDecisionRationale(node *TemporalNode) string {
	if decision, exists := dn.graph.decisions[node.DecisionID]; exists {
		return decision.Rationale
	}
	return ""
}

func (dn *decisionNavigatorImpl) analyzeTimeline(sequence []*DecisionTimelineEntry, related []*RelatedDecision) *TimelineAnalysis {
	if len(sequence) == 0 {
		return &TimelineAnalysis{
			AnalyzedAt: time.Now(),
		}
	}
	
	// Calculate change velocity
	var changeVelocity float64
	if len(sequence) > 1 {
		firstTime := sequence[0].Timestamp
		lastTime := sequence[len(sequence)-1].Timestamp
		duration := lastTime.Sub(firstTime)
		if duration > 0 {
			changeVelocity = float64(len(sequence)-1) / duration.Hours()
		}
	}
	
	// Analyze confidence trend
	confidenceTrend := "stable"
	if len(sequence) > 1 {
		firstConfidence := sequence[0].ConfidenceEvolution
		lastConfidence := sequence[len(sequence)-1].ConfidenceEvolution
		diff := lastConfidence - firstConfidence
		
		if diff > 0.1 {
			confidenceTrend = "increasing"
		} else if diff < -0.1 {
			confidenceTrend = "decreasing"
		}
	}
	
	// Count change reasons
	reasonCounts := make(map[ChangeReason]int)
	for _, entry := range sequence {
		reasonCounts[entry.ChangeReason]++
	}
	
	// Find dominant reasons
	dominantReasons := make([]ChangeReason, 0)
	maxCount := 0
	for reason, count := range reasonCounts {
		if count > maxCount {
			maxCount = count
			dominantReasons = []ChangeReason{reason}
		} else if count == maxCount {
			dominantReasons = append(dominantReasons, reason)
		}
	}
	
	// Count decision makers
	makerCounts := make(map[string]int)
	for _, entry := range sequence {
		makerCounts[entry.DecisionMaker]++
	}
	
	// Count impact scope distribution
	scopeCounts := make(map[ImpactScope]int)
	for _, entry := range sequence {
		scopeCounts[entry.ImpactScope]++
	}
	
	return &TimelineAnalysis{
		ChangeVelocity:          changeVelocity,
		ConfidenceTrend:         confidenceTrend,
		DominantChangeReasons:   dominantReasons,
		DecisionMakers:          makerCounts,
		ImpactScopeDistribution: scopeCounts,
		InfluenceNetworkSize:    len(related),
		AnalyzedAt:              time.Now(),
	}
}

func (dn *decisionNavigatorImpl) getStalenessReasons(node *TemporalNode) []string {
	reasons := make([]string, 0)
	
	// Time-based staleness
	timeSinceUpdate := time.Since(node.Timestamp)
	if timeSinceUpdate > 7*24*time.Hour {
		reasons = append(reasons, "not updated in over a week")
	}
	
	// Influence-based staleness
	if len(node.InfluencedBy) > 0 {
		reasons = append(reasons, "influenced by other contexts that may have changed")
	}
	
	// Confidence-based staleness
	if node.Confidence < 0.7 {
		reasons = append(reasons, "low confidence score")
	}
	
	return reasons
}

func (dn *decisionNavigatorImpl) getSuggestedActions(node *TemporalNode) []string {
	actions := make([]string, 0)
	
	actions = append(actions, "review context for accuracy")
	actions = append(actions, "check related decisions for impact")
	
	if node.Confidence < 0.7 {
		actions = append(actions, "improve context confidence through additional analysis")
	}
	
	if len(node.InfluencedBy) > 3 {
		actions = append(actions, "validate dependencies are still accurate")
	}
	
	return actions
}

func (dn *decisionNavigatorImpl) getRelatedChanges(node *TemporalNode) []ucxl.Address {
	// Find contexts that have changed recently and might affect this one
	relatedChanges := make([]ucxl.Address, 0)
	
	cutoff := time.Now().Add(-24 * time.Hour)
	for _, otherNode := range dn.graph.nodes {
		if otherNode.Timestamp.After(cutoff) && otherNode.ID != node.ID {
			// Check if this node influences the stale node
			for _, influenced := range otherNode.Influences {
				if influenced.String() == node.UCXLAddress.String() {
					relatedChanges = append(relatedChanges, otherNode.UCXLAddress)
					break
				}
			}
		}
	}
	
	return relatedChanges
}

func (dn *decisionNavigatorImpl) calculateStalePriority(node *TemporalNode) StalePriority {
	score := node.Staleness
	
	// Adjust based on influence
	if len(node.Influences) > 5 {
		score += 0.2 // Higher priority if it influences many others
	}
	
	// Adjust based on impact scope
	switch node.ImpactScope {
	case ImpactSystem:
		score += 0.3
	case ImpactProject:
		score += 0.2
	case ImpactModule:
		score += 0.1
	}
	
	if score >= 0.9 {
		return PriorityCritical
	} else if score >= 0.7 {
		return PriorityHigh
	} else if score >= 0.5 {
		return PriorityMedium
	}
	return PriorityLow
}

func (dn *decisionNavigatorImpl) validateStepRelationship(step, nextStep *DecisionStep) bool {
	// Check if there's a valid relationship between the steps
	currentNodeID := step.TemporalNode.ID
	nextNodeID := nextStep.TemporalNode.ID
	
	switch step.Relationship {
	case "influences":
		if influences, exists := dn.graph.influences[currentNodeID]; exists {
			for _, influenced := range influences {
				if influenced == nextNodeID {
					return true
				}
			}
		}
	case "influenced_by":
		if influencedBy, exists := dn.graph.influencedBy[currentNodeID]; exists {
			for _, influencer := range influencedBy {
				if influencer == nextNodeID {
					return true
				}
			}
		}
	}
	
	return false
}