package routing

import (
	"fmt"
	"strings"

	"github.com/rockliang/poimen/workflows/pkg/db"
)

// CanvasValidator validates React Flow canvas (nodes + edges)
type CanvasValidator struct {
	activityRegistry map[string]bool
}

// NewCanvasValidator creates validator with activity registry
func NewCanvasValidator() *CanvasValidator {
	return &CanvasValidator{
		activityRegistry: map[string]bool{
			"clone-repo":              true,
			"analyze-code":            true,
			"security-scan":           true,
			"generate-report":         true,
			"deployment-precheck":     true,
			"notify-status":           true,
			"approve-workflow":        true,
			"archive-results":         true,
			"retrieve-memory":         true,
			"assume-role":             true,
			"llm-inference":           true,
			"llm-batch-inference":     true,
			"canvas-reasoner":         true,
		},
	}
}

// ValidateCanvas checks canvas structure, connectivity, and DAG
func (cv *CanvasValidator) ValidateCanvas(canvas *db.Canvas) error {
	if canvas == nil {
		return fmt.Errorf("canvas is nil")
	}

	if len(canvas.Nodes) == 0 {
		return fmt.Errorf("canvas has no nodes")
	}

	// Step 1: Validate nodes
	if err := cv.validateNodes(canvas.Nodes); err != nil {
		return fmt.Errorf("node validation failed: %w", err)
	}

	// Step 2: Validate edges
	if err := cv.validateEdges(canvas.Nodes, canvas.Edges); err != nil {
		return fmt.Errorf("edge validation failed: %w", err)
	}

	// Step 3: Check for cycles (must be DAG)
	if err := cv.detectCycles(canvas.Nodes, canvas.Edges); err != nil {
		return fmt.Errorf("cycle detected: %w", err)
	}

	// Step 4: Check connectivity (all nodes reachable from start)
	if err := cv.validateConnectivity(canvas.Nodes, canvas.Edges); err != nil {
		return fmt.Errorf("connectivity check failed: %w", err)
	}

	return nil
}

// validateNodes checks each node has required fields and valid type
func (cv *CanvasValidator) validateNodes(nodes []db.WorkflowNode) error {
	if len(nodes) == 0 {
		return fmt.Errorf("no nodes in canvas")
	}

	nodeIds := make(map[string]bool)

	for i, node := range nodes {
		// Check required fields
		if node.ID == "" {
			return fmt.Errorf("node[%d] has empty ID", i)
		}

		if nodeIds[node.ID] {
			return fmt.Errorf("node[%d] has duplicate ID: %s", i, node.ID)
		}
		nodeIds[node.ID] = true

		if node.Label == "" {
			return fmt.Errorf("node[%d] (%s) has empty label", i, node.ID)
		}

		if node.Position == nil {
			return fmt.Errorf("node[%d] (%s) has no position", i, node.ID)
		}

		// Check activity type (if present)
		if node.Type != "" && !cv.activityRegistry[strings.ToLower(node.Type)] {
			return fmt.Errorf("node[%d] (%s) has unknown activity type: %s", i, node.ID, node.Type)
		}

		// Check data structure
		if node.Data == nil {
			return fmt.Errorf("node[%d] (%s) has no data", i, node.ID)
		}
	}

	return nil
}

// validateEdges checks edges reference valid nodes
func (cv *CanvasValidator) validateEdges(nodes []db.WorkflowNode, edges []db.WorkflowEdge) error {
	nodeIds := make(map[string]bool)
	for _, node := range nodes {
		nodeIds[node.ID] = true
	}

	for i, edge := range edges {
		// Check required fields
		if edge.Source == "" {
			return fmt.Errorf("edge[%d] has empty source", i)
		}

		if edge.Target == "" {
			return fmt.Errorf("edge[%d] has empty target", i)
		}

		// Check source node exists
		if !nodeIds[edge.Source] {
			return fmt.Errorf("edge[%d] references unknown source node: %s", i, edge.Source)
		}

		// Check target node exists
		if !nodeIds[edge.Target] {
			return fmt.Errorf("edge[%d] references unknown target node: %s", i, edge.Target)
		}

		// Check self-loops (discouraged but allow for now)
		if edge.Source == edge.Target {
			// Could warn here but not fail
		}
	}

	return nil
}

