package workflow

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
)

var idPattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)

// Normalize alanları kırpar, boş dizileri nil olmaktan çıkarır. Doğrulamadan ÖNCE çağrılır.
func Normalize(w *Workflow) {
	w.Name = strings.TrimSpace(w.Name)
	w.Description = strings.TrimSpace(w.Description)
	if w.Nodes == nil {
		w.Nodes = []Node{}
	}
	if w.Edges == nil {
		w.Edges = []Edge{}
	}
	for i := range w.Nodes {
		w.Nodes[i].ID = strings.TrimSpace(w.Nodes[i].ID)
		w.Nodes[i].Label = strings.TrimSpace(w.Nodes[i].Label)
		w.Nodes[i].Type = NodeType(strings.ToLower(strings.TrimSpace(string(w.Nodes[i].Type))))
	}
	for i := range w.Edges {
		e := &w.Edges[i]
		e.ID, e.From, e.To = strings.TrimSpace(e.ID), strings.TrimSpace(e.From), strings.TrimSpace(e.To)
		e.Branch = strings.ToLower(strings.TrimSpace(e.Branch))
		if e.ID == "" {
			e.ID = "e" + fmt.Sprint(i+1) + "-" + e.From + "-" + e.To
		}
	}
}

// Validate akışın YAPISAL geçerliliğini denetler: kaydetmek için yeterli (taslak olabilir).
// Çalıştırılabilirlik için Check kullanılır.
func Validate(w *Workflow) []FieldError {
	var errs []FieldError
	add := func(f, m string) { errs = append(errs, FieldError{Field: f, Message: m}) }

	if w.Name == "" {
		add("name", "ad gerekli")
	} else if runes(w.Name) > MaxNameRunes {
		add("name", "ad en fazla 100 karakter olabilir")
	}
	if runes(w.Description) > MaxDescRunes {
		add("description", "açıklama en fazla 500 karakter olabilir")
	}
	if len(w.Nodes) > MaxNodes {
		add("nodes", fmt.Sprintf("en fazla %d node olabilir", MaxNodes))
		return errs // devamı dev listede anlamsız
	}
	if len(w.Edges) > MaxEdges {
		add("edges", fmt.Sprintf("en fazla %d bağlantı olabilir", MaxEdges))
		return errs
	}

	byID := make(map[string]*Node, len(w.Nodes))
	for i := range w.Nodes {
		n := &w.Nodes[i]
		f := fmt.Sprintf("nodes[%d]", i)
		switch {
		case n.ID == "":
			add(f+".id", "node kimliği gerekli")
			continue
		case len(n.ID) > MaxIDLen || !idPattern.MatchString(n.ID):
			add(f+".id", "kimlik yalnızca harf, rakam, '_' ve '-' içerebilir (en fazla 40)")
			continue
		case reservedVar(n.ID):
			// {{input}} gibi ayrılmış değişkenlerle çakışan kimlik, şablonlarda belirsizlik yaratırdı.
			add(f+".id", fmt.Sprintf("%q ayrılmış bir değişken adı; başka bir kimlik seç", n.ID))
			continue
		}
		if _, dup := byID[n.ID]; dup {
			add(f+".id", "bu kimlik tekrar ediyor: "+n.ID)
			continue
		}
		byID[n.ID] = n
		if !n.Type.Valid() {
			add(f+".type", "bilinmeyen node türü: "+string(n.Type))
		}
		if runes(n.Label) > MaxLabelRunes {
			add(f+".label", "etiket en fazla 100 karakter olabilir")
		}
		if raw, err := json.Marshal(n.Config); err != nil {
			add(f+".config", "config JSON'a çevrilemiyor")
		} else if len(raw) > MaxConfigBytes {
			add(f+".config", "config en fazla 16 KB olabilir")
		}
	}

	seenOut := map[string]string{} // from|branch → edge id
	edgeIDs := map[string]struct{}{}
	for i, e := range w.Edges {
		f := fmt.Sprintf("edges[%d]", i)
		if _, dup := edgeIDs[e.ID]; dup {
			add(f+".id", "bağlantı kimliği tekrar ediyor: "+e.ID)
		}
		edgeIDs[e.ID] = struct{}{}
		from, okF := byID[e.From]
		_, okT := byID[e.To]
		if !okF {
			add(f+".from", "kaynak node yok: "+e.From)
		}
		if !okT {
			add(f+".to", "hedef node yok: "+e.To)
		}
		if e.From == e.To && e.From != "" {
			add(f, "node kendine bağlanamaz")
		}
		if okF && !branchAllowed(from.Type, e.Branch) {
			add(f+".branch", fmt.Sprintf("%s node'u için %q dalı geçersiz", from.Type, e.Branch))
		}
		key := e.From + "|" + e.Branch
		if prev, dup := seenOut[key]; dup {
			add(f, fmt.Sprintf("%s node'unun %q çıkışı zaten %s bağlantısında kullanılıyor", e.From, e.Branch, prev))
		}
		seenOut[key] = e.ID
		if okT && byID[e.To].Type == NodeInput {
			add(f+".to", "input node'una bağlantı girilemez")
		}
	}
	if len(errs) == 0 {
		if cyc := findCycle(w); cyc != "" {
			add("edges", "döngü var (node'lar geri bağlanamaz; tekrar için loop node'u kullan): "+cyc)
		}
	}
	return errs
}

