package workflow_test

import (
	"context"
	"fmt"
	"testing"

	"workflow"
)

// ── helpers ───────────────────────────────────────────────────────────────────

func intPtr(n int) *int { return &n }

// toInt converts various numeric types to int for test assertions.
func toInt(v interface{}) (int, error) {
	switch n := v.(type) {
	case int:
		return n, nil
	case int64:
		return int(n), nil
	case float64:
		return int(n), nil
	case int32:
		return int(n), nil
	default:
		return 0, fmt.Errorf("not a number: %T %v", v, v)
	}
}

func v316Registry(vars ...string) workflow.Registry {
	return workflow.Registry{
		Services:   []string{},
		Components: []string{},
		Vars:       vars,
	}
}

func v316Adapters() *workflow.Adapters {
	return &workflow.Adapters{
		Service:   workflow.NewDefaultServiceAdapter(),
		Component: workflow.NewDefaultComponentAdapter(),
		LLM:       workflow.NewDefaultLLMAdapter(),
	}
}

func mustEngineV316(t *testing.T, wf *workflow.Workflow) *workflow.Engine {
	t.Helper()
	eng, err := workflow.NewEngine(wf)
	if err != nil {
		t.Fatalf("NewEngine: %v", err)
	}
	return eng
}

// ── Validation tests ─────────────────────────────────────────────────────────

