package resolver

import (
	"fmt"
	"sort"
	"sync"
)

// DependencyGraph represents a dependency graph for packages
type DependencyGraph struct {
	nodes map[string]*GraphNode
	mu    sync.RWMutex
}

// GraphNode represents a node in the dependency graph
type GraphNode struct {
	Package      *SourcePackage
	Dependencies []string
	Dependents   []string
	Level        int // -1 means not calculated yet
	Built        bool
}

// NewDependencyGraph creates a new dependency graph
func NewDependencyGraph() *DependencyGraph {
	return &DependencyGraph{
		nodes: make(map[string]*GraphNode),
	}
}

// AddPackage adds a package to the graph
func (g *DependencyGraph) AddPackage(pkg *SourcePackage) {
	g.mu.Lock()
	defer g.mu.Unlock()

	if _, exists := g.nodes[pkg.Name]; !exists {
		g.nodes[pkg.Name] = &GraphNode{
			Package:      pkg,
			Dependencies: []string{},
			Dependents:   []string{},
			Level:        -1,
		}
	}
}

// AddDependency adds a dependency relationship: pkg depends on dep
func (g *DependencyGraph) AddDependency(pkg, dep string) {
	g.mu.Lock()
	defer g.mu.Unlock()

	// Ensure both nodes exist
	if _, exists := g.nodes[pkg]; !exists {
		g.nodes[pkg] = &GraphNode{
			Package:      &SourcePackage{Name: pkg},
			Dependencies: []string{},
			Dependents:   []string{},
			Level:        -1,
		}
	}

	if _, exists := g.nodes[dep]; !exists {
		g.nodes[dep] = &GraphNode{
			Package:      &SourcePackage{Name: dep},
			Dependencies: []string{},
			Dependents:   []string{},
			Level:        -1,
		}
	}

	// Add dependency relationship
	pkgNode := g.nodes[pkg]
	depNode := g.nodes[dep]

	// Add dep to pkg's dependencies if not already there
	found := false
	for _, d := range pkgNode.Dependencies {
		if d == dep {
			found = true
			break
		}
	}
	if !found {
		pkgNode.Dependencies = append(pkgNode.Dependencies, dep)
	}

	// Add pkg to dep's dependents if not already there
	found = false
	for _, d := range depNode.Dependents {
		if d == pkg {
			found = true
			break
		}
	}
	if !found {
		depNode.Dependents = append(depNode.Dependents, pkg)
	}
}

// CalculateBuildOrder performs topological sort to determine build order
func (g *DependencyGraph) CalculateBuildOrder() error {
	g.mu.Lock()
	defer g.mu.Unlock()

	// Reset all levels
	for _, node := range g.nodes {
		node.Level = -1
	}

	// Find nodes with no dependencies (level 0)
	queue := make([]*GraphNode, 0)
	for _, node := range g.nodes {
		if len(node.Dependencies) == 0 {
			node.Level = 0
			queue = append(queue, node)
		}
	}

	// BFS to calculate levels
	for len(queue) > 0 {
		current := queue[0]
		queue = queue[1:]

		for _, depName := range current.Dependents {
			depNode := g.nodes[depName]

			// Calculate the maximum level of all dependencies
			maxDepLevel := -1
			for _, dep := range depNode.Dependencies {
				if depNode, exists := g.nodes[dep]; exists {
					if depNode.Level > maxDepLevel {
						maxDepLevel = depNode.Level
					}
				}
			}

			newLevel := maxDepLevel + 1
			if depNode.Level == -1 || depNode.Level < newLevel {
				depNode.Level = newLevel
				queue = append(queue, depNode)
			}
		}
	}

	// Check for cycles (nodes that still have level -1)
	for _, node := range g.nodes {
		if node.Level == -1 {
			return fmt.Errorf("dependency cycle detected involving package %s", node.Package.Name)
		}
	}

	return nil
}

// GetBuildOrder returns packages in build order (dependencies first)
func (g *DependencyGraph) GetBuildOrder() []*SourcePackage {
	g.mu.RLock()
	defer g.mu.RUnlock()

	// Group nodes by level
	levels := make(map[int][]*GraphNode)
	for _, node := range g.nodes {
		levels[node.Level] = append(levels[node.Level], node)
	}

	// Sort levels
	var sortedLevels []int
	for level := range levels {
		sortedLevels = append(sortedLevels, level)
	}
	sort.Ints(sortedLevels)

	// Build result
	result := make([]*SourcePackage, 0)
	for _, level := range sortedLevels {
		nodes := levels[level]

		// Sort nodes within level for deterministic order
		sort.Slice(nodes, func(i, j int) bool {
			return nodes[i].Package.Name < nodes[j].Package.Name
		})

		for _, node := range nodes {
			result = append(result, node.Package)
		}
	}

	return result
}

// GetStats returns graph statistics
func (g *DependencyGraph) GetStats() (nodeCount int, maxLevel int) {
	g.mu.RLock()
	defer g.mu.RUnlock()

	nodeCount = len(g.nodes)
	maxLevel = 0

	for _, node := range g.nodes {
		if node.Level > maxLevel {
			maxLevel = node.Level
		}
	}

	return nodeCount, maxLevel
}

// GetDependencies returns all dependencies for a package
func (g *DependencyGraph) GetDependencies(pkgName string) []string {
	g.mu.RLock()
	defer g.mu.RUnlock()

	if node, exists := g.nodes[pkgName]; exists {
		return append([]string{}, node.Dependencies...)
	}

	return nil
}

// GetDependents returns all packages that depend on the given package
func (g *DependencyGraph) GetDependents(pkgName string) []string {
	g.mu.RLock()
	defer g.mu.RUnlock()

	if node, exists := g.nodes[pkgName]; exists {
		return append([]string{}, node.Dependents...)
	}

	return nil
}