// branchAllowed bir node türünün hangi dallardan çıkabileceğini belirler.
func branchAllowed(t NodeType, branch string) bool {
	switch t {
	case NodeCondition:
		return branch == BranchTrue || branch == BranchFalse
	case NodeLoop:
		return branch == BranchBody || branch == BranchNext
	default:
		return branch == BranchNext
	}
}

// findCycle kenarlar arasında döngü bulursa üyelerinden birini döndürür (yoksa "").
func findCycle(w *Workflow) string {
	adj := map[string][]string{}
	for _, e := range w.Edges {
		adj[e.From] = append(adj[e.From], e.To)
	}
	const (
		white = 0
		grey  = 1
		black = 2
	)
	color := map[string]int{}
	var dfs func(string) string
	dfs = func(u string) string {
		color[u] = grey
		for _, v := range adj[u] {
			switch color[v] {
			case grey:
				return v
			case white:
				if c := dfs(v); c != "" {
					return c
				}
			}
		}
		color[u] = black
		return ""
	}
	for _, n := range w.Nodes {
		if color[n.ID] == white {
			if c := dfs(n.ID); c != "" {
				return c
			}
		}
	}
	return ""
}

// Check akışın ÇALIŞTIRILABİLİR olup olmadığını denetler (Validate'in üstüne): tek input, en az bir
// output, her node input'tan ulaşılabilir, her türün zorunlu config'i dolu.
func Check(w *Workflow) []FieldError {
	errs := Validate(w)
	if len(errs) > 0 {
		return errs
	}
	add := func(f, m string) { errs = append(errs, FieldError{Field: f, Message: m}) }

	var inputID string
	inputs, outputs := 0, 0
	for _, n := range w.Nodes {
		switch n.Type {
		case NodeInput:
			inputs++
			inputID = n.ID
		case NodeOutput:
			outputs++
		}
	}
	if inputs != 1 {
		add("nodes", fmt.Sprintf("tam olarak bir input node'u gerekli (var: %d)", inputs))
	}
	if outputs < 1 {
		add("nodes", "en az bir output node'u gerekli")
	}
	if inputs == 1 {
		reach := reachable(w, inputID)
		for i, n := range w.Nodes {
			if !reach[n.ID] {
				add(fmt.Sprintf("nodes[%d]", i), fmt.Sprintf("%q input'tan ulaşılamıyor (bağlantısız node)", n.ID))
			}
		}
	}

	out := map[string]map[string]bool{} // from → dal kümesi
	for _, e := range w.Edges {
		if out[e.From] == nil {
			out[e.From] = map[string]bool{}
		}
		out[e.From][e.Branch] = true
	}
	for i, n := range w.Nodes {
		f := fmt.Sprintf("nodes[%d]", i)
		need := func(key, why string) {
			if strings.TrimSpace(cfgString(n.Config, key)) == "" {
				add(f+".config."+key, why)
			}
		}
		switch n.Type {
		case NodeLLM:
			need("prompt", "LLM node'u için prompt gerekli")
		case NodeTool:
			need("name", "tool node'u için tool adı gerekli")
		case NodeRAG:
			need("query", "RAG node'u için sorgu gerekli")
		case NodeSkill:
			need("query", "skill node'u için sorgu gerekli")
		case NodeTransform:
			need("template", "transform node'u için şablon gerekli")
		case NodeLoop:
			need("items", "loop node'u için items gerekli")
			if !out[n.ID][BranchBody] {
				add(f, "loop node'unun 'body' çıkışı bağlı olmalı")
			}
		case NodeCondition:
			op := strings.ToLower(cfgString(n.Config, "op"))
			if !validOp(op) {
				add(f+".config.op", "geçersiz operatör: "+op)
			}
			if !out[n.ID][BranchTrue] && !out[n.ID][BranchFalse] {
				add(f, "condition node'unun en az bir dalı (true/false) bağlı olmalı")
			}
		}
	}
	return errs
}

// reachable input'tan kenarlar boyunca ulaşılabilen node kümesidir.
func reachable(w *Workflow, start string) map[string]bool {
	adj := map[string][]string{}
	for _, e := range w.Edges {
		adj[e.From] = append(adj[e.From], e.To)
	}
	seen := map[string]bool{start: true}
	stack := []string{start}
	for len(stack) > 0 {
		u := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		for _, v := range adj[u] {
			if !seen[v] {
				seen[v] = true
				stack = append(stack, v)
			}
		}
	}
	return seen
}

// --- config okuma yardımcıları (JSON sayıları float64, Mongo'dan int32/int64 gelebilir) ---

func cfgString(c map[string]any, key string) string {
	if v, ok := c[key]; ok {
		if s, ok := v.(string); ok {
			return s
		}
	}
	return ""
}

func cfgInt(c map[string]any, key string, def int) int {
	switch v := c[key].(type) {
	case float64:
		return int(v)
	case int:
		return v
	case int32:
		return int(v)
	case int64:
		return int(v)
	}
	return def
}

func cfgFloat(c map[string]any, key string) (float64, bool) {
	switch v := c[key].(type) {
	case float64:
		return v, true
	case int:
		return float64(v), true
	case int32:
		return float64(v), true
	case int64:
		return float64(v), true
	}
	return 0, false
}

func cfgBool(c map[string]any, key string) bool {
	b, _ := c[key].(bool)
	return b
}