// detectCycles checks for cycles in the DAG (must be acyclic)
func (cv *CanvasValidator) detectCycles(nodes []db.WorkflowNode, edges []db.WorkflowEdge) error {
	// Build adjacency list
	graph := make(map[string][]string)
	inDegree := make(map[string]int)

	for _, node := range nodes {
		graph[node.ID] = []string{}
		inDegree[node.ID] = 0
	}

	for _, edge := range edges {
		graph[edge.Source] = append(graph[edge.Source], edge.Target)
		inDegree[edge.Target]++
	}

	// Kahn's algorithm: topological sort
	queue := []string{}
	for _, node := range nodes {
		if inDegree[node.ID] == 0 {
			queue = append(queue, node.ID)
		}
	}

	processed := 0
	for len(queue) > 0 {
		// Dequeue
		current := queue[0]
		queue = queue[1:]
		processed++

		// Visit neighbors
		for _, neighbor := range graph[current] {
			inDegree[neighbor]--
			if inDegree[neighbor] == 0 {
				queue = append(queue, neighbor)
			}
		}
	}

	// If we didn't process all nodes, there's a cycle
	if processed != len(nodes) {
		return fmt.Errorf("graph has cycle (processed %d/%d nodes)", processed, len(nodes))
	}

	return nil
}

// validateConnectivity checks all nodes are reachable from start nodes
func (cv *CanvasValidator) validateConnectivity(nodes []db.WorkflowNode, edges []db.WorkflowEdge) error {
	if len(nodes) == 0 {
		return nil
	}

	// Build adjacency list
	graph := make(map[string][]string)
	inDegree := make(map[string]int)

	for _, node := range nodes {
		graph[node.ID] = []string{}
		inDegree[node.ID] = 0
	}

	for _, edge := range edges {
		graph[edge.Source] = append(graph[edge.Source], edge.Target)
		inDegree[edge.Target]++
	}

	// Find start nodes (in-degree 0)
	startNodes := []string{}
	for _, node := range nodes {
		if inDegree[node.ID] == 0 {
			startNodes = append(startNodes, node.ID)
		}
	}

	if len(startNodes) == 0 {
		return fmt.Errorf("no start nodes found (all nodes have incoming edges)")
	}

	// BFS from all start nodes
	visited := make(map[string]bool)
	queue := startNodes

	for len(queue) > 0 {
		// Dequeue
		current := queue[0]
		queue = queue[1:]

		if visited[current] {
			continue
		}
		visited[current] = true

		// Visit neighbors
		for _, neighbor := range graph[current] {
			if !visited[neighbor] {
				queue = append(queue, neighbor)
			}
		}
	}

	// Check all nodes were visited
	if len(visited) != len(nodes) {
		unreached := []string{}
		for _, node := range nodes {
			if !visited[node.ID] {
				unreached = append(unreached, node.ID)
			}
		}
		return fmt.Errorf("unreachable nodes: %v", unreached)
	}

	return nil
}

// TopoSort returns nodes in topological order (execution order)
func (cv *CanvasValidator) TopoSort(nodes []db.WorkflowNode, edges []db.WorkflowEdge) ([]db.WorkflowNode, error) {
	if len(nodes) == 0 {
		return []db.WorkflowNode{}, nil
	}

	// Build adjacency list and in-degree map
	graph := make(map[string][]string)
	inDegree := make(map[string]int)
	nodeMap := make(map[string]db.WorkflowNode)

	for _, node := range nodes {
		graph[node.ID] = []string{}
		inDegree[node.ID] = 0
		nodeMap[node.ID] = node
	}

	for _, edge := range edges {
		graph[edge.Source] = append(graph[edge.Source], edge.Target)
		inDegree[edge.Target]++
	}

	// Kahn's algorithm
	queue := []string{}
	for _, node := range nodes {
		if inDegree[node.ID] == 0 {
			queue = append(queue, node.ID)
		}
	}

	result := []db.WorkflowNode{}
	processed := make(map[string]bool)

	for len(queue) > 0 {
		// Dequeue
		current := queue[0]
		queue = queue[1:]

		result = append(result, nodeMap[current])
		processed[current] = true

		// Visit neighbors
		for _, neighbor := range graph[current] {
			inDegree[neighbor]--
			if inDegree[neighbor] == 0 {
				queue = append(queue, neighbor)
			}
		}
	}

	if len(result) != len(nodes) {
		return nil, fmt.Errorf("topological sort failed: graph has cycle")
	}

	return result, nil
}
