package core

import (
	"errors"
	"fmt"
	"sort"

	df "github.com/lucasdataproyects/devfactory/core"
)

// TopologicalSort ordena issues por dependencias usando Kahn's algorithm.
// Función pura: retorna Result con los grupos ordenados.
func TopologicalSort(issues []Issue) df.Result[[]IssueGroup] {
	if len(issues) == 0 {
		return df.Err[[]IssueGroup](errors.New("no issues to sort"))
	}

	// Construir mapa de issues por número
	issueMap := make(map[int]Issue)
	for _, issue := range issues {
		issueMap[issue.Number] = issue
	}

	// Calcular in-degree (solo deps que existen en el set)
	inDegree := make(map[int]int)
	for _, issue := range issues {
		if _, exists := inDegree[issue.Number]; !exists {
			inDegree[issue.Number] = 0
		}
		for _, dep := range issue.Deps {
			if _, exists := issueMap[dep]; exists {
				inDegree[issue.Number]++
			}
		}
	}

	// Kahn's algorithm por niveles (cada nivel = un grupo paralelo)
	var groups []IssueGroup
	resolved := make(map[int]bool)
	remaining := len(issues)
	groupNum := 1

	for remaining > 0 {
		// Encontrar issues con in-degree 0
		var ready []Issue
		for _, issue := range issues {
			if !resolved[issue.Number] && inDegree[issue.Number] == 0 {
				issue.Group = groupNum
				ready = append(ready, issue)
			}
		}

		if len(ready) == 0 {
			// Ciclo detectado
			var cycleNums []int
			for _, issue := range issues {
				if !resolved[issue.Number] {
					cycleNums = append(cycleNums, issue.Number)
				}
			}
			return df.Err[[]IssueGroup](
				fmt.Errorf("circular dependency detected among issues: %v", cycleNums),
			)
		}

		// Ordenar por número dentro del grupo (determinista)
		sort.Slice(ready, func(i, j int) bool {
			return ready[i].Number < ready[j].Number
		})

		groups = append(groups, IssueGroup{
			Number: groupNum,
			Name:   fmt.Sprintf("Grupo %d", groupNum),
			Issues: ready,
		})

		// Marcar como resueltas y decrementar in-degree
		for _, issue := range ready {
			resolved[issue.Number] = true
			remaining--
			// Decrementar in-degree de dependientes
			for _, other := range issues {
				if !resolved[other.Number] {
					for _, dep := range other.Deps {
						if dep == issue.Number {
							inDegree[other.Number]--
						}
					}
				}
			}
		}

		groupNum++
	}

	return df.Ok(groups)
}

// AnalyzeFileConflicts detecta issues que tocan los mismos archivos.
// Recibe un mapa issue_number -> lista de archivos mencionados.
// Retorna pares de issues en conflicto.
func AnalyzeFileConflicts(filesByIssue map[int][]string) []ConflictPair {
	var conflicts []ConflictPair
	nums := sortedKeys(filesByIssue)

	for i := 0; i < len(nums); i++ {
		for j := i + 1; j < len(nums); j++ {
			shared := intersect(filesByIssue[nums[i]], filesByIssue[nums[j]])
			if len(shared) > 0 {
				conflicts = append(conflicts, ConflictPair{
					IssueA:      nums[i],
					IssueB:      nums[j],
					SharedFiles: shared,
				})
			}
		}
	}

	return conflicts
}

// ConflictPair representa dos issues que comparten archivos.
type ConflictPair struct {
	IssueA      int
	IssueB      int
	SharedFiles []string
}

// GeneratePlanMarkdown genera el markdown del plan de ejecución.
// Función pura: recibe grupos, retorna string.
func GeneratePlanMarkdown(groups []IssueGroup, conflicts []ConflictPair) string {
	var b []byte

	b = append(b, "# Plan de Ejecución Paralela\n\n"...)
	b = append(b, fmt.Sprintf("Generado automáticamente. Total: %d issues en %d grupos.\n\n",
		countIssues(groups), len(groups))...)

	if len(conflicts) > 0 {
		b = append(b, "## Conflictos Detectados\n\n"...)
		for _, c := range conflicts {
			b = append(b, fmt.Sprintf("- Issue #%04d y #%04d comparten: %v\n",
				c.IssueA, c.IssueB, c.SharedFiles)...)
		}
		b = append(b, "\n"...)
	}

	for _, group := range groups {
		b = append(b, fmt.Sprintf("## Grupo %d\n\n", group.Number)...)
		for _, issue := range group.Issues {
			depStr := ""
			if len(issue.Deps) > 0 {
				depStr = fmt.Sprintf(" — depende de %s", formatDeps(issue.Deps))
			}
			b = append(b, fmt.Sprintf("- Issue #%04d - %s%s\n", issue.Number, issue.Title, depStr)...)
		}
		b = append(b, "\n"...)
	}

	b = append(b, "## Resumen\n\n"...)
	b = append(b, "| Métrica | Valor |\n"...)
	b = append(b, "|---------|-------|\n"...)
	b = append(b, fmt.Sprintf("| Issues totales | %d |\n", countIssues(groups))...)
	b = append(b, fmt.Sprintf("| Grupos paralelos | %d |\n", len(groups))...)
	if len(groups) > 0 {
		sequential := countIssues(groups)
		parallel := len(groups)
		if sequential > 0 {
			saving := ((sequential - parallel) * 100) / sequential
			b = append(b, fmt.Sprintf("| Ahorro estimado | %d%% |\n", saving)...)
		}
	}

	return string(b)
}

func countIssues(groups []IssueGroup) int {
	return df.Reduce(groups, 0, func(acc int, g IssueGroup) int {
		return acc + len(g.Issues)
	})
}

func formatDeps(deps []int) string {
	strs := df.MapSlice(deps, func(n int) string {
		return fmt.Sprintf("#%04d", n)
	})
	s := ""
	for i, str := range strs {
		if i > 0 {
			s += ", "
		}
		s += str
	}
	return s
}

func sortedKeys(m map[int][]string) []int {
	keys := make([]int, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	return keys
}

func intersect(a, b []string) []string {
	set := make(map[string]bool)
	for _, s := range a {
		set[s] = true
	}
	var result []string
	for _, s := range b {
		if set[s] {
			result = append(result, s)
		}
	}
	return result
}
