package workflow

import (
	"fmt"
	"strings"
	"testing"
)

func hasField(errs []FieldError, field string) bool {
	for _, e := range errs {
		if e.Field == field {
			return true
		}
	}
	return false
}

func TestNormalize_TrimsAndFillsEdgeIDs(t *testing.T) {
	w := &Workflow{
		Name:  "  Ad  ",
		Nodes: []Node{{ID: " in ", Type: " INPUT ", Label: " Girdi "}},
		Edges: []Edge{{From: " in ", To: "out"}},
	}
	Normalize(w)
	if w.Name != "Ad" || w.Nodes[0].ID != "in" || w.Nodes[0].Type != NodeInput || w.Nodes[0].Label != "Girdi" {
		t.Errorf("kırpma/küçültme: %+v", w.Nodes[0])
	}
	if w.Edges[0].ID == "" {
		t.Error("kenar kimliği otomatik üretilmeliydi")
	}
	empty := &Workflow{}
	Normalize(empty)
	if empty.Nodes == nil || empty.Edges == nil {
		t.Error("nil diziler boş dilime çevrilmeli (JSON'da null görünmemeli)")
	}
}

// Kaydetme ESNEKTİR: output'u olmayan taslak kaydedilebilmeli; çalıştırma KATI'dır.
func TestValidate_DraftIsSavable(t *testing.T) {
	w := draftWorkflow().Clone()
	if errs := Validate(&w); len(errs) > 0 {
		t.Errorf("taslak reddedildi: %v", errs)
	}
	if errs := Check(&w); len(errs) == 0 {
		t.Error("output'suz akış çalıştırılamaz olmalı")
	}
}

func TestValidate_StructuralErrors(t *testing.T) {
	t.Run("ad gerekli", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		Normalize(&w) // kırpma Normalize'in işidir
		w.Name = "   "
		Normalize(&w)
		if !hasField(Validate(&w), "name") {
			t.Error("yalnızca boşluktan oluşan ad hata vermeli")
		}
	})
	t.Run("node kimliği geçersiz", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Nodes[0].ID = "boşluk var!"
		w.Nodes[1].ID, w.Nodes[2].ID = "t1", "out"
		w.Edges = []Edge{{From: "boşluk var!", To: "t1"}, {From: "t1", To: "out"}}
		Normalize(&w)
		if errs := Validate(&w); !hasField(errs, "nodes[0].id") {
			t.Errorf("geçersiz node kimliği denetlenmedi: %v", errs)
		}
	})
	t.Run("ayrılmış değişken adı", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Nodes[0].ID = "input" // "input" ayrılmış bir değişken adı
		w.Nodes[1].ID, w.Nodes[2].ID = "t1", "out"
		w.Edges = []Edge{{From: "input", To: "t1"}, {From: "t1", To: "out"}}
		Normalize(&w)
		if errs := Validate(&w); !hasField(errs, "nodes[0].id") {
			t.Errorf("ayrılmış ad denetlenmedi: %v", errs)
		}
	})
	t.Run("tekrarlanan node kimliği", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Nodes[1].ID = "in"
		Normalize(&w)
		if errs := Validate(&w); !hasField(errs, "nodes[1].id") {
			t.Errorf("tekrar denetlenmedi: %v", errs)
		}
	})
	t.Run("olmayan hedef", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Edges[0].To = "yok"
		if errs := Validate(&w); !hasField(errs, "edges[0].to") {
			t.Errorf("olmayan hedef denetlenmedi: %v", errs)
		}
	})
	t.Run("aynı çıkış iki kez", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Nodes = append(w.Nodes, wfNode("out2", NodeOutput, nil))
		w.Edges = append(w.Edges, wfEdge("t1", BranchNext, "out2"))
		Normalize(&w)
		if errs := Validate(&w); !hasField(errs, "edges[2]") {
			t.Errorf("çakışan çıkış denetlenmedi: %v", errs)
		}
	})
}

