package graph

import (
	"fmt"
	"sync"
)

// Task represents a node in the dependency graph
type Task struct {
	ID        string
	Title     string
	Status    string // pending, ready, running, completed, failed
	DependsOn []string
	Metadata  map[string]interface{}
}

// DependencyGraph manages task dependencies
type DependencyGraph struct {
	mu             sync.RWMutex
	tasks          map[string]*Task
	adjacencyList  map[string][]string // task -> dependent tasks
	reverseList    map[string][]string // task -> dependencies
	topologicalOrder []string
	cycleDetected  bool
	status         map[string]string   // task -> status
}

// NewDependencyGraph creates a new dependency graph
func NewDependencyGraph() *DependencyGraph {
	return &DependencyGraph{
		tasks:          make(map[string]*Task),
		adjacencyList:  make(map[string][]string),
		reverseList:    make(map[string][]string),
		topologicalOrder: make([]string, 0),
		status:         make(map[string]string),
	}
}

// AddTask adds a task to the graph
func (dg *DependencyGraph) AddTask(task *Task) error {
	if task == nil || task.ID == "" {
		return fmt.Errorf("task cannot be nil and must have an ID")
	}

	dg.mu.Lock()
	defer dg.mu.Unlock()

	if _, exists := dg.tasks[task.ID]; exists {
		return fmt.Errorf("task already exists: %s", task.ID)
	}

	dg.tasks[task.ID] = task
	dg.status[task.ID] = "pending"

	// Initialize adjacency lists
	if _, exists := dg.adjacencyList[task.ID]; !exists {
		dg.adjacencyList[task.ID] = make([]string, 0)
	}
	if _, exists := dg.reverseList[task.ID]; !exists {
		dg.reverseList[task.ID] = make([]string, 0)
	}

	return nil
}

// AddDependency adds a dependency: dependent depends on prerequisite
func (dg *DependencyGraph) AddDependency(dependent, prerequisite string) error {
	dg.mu.Lock()
	defer dg.mu.Unlock()

	if _, exists := dg.tasks[dependent]; !exists {
		return fmt.Errorf("dependent task not found: %s", dependent)
	}

	if _, exists := dg.tasks[prerequisite]; !exists {
		return fmt.Errorf("prerequisite task not found: %s", prerequisite)
	}

	// Check for duplicate
	for _, dep := range dg.reverseList[dependent] {
		if dep == prerequisite {
			return fmt.Errorf("dependency already exists: %s -> %s", dependent, prerequisite)
		}
	}

	dg.reverseList[dependent] = append(dg.reverseList[dependent], prerequisite)
	dg.adjacencyList[prerequisite] = append(dg.adjacencyList[prerequisite], dependent)

	return nil
}

// ValidateGraph checks for cycles and structural integrity
func (dg *DependencyGraph) ValidateGraph() error {
	dg.mu.Lock()
	defer dg.mu.Unlock()

	// Check for cycles using DFS
	visited := make(map[string]bool)
	recStack := make(map[string]bool)

	for taskID := range dg.tasks {
		if !visited[taskID] {
			if dg.hasCycleLocked(taskID, visited, recStack) {
				dg.cycleDetected = true
				return fmt.Errorf("cycle detected in dependency graph")
			}
		}
	}

	return nil
}

// hasCycleLocked detects cycles using DFS (must be called with lock held)
func (dg *DependencyGraph) hasCycleLocked(node string, visited, recStack map[string]bool) bool {
	visited[node] = true
	recStack[node] = true

	for _, dep := range dg.reverseList[node] {
		if !visited[dep] {
			if dg.hasCycleLocked(dep, visited, recStack) {
				return true
			}
		} else if recStack[dep] {
			return true
		}
	}

	recStack[node] = false
	return false
}

// GetTopologicalOrder returns tasks in execution order
func (dg *DependencyGraph) GetTopologicalOrder() ([]string, error) {
	dg.mu.Lock()
	defer dg.mu.Unlock()

	if dg.cycleDetected {
		return nil, fmt.Errorf("graph contains cycles")
	}

	// Kahn's algorithm
	inDegree := make(map[string]int)
	for taskID := range dg.tasks {
		inDegree[taskID] = len(dg.reverseList[taskID])
	}

	queue := make([]string, 0)
	for taskID, degree := range inDegree {
		if degree == 0 {
			queue = append(queue, taskID)
		}
	}

	topOrder := make([]string, 0)
	for len(queue) > 0 {
		current := queue[0]
		queue = queue[1:]
		topOrder = append(topOrder, current)

		for _, dependent := range dg.adjacencyList[current] {
			inDegree[dependent]--
			if inDegree[dependent] == 0 {
				queue = append(queue, dependent)
			}
		}
	}

	if len(topOrder) != len(dg.tasks) {
		return nil, fmt.Errorf("topological sort failed - graph may have cycles")
	}

	dg.topologicalOrder = topOrder
	return topOrder, nil
}