func TestV316_Validate_WhileAndSourceMutuallyExclusive(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while+source",
		Registry: v316Registry("$x(INT)"),
		Steps: []workflow.Step{
			{ID: "Loop_test", Source: "=$x", While: "=true", MaxIterations: intPtr(3), Mode: "serial", Children: []string{"Stop_End"}, Next: "Stop_End"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for while + source, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_WhileRequiresMaxIterations(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while-no-max",
		Registry: v316Registry(),
		Steps: []workflow.Step{
			{ID: "Loop_test", While: "=true", Mode: "serial", Children: []string{"Noop_body"}, Next: "Stop_End"},
			{ID: "Noop_body", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for while without maxIterations, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_MaxIterationsLessThan1(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "max-iter-zero",
		Registry: v316Registry(),
		Steps: []workflow.Step{
			{ID: "Loop_test", While: "=true", MaxIterations: intPtr(0), Mode: "serial", Children: []string{"Noop_body"}, Next: "Stop_End"},
			{ID: "Noop_body", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for maxIterations < 1, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_WhileRequiresSerialMode(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while-parallel",
		Registry: v316Registry(),
		Steps: []workflow.Step{
			{ID: "Loop_test", While: "=true", MaxIterations: intPtr(5), Mode: "parallel", Children: []string{"Noop_body"}, Next: "Stop_End"},
			{ID: "Noop_body", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for while + parallel, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_BreakOutsideLoop(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "break-outside",
		Registry: v316Registry(),
		Steps: []workflow.Step{
			{ID: "Set_x", Target: "$x", Value: "1", Next: "BREAK"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for BREAK outside loop, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_BreakAsStepID(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "break-stepid",
		Registry: v316Registry(),
		Steps: []workflow.Step{
			{ID: "BREAK"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for BREAK as stepId, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_ModelMalformedSlash(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "model-slash",
		Registry: v316Registry("$a(STRING)"),
		Steps: []workflow.Step{
			{
				ID:    "LLM_test",
				Model: "openai/", // empty modelId
				In:    workflow.StepInput{"messages": []interface{}{map[string]interface{}{"role": "user", "content": "hi"}}},
				Out:   workflow.StepOutput{"$a": "=_result"},
				Next:  "Stop_End",
			},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for model with empty modelId, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_ModelMalformedSlashNoProvider(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "model-no-provider",
		Registry: v316Registry("$a(STRING)"),
		Steps: []workflow.Step{
			{
				ID:    "LLM_test",
				Model: "/gpt-4.1", // empty provider
				In:    workflow.StepInput{"messages": []interface{}{map[string]interface{}{"role": "user", "content": "hi"}}},
				Out:   workflow.StepOutput{"$a": "=_result"},
				Next:  "Stop_End",
			},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err == nil {
		t.Fatal("expected error for model with empty provider, got nil")
	}
	t.Logf("OK: %v", err)
}

func TestV316_Validate_BreakInsideLoopOK(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "break-inside-loop",
		Registry: v316Registry("$items([INT])"),
		Steps: []workflow.Step{
			{ID: "Set_items", Target: "$items", Value: "[1,2,3]", Next: "Loop_x"},
			{ID: "Loop_x", Source: "=$items", Mode: "serial", Children: []string{"Set_brk"}, Next: "Stop_End"},
			{ID: "Set_brk", Target: "$items", Value: "1", Next: "BREAK"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err != nil {
		t.Fatalf("expected no error for BREAK inside loop, got: %v", err)
	}
}

func TestV316_Validate_WhileLoopOK(t *testing.T) {
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while-loop-ok",
		Registry: v316Registry("$counter(INT)"),
		Steps: []workflow.Step{
			{ID: "Set_init", Target: "$counter", Value: "0", Next: "Loop_w"},
			{ID: "Loop_w", While: "=$counter < 5", MaxIterations: intPtr(10), Mode: "serial", Children: []string{"Set_inc"}, Next: "Stop_End"},
			{ID: "Set_inc", Target: "$counter", Value: "=$counter + 1", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	_, err := workflow.NewEngine(wf)
	if err != nil {
		t.Fatalf("expected no error for valid while loop, got: %v", err)
	}
}

// ── Execution tests ──────────────────────────────────────────────────────────

func TestV316_WhileLoop_BasicCounter(t *testing.T) {
	// While loop counting $counter from 0 to 4
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while-counter",
		Registry: v316Registry("$counter(INT)"),
		Steps: []workflow.Step{
			{ID: "Set_init", Target: "$counter", Value: "0", Next: "Loop_w"},
			{ID: "Loop_w", While: "=$counter < 5", MaxIterations: intPtr(10), Mode: "serial", Children: []string{"Set_inc"}, Next: "Stop_End"},
			{ID: "Set_inc", Target: "$counter", Value: "=$counter + 1", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	eng := mustEngineV316(t, wf)
	result, err := eng.Execute(context.Background(), nil, v316Adapters())
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	events := collectEvents(result.RunEventStream)

	// Verify counter reached 5
	counter, ok := result.Context.Variables["$counter"]
	if !ok {
		t.Fatal("$counter not found in variables")
	}
	// counter should be 5 (0 + 5 increments)
	counterVal, err2 := toInt(counter)
	if err2 != nil {
		t.Fatalf("$counter: %v", err2)
	}
	if counterVal != 5 {
		t.Errorf("expected $counter=5, got %d", counterVal)
	}

	// Count step_start events for Set_inc (should be 5)
	incCount := 0
	for _, ev := range events {
		if ev.Type == workflow.RunEventStepStart && ev.StepID != nil && *ev.StepID == "Set_inc" {
			incCount++
		}
	}
	if incCount != 5 {
		t.Errorf("expected 5 Set_inc step_start events, got %d", incCount)
	}
}

func TestV316_WhileLoop_MaxIterationsForceExit(t *testing.T) {
	// while=true but maxIterations=3 → exactly 3 iterations
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while-maxiter",
		Registry: v316Registry("$counter(INT)"),
		Steps: []workflow.Step{
			{ID: "Set_init", Target: "$counter", Value: "0", Next: "Loop_w"},
			{ID: "Loop_w", While: "=true", MaxIterations: intPtr(3), Mode: "serial", Children: []string{"Set_inc"}, Next: "Stop_End"},
			{ID: "Set_inc", Target: "$counter", Value: "=$counter + 1", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	eng := mustEngineV316(t, wf)
	result, err := eng.Execute(context.Background(), nil, v316Adapters())
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	collectEvents(result.RunEventStream)

	counter, _ := result.Context.Variables["$counter"]
	counterVal, err2 := toInt(counter)
	if err2 != nil {
		t.Fatalf("$counter: %v", err2)
	}
	if counterVal != 3 {
		t.Errorf("expected $counter=3, got %d", counterVal)
	}
}

func TestV316_WhileLoop_ConditionFalseImmediately(t *testing.T) {
	// while=false from the start → 0 iterations
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "while-false",
		Registry: v316Registry("$counter(INT)"),
		Steps: []workflow.Step{
			{ID: "Set_init", Target: "$counter", Value: "0", Next: "Loop_w"},
			{ID: "Loop_w", While: "=false", MaxIterations: intPtr(10), Mode: "serial", Children: []string{"Set_inc"}, Next: "Stop_End"},
			{ID: "Set_inc", Target: "$counter", Value: "=$counter + 1", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	eng := mustEngineV316(t, wf)
	result, err := eng.Execute(context.Background(), nil, v316Adapters())
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	events := collectEvents(result.RunEventStream)

	counter, _ := result.Context.Variables["$counter"]
	counterVal, err2 := toInt(counter)
	if err2 != nil {
		t.Fatalf("$counter: %v", err2)
	}
	if counterVal != 0 {
		t.Errorf("expected $counter=0, got %d", counterVal)
	}

	// Set_inc should never fire
	for _, ev := range events {
		if ev.Type == workflow.RunEventStepStart && ev.StepID != nil && *ev.StepID == "Set_inc" {
			t.Error("Set_inc should not have started")
		}
	}
}

func TestV316_Break_SerialLoop(t *testing.T) {
	// Loop over [1,2,3,4,5], BREAK when _item == 3
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "break-serial",
		Registry: v316Registry("$items([INT])", "$results([INT])"),
		Steps: []workflow.Step{
			{ID: "Set_items", Target: "$items", Value: "[1, 2, 3, 4, 5]", Next: "Loop_x"},
			{ID: "Loop_x", Source: "=$items", Mode: "serial", Children: []string{"Branch_chk"}, Next: "Stop_End"},
			{ID: "Branch_chk", Cases: [][]string{{"=_item == 3", "Set_brk"}, {"ELSE", "Set_ok"}}, Next: "RETURN"},
			{ID: "Set_brk", Target: "$results[_index]", Value: "=_item", Next: "BREAK"},
			{ID: "Set_ok", Target: "$results[_index]", Value: "=_item", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	eng := mustEngineV316(t, wf)
	result, err := eng.Execute(context.Background(), nil, v316Adapters())
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	events := collectEvents(result.RunEventStream)

	// Should have results for indices 0,1,2 (items 1,2,3) — BREAK at item 3
	results, _ := result.Context.Variables["$results"]
	resultsSlice, ok := results.([]interface{})
	if !ok {
		t.Fatalf("$results not a slice: %T", results)
	}
	if len(resultsSlice) != 3 {
		t.Errorf("expected 3 results, got %d: %v", len(resultsSlice), resultsSlice)
	}

	// Items 4 and 5 should NOT have been processed
	setOkCount := 0
	for _, ev := range events {
		if ev.Type == workflow.RunEventStepStart && ev.StepID != nil && *ev.StepID == "Set_ok" {
			setOkCount++
		}
	}
	if setOkCount != 2 { // items 1 and 2
		t.Errorf("expected 2 Set_ok executions, got %d", setOkCount)
	}
}

func TestV316_Break_WhileLoop(t *testing.T) {
	// While loop that BREAKs after 2 iterations
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "break-while",
		Registry: v316Registry("$counter(INT)"),
		Steps: []workflow.Step{
			{ID: "Set_init", Target: "$counter", Value: "0", Next: "Loop_w"},
			{ID: "Loop_w", While: "=true", MaxIterations: intPtr(10), Mode: "serial", Children: []string{"Branch_chk"}, Next: "Stop_End"},
			{ID: "Branch_chk", Cases: [][]string{{"=$counter >= 2", "Set_brk"}, {"ELSE", "Set_inc"}}, Next: "RETURN"},
			{ID: "Set_brk", Target: "$counter", Value: "=$counter", Next: "BREAK"},
			{ID: "Set_inc", Target: "$counter", Value: "=$counter + 1", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	eng := mustEngineV316(t, wf)
	result, err := eng.Execute(context.Background(), nil, v316Adapters())
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	collectEvents(result.RunEventStream)

	counter, _ := result.Context.Variables["$counter"]
	counterVal, err2 := toInt(counter)
	if err2 != nil {
		t.Fatalf("$counter: %v", err2)
	}
	if counterVal != 2 {
		t.Errorf("expected $counter=2, got %d", counterVal)
	}
}

func TestV316_SourceLoop_MaxIterationsCaps(t *testing.T) {
	// source has 5 items, maxIterations: 3 → only 3 iterations
	wf := &workflow.Workflow{
		Version:  "3.16",
		Name:     "source-maxiter",
		Registry: v316Registry("$items([INT])", "$results([INT])"),
		Steps: []workflow.Step{
			{ID: "Set_items", Target: "$items", Value: "[10, 20, 30, 40, 50]", Next: "Loop_x"},
			{ID: "Loop_x", Source: "=$items", Mode: "serial", MaxIterations: intPtr(3), Children: []string{"Set_col"}, Next: "Stop_End"},
			{ID: "Set_col", Target: "$results[_index]", Value: "=_item", Next: "RETURN"},
			{ID: "Stop_End"},
		},
	}
	eng := mustEngineV316(t, wf)
	result, err := eng.Execute(context.Background(), nil, v316Adapters())
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	collectEvents(result.RunEventStream)

	results, _ := result.Context.Variables["$results"]
	resultsSlice, ok := results.([]interface{})
	if !ok {
		t.Fatalf("$results not a slice: %T", results)
	}
	if len(resultsSlice) != 3 {
		t.Errorf("expected 3 results, got %d: %v", len(resultsSlice), resultsSlice)
	}
}

// ── LLMAdapterRegistry tests ─────────────────────────────────────────────────

func TestV316_LLMAdapterRegistry_Resolve(t *testing.T) {
	mockOpenAI := workflow.NewDefaultLLMAdapter()
	mockAnthropic := workflow.NewDefaultLLMAdapter()

	registry := workflow.NewLLMAdapterRegistry(mockOpenAI, "openai")
	registry.Register("openai", mockOpenAI)
	registry.Register("anthropic", mockAnthropic)

	// Empty spec → default adapter
	adapter, override, err := registry.Resolve("")
	if err != nil {
		t.Fatalf("Resolve empty: %v", err)
	}
	if adapter != mockOpenAI {
		t.Error("expected default openai adapter")
	}
	if override != "" {
		t.Errorf("expected no override, got %q", override)
	}

	// Provider-only → correct adapter, no override
	adapter, override, err = registry.Resolve("anthropic")
	if err != nil {
		t.Fatalf("Resolve anthropic: %v", err)
	}
	if adapter != mockAnthropic {
		t.Error("expected anthropic adapter")
	}
	if override != "" {
		t.Errorf("expected no override, got %q", override)
	}

	// Full spec → correct adapter with model override
	adapter, override, err = registry.Resolve("openai/gpt-4.1")
	if err != nil {
		t.Fatalf("Resolve openai/gpt-4.1: %v", err)
	}
	if adapter != mockOpenAI {
		t.Error("expected openai adapter")
	}
	if override != "gpt-4.1" {
		t.Errorf("expected override 'gpt-4.1', got %q", override)
	}

	// Unknown provider → error
	_, _, err = registry.Resolve("unknown")
	if err == nil {
		t.Error("expected error for unknown provider")
	}
	_, _, err = registry.Resolve("unknown/model")
	if err == nil {
		t.Error("expected error for unknown/model")
	}
}