func TestValidate_EdgeRules(t *testing.T) {
	t.Run("condition dalı yanlış", func(t *testing.T) {
		w := testWorkflow(
			[]Node{
				wfNode("in", NodeInput, nil),
				wfNode("cond", NodeCondition, map[string]any{"left": "{{input}}", "op": "contains", "right": "x"}),
				wfNode("out", NodeOutput, nil),
			},
			[]Edge{wfEdge("in", BranchNext, "cond"), wfEdge("cond", BranchNext, "out")},
		)
		if errs := Validate(w); !hasField(errs, "edges[1].branch") {
			t.Errorf("geçersiz dal denetlenmedi: %v", errs)
		}
	})
	t.Run("input'a bağlantı girilemez", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Edges = append(w.Edges, wfEdge("out", BranchNext, "in"))
		Normalize(&w)
		if errs := Validate(&w); !hasField(errs, "edges[2].to") {
			t.Errorf("input'a giriş denetlenmedi: %v", errs)
		}
	})
	t.Run("node kendine bağlanamaz", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		w.Edges = append(w.Edges, wfEdge("t1", BranchNext, "t1"))
		Normalize(&w)
		if errs := Validate(&w); len(errs) == 0 {
			t.Error("kendi kendine bağlantı denetlenmedi")
		}
	})
	t.Run("döngü", func(t *testing.T) {
		// Her node'un tek "" çıkışı olduğundan döngü, iki node arasında kurulur.
		w := testWorkflow(
			[]Node{
				wfNode("in", NodeInput, nil),
				wfNode("a", NodeTransform, map[string]any{"template": "a"}),
				wfNode("b", NodeTransform, map[string]any{"template": "b"}),
			},
			[]Edge{
				wfEdge("in", BranchNext, "a"), wfEdge("a", BranchNext, "b"),
				wfEdge("b", BranchNext, "a"),
			},
		)
		errs := Validate(w)
		if len(errs) == 0 || !strings.Contains(errs[len(errs)-1].Message, "döngü") {
			t.Errorf("döngü denetlenmedi: %v", errs)
		}
	})
	t.Run("node sınırı", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		for i := 0; i < MaxNodes; i++ {
			w.Nodes = append(w.Nodes, wfNode(fmt.Sprintf("n%d", i), NodeTransform, map[string]any{"template": "x"}))
		}
		if errs := Validate(&w); !hasField(errs, "nodes") {
			t.Error("node sınırı denetlenmedi")
		}
	})
}

// Check'in kuralı: tek input, en az bir output, her node input'tan ULAŞILABİLİR olmalı.
func TestCheck_RunRules(t *testing.T) {
	t.Run("iki input", func(t *testing.T) {
		w := testWorkflow(
			[]Node{wfNode("in", NodeInput, nil), wfNode("in2", NodeInput, nil), wfNode("out", NodeOutput, nil)},
			[]Edge{wfEdge("in", BranchNext, "out"), wfEdge("in2", BranchNext, "out")},
		)
		if errs := Check(w); !hasField(errs, "nodes") {
			t.Errorf("birden çok input denetlenmedi: %v", errs)
		}
	})
	t.Run("bağlantısız node", func(t *testing.T) {
		w := testWorkflow(
			[]Node{
				wfNode("in", NodeInput, nil), wfNode("out", NodeOutput, nil),
				wfNode("cop", NodeTransform, map[string]any{"template": "x"}),
			},
			[]Edge{wfEdge("in", BranchNext, "out")},
		)
		if errs := Check(w); !hasField(errs, "nodes[2]") {
			t.Errorf("ulaşılamayan node denetlenmedi: %v", errs)
		}
	})
	t.Run("zorunlu config eksik", func(t *testing.T) {
		w := testWorkflow(
			[]Node{wfNode("in", NodeInput, nil), wfNode("llm1", NodeLLM, nil), wfNode("out", NodeOutput, nil)},
			[]Edge{wfEdge("in", BranchNext, "llm1"), wfEdge("llm1", BranchNext, "out")},
		)
		if errs := Check(w); !hasField(errs, "nodes[1].config.prompt") {
			t.Errorf("LLM prompt zorunluluğu denetlenmedi: %v", errs)
		}
	})
	t.Run("loop gövdesi bağlı değil", func(t *testing.T) {
		w := testWorkflow(
			[]Node{
				wfNode("in", NodeInput, nil),
				wfNode("loop", NodeLoop, map[string]any{"items": `["a"]`}),
				wfNode("out", NodeOutput, nil),
			},
			[]Edge{wfEdge("in", BranchNext, "loop"), wfEdge("loop", BranchNext, "out")},
		)
		if errs := Check(w); !hasField(errs, "nodes[1]") {
			t.Errorf("loop gövde zorunluluğu denetlenmedi: %v", errs)
		}
	})
	t.Run("geçerli akış", func(t *testing.T) {
		w := runnableWorkflow().Clone()
		if errs := Check(&w); len(errs) > 0 {
			t.Errorf("geçerli akış reddedildi: %v", errs)
		}
	})
}

// Deps.Missing yalnızca akışın kullandığı ve bağlı olmayan türleri döndürür (tekrarsız).
func TestDeps_Missing(t *testing.T) {
	w := testWorkflow(
		[]Node{
			wfNode("in", NodeInput, nil),
			wfNode("llm1", NodeLLM, map[string]any{"prompt": "x"}),
			wfNode("llm2", NodeLLM, map[string]any{"prompt": "y"}),
			wfNode("out", NodeOutput, nil),
		},
		[]Edge{wfEdge("in", BranchNext, "llm1"), wfEdge("llm1", BranchNext, "llm2"), wfEdge("llm2", BranchNext, "out")},
	)
	if got := (Deps{}).Missing(w); len(got) != 1 || got[0] != NodeLLM {
		t.Errorf("eksik türler: %v (tek ve tekrarsız LLM bekleniyordu)", got)
	}
	if got := (Deps{LLM: &fakeLLM{}}).Missing(w); len(got) != 0 {
		t.Errorf("bağımlılık bağlıyken eksik sayılmamalı: %v", got)
	}
}