// GetReadyTasks returns tasks that have no remaining dependencies
func (dg *DependencyGraph) GetReadyTasks() []string {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	ready := make([]string, 0)

	for taskID, deps := range dg.reverseList {
		allDepsComplete := true
		for _, dep := range deps {
			if dg.status[dep] != "completed" {
				allDepsComplete = false
				break
			}
		}

		if allDepsComplete && dg.status[taskID] == "pending" {
			ready = append(ready, taskID)
		}
	}

	return ready
}

// MarkCompleted marks a task as completed and updates dependents
func (dg *DependencyGraph) MarkCompleted(taskID string) error {
	dg.mu.Lock()
	defer dg.mu.Unlock()

	if _, exists := dg.tasks[taskID]; !exists {
		return fmt.Errorf("task not found: %s", taskID)
	}

	dg.status[taskID] = "completed"
	return nil
}

// MarkFailed marks a task as failed
func (dg *DependencyGraph) MarkFailed(taskID string) error {
	dg.mu.Lock()
	defer dg.mu.Unlock()

	if _, exists := dg.tasks[taskID]; !exists {
		return fmt.Errorf("task not found: %s", taskID)
	}

	dg.status[taskID] = "failed"
	return nil
}

// GetTaskStatus returns the status of a task
func (dg *DependencyGraph) GetTaskStatus(taskID string) (string, error) {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	status, exists := dg.status[taskID]
	if !exists {
		return "", fmt.Errorf("task not found: %s", taskID)
	}

	return status, nil
}

// GetDependencies returns all dependencies of a task
func (dg *DependencyGraph) GetDependencies(taskID string) ([]string, error) {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	deps, exists := dg.reverseList[taskID]
	if !exists {
		return nil, fmt.Errorf("task not found: %s", taskID)
	}

	result := make([]string, len(deps))
	copy(result, deps)
	return result, nil
}

// GetDependents returns all tasks that depend on this task
func (dg *DependencyGraph) GetDependents(taskID string) ([]string, error) {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	deps, exists := dg.adjacencyList[taskID]
	if !exists {
		return nil, fmt.Errorf("task not found: %s", taskID)
	}

	result := make([]string, len(deps))
	copy(result, deps)
	return result, nil
}

// GetTask returns a task by ID
func (dg *DependencyGraph) GetTask(taskID string) (*Task, bool) {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	task, exists := dg.tasks[taskID]
	return task, exists
}

// GetAllTasks returns all tasks
func (dg *DependencyGraph) GetAllTasks() map[string]*Task {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	result := make(map[string]*Task)
	for id, task := range dg.tasks {
		result[id] = task
	}

	return result
}

// GetGraphStats returns statistics about the graph
func (dg *DependencyGraph) GetGraphStats() map[string]interface{} {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	pending := 0
	completed := 0
	failed := 0

	for _, status := range dg.status {
		switch status {
		case "pending":
			pending++
		case "completed":
			completed++
		case "failed":
			failed++
		}
	}

	return map[string]interface{}{
		"total_tasks":      len(dg.tasks),
		"pending_tasks":    pending,
		"completed_tasks":  completed,
		"failed_tasks":     failed,
		"cycle_detected":   dg.cycleDetected,
		"total_edges":      dg.countEdgesLocked(),
	}
}

// countEdgesLocked counts total dependencies (must be called with lock held)
func (dg *DependencyGraph) countEdgesLocked() int {
	count := 0
	for _, deps := range dg.reverseList {
		count += len(deps)
	}
	return count
}

// Clear clears all tasks and dependencies
func (dg *DependencyGraph) Clear() {
	dg.mu.Lock()
	defer dg.mu.Unlock()

	dg.tasks = make(map[string]*Task)
	dg.adjacencyList = make(map[string][]string)
	dg.reverseList = make(map[string][]string)
	dg.topologicalOrder = make([]string, 0)
	dg.status = make(map[string]string)
	dg.cycleDetected = false
}

// CanExecuteTask checks if a task can be executed (all deps complete)
func (dg *DependencyGraph) CanExecuteTask(taskID string) bool {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	deps, exists := dg.reverseList[taskID]
	if !exists {
		return false
	}

	for _, dep := range deps {
		if dg.status[dep] != "completed" {
			return false
		}
	}

	return true
}

// GetCriticalPath returns the longest path through the graph
func (dg *DependencyGraph) GetCriticalPath() []string {
	dg.mu.RLock()
	defer dg.mu.RUnlock()

	// Use longest path algorithm
	distances := make(map[string]int)
	parent := make(map[string]string)

	for taskID := range dg.tasks {
		distances[taskID] = 0
	}

	// Process in topological order
	for _, taskID := range dg.topologicalOrder {
		for _, dependent := range dg.adjacencyList[taskID] {
			if distances[dependent] < distances[taskID]+1 {
				distances[dependent] = distances[taskID] + 1
				parent[dependent] = taskID
			}
		}
	}

	// Find task with maximum distance
	maxDist := 0
	endTask := ""
	for taskID, dist := range distances {
		if dist > maxDist {
			maxDist = dist
			endTask = taskID
		}
	}

	// Reconstruct path
	path := make([]string, 0)
	current := endTask
	for current != "" {
		path = append([]string{current}, path...)
		current = parent[current]
	}

	return path
}
