package formula

import (
	"os"
	"testing"
)

func TestParseCondition_FieldConditions(t *testing.T) {
	tests := []struct {
		name     string
		expr     string
		wantType ConditionType
		wantStep string
		wantOp   Operator
	}{
		{
			name:     "step status",
			expr:     "step.status == 'complete'",
			wantType: ConditionTypeField,
			wantStep: "step",
			wantOp:   OpEqual,
		},
		{
			name:     "named step status",
			expr:     "review.status == 'complete'",
			wantType: ConditionTypeField,
			wantStep: "review",
			wantOp:   OpEqual,
		},
		{
			name:     "step output field",
			expr:     "review.output.approved == true",
			wantType: ConditionTypeField,
			wantStep: "review",
			wantOp:   OpEqual,
		},
		{
			name:     "not equal",
			expr:     "step.status != 'failed'",
			wantType: ConditionTypeField,
			wantStep: "step",
			wantOp:   OpNotEqual,
		},
		{
			name:     "nested output",
			expr:     "test.output.errors.count == 0",
			wantType: ConditionTypeField,
			wantStep: "test",
			wantOp:   OpEqual,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cond, err := ParseCondition(tt.expr)
			if err != nil {
				t.Fatalf("ParseCondition(%q) error: %v", tt.expr, err)
			}
			if cond.Type != tt.wantType {
				t.Errorf("Type = %v, want %v", cond.Type, tt.wantType)
			}
			if cond.StepRef != tt.wantStep {
				t.Errorf("StepRef = %v, want %v", cond.StepRef, tt.wantStep)
			}
			if cond.Operator != tt.wantOp {
				t.Errorf("Operator = %v, want %v", cond.Operator, tt.wantOp)
			}
		})
	}
}

func TestParseCondition_AggregateConditions(t *testing.T) {
	tests := []struct {
		name     string
		expr     string
		wantFunc string
		wantOver string
	}{
		{
			name:     "children all complete",
			expr:     "children(step).all(status == 'complete')",
			wantFunc: "all",
			wantOver: "children",
		},
		{
			name:     "children any failed",
			expr:     "children(review).any(status == 'failed')",
			wantFunc: "any",
			wantOver: "children",
		},
		{
			name:     "steps count",
			expr:     "steps.complete >= 3",
			wantFunc: "count",
			wantOver: "steps",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cond, err := ParseCondition(tt.expr)
			if err != nil {
				t.Fatalf("ParseCondition(%q) error: %v", tt.expr, err)
			}
			if cond.Type != ConditionTypeAggregate {
				t.Errorf("Type = %v, want %v", cond.Type, ConditionTypeAggregate)
			}
			if cond.AggregateFunc != tt.wantFunc {
				t.Errorf("AggregateFunc = %v, want %v", cond.AggregateFunc, tt.wantFunc)
			}
			if cond.AggregateOver != tt.wantOver {
				t.Errorf("AggregateOver = %v, want %v", cond.AggregateOver, tt.wantOver)
			}
		})
	}
}

func TestParseCondition_ExternalConditions(t *testing.T) {
	tests := []struct {
		name         string
		expr         string
		wantExtType  string
		wantExtArg   string
	}{
		{
			name:        "file exists single quotes",
			expr:        "file.exists('go.mod')",
			wantExtType: "file.exists",
			wantExtArg:  "go.mod",
		},
		{
			name:        "file exists double quotes",
			expr:        `file.exists("package.json")`,
			wantExtType: "file.exists",
			wantExtArg:  "package.json",
		},
		{
			name:        "env var",
			expr:        "env.CI == 'true'",
			wantExtType: "env",
			wantExtArg:  "CI",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cond, err := ParseCondition(tt.expr)
			if err != nil {
				t.Fatalf("ParseCondition(%q) error: %v", tt.expr, err)
			}
			if cond.Type != ConditionTypeExternal {
				t.Errorf("Type = %v, want %v", cond.Type, ConditionTypeExternal)
			}
			if cond.ExternalType != tt.wantExtType {
				t.Errorf("ExternalType = %v, want %v", cond.ExternalType, tt.wantExtType)
			}
			if cond.ExternalArg != tt.wantExtArg {
				t.Errorf("ExternalArg = %v, want %v", cond.ExternalArg, tt.wantExtArg)
			}
		})
	}
}

func TestEvaluateCondition_Field(t *testing.T) {
	ctx := &ConditionContext{
		CurrentStep: "test",
		Steps: map[string]*StepState{
			"design": {
				ID:     "design",
				Status: "complete",
				Output: map[string]interface{}{
					"approved": true,
				},
			},
			"test": {
				ID:     "test",
				Status: "in_progress",
				Output: map[string]interface{}{
					"errors": map[string]interface{}{
						"count": float64(0),
					},
				},
			},
			"review": {
				ID:     "review",
				Status: "pending",
				Output: map[string]interface{}{
					"approved": false,
				},
			},
		},
	}

	tests := []struct {
		name      string
		expr      string
		wantSatis bool
	}{
		{
			name:      "step complete - true",
			expr:      "design.status == 'complete'",
			wantSatis: true,
		},
		{
			name:      "step complete - false",
			expr:      "review.status == 'complete'",
			wantSatis: false,
		},
		{
			name:      "current step status",
			expr:      "step.status == 'in_progress'",
			wantSatis: true,
		},
		{
			name:      "output bool true",
			expr:      "design.output.approved == true",
			wantSatis: true,
		},
		{
			name:      "output bool false",
			expr:      "review.output.approved == true",
			wantSatis: false,
		},
		{
			name:      "nested output",
			expr:      "test.output.errors.count == 0",
			wantSatis: true,
		},
		{
			name:      "not equal satisfied",
			expr:      "design.status != 'failed'",
			wantSatis: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := EvaluateCondition(tt.expr, ctx)
			if err != nil {
				t.Fatalf("EvaluateCondition(%q) error: %v", tt.expr, err)
			}
			if result.Satisfied != tt.wantSatis {
				t.Errorf("Satisfied = %v, want %v (reason: %s)", result.Satisfied, tt.wantSatis, result.Reason)
			}
		})
	}
}

func TestEvaluateCondition_Aggregate(t *testing.T) {
	ctx := &ConditionContext{
		CurrentStep: "aggregate",
		Steps: map[string]*StepState{
			"parent": {
				ID:     "parent",
				Status: "in_progress",
				Children: []*StepState{
					{ID: "child1", Status: "complete"},
					{ID: "child2", Status: "complete"},
					{ID: "child3", Status: "complete"},
				},
			},
			"mixed": {
				ID:     "mixed",
				Status: "in_progress",
				Children: []*StepState{
					{ID: "m1", Status: "complete"},
					{ID: "m2", Status: "failed"},
					{ID: "m3", Status: "pending"},
				},
			},
			"step1": {ID: "step1", Status: "complete"},
			"step2": {ID: "step2", Status: "complete"},
			"step3": {ID: "step3", Status: "complete"},
			"step4": {ID: "step4", Status: "pending"},
		},
	}

	tests := []struct {
		name      string
		expr      string
		wantSatis bool
	}{
		{
			name:      "all children complete - true",
			expr:      "children(parent).all(status == 'complete')",
			wantSatis: true,
		},
		{
			name:      "all children complete - false",
			expr:      "children(mixed).all(status == 'complete')",
			wantSatis: false,
		},
		{
			name:      "any children failed - true",
			expr:      "children(mixed).any(status == 'failed')",
			wantSatis: true,
		},
		{
			name:      "any children failed - false",
			expr:      "children(parent).any(status == 'failed')",
			wantSatis: false,
		},
		{
			name:      "steps count >= satisfied",
			expr:      "steps.complete >= 3",
			wantSatis: true,
		},
		{
			name:      "steps count >= not satisfied",
			expr:      "steps.complete >= 5",
			wantSatis: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := EvaluateCondition(tt.expr, ctx)
			if err != nil {
				t.Fatalf("EvaluateCondition(%q) error: %v", tt.expr, err)
			}
			if result.Satisfied != tt.wantSatis {
				t.Errorf("Satisfied = %v, want %v (reason: %s)", result.Satisfied, tt.wantSatis, result.Reason)
			}
		})
	}
}

func TestEvaluateCondition_External(t *testing.T) {
	// Create a temp file for testing
	tmpFile, err := os.CreateTemp("", "condition_test_*.txt")
	if err != nil {
		t.Fatal(err)
	}
	tmpFile.Close()
	defer os.Remove(tmpFile.Name())

	// Set an env var for testing
	os.Setenv("TEST_CONDITION_VAR", "test_value")
	defer os.Unsetenv("TEST_CONDITION_VAR")

	ctx := &ConditionContext{
		Vars: map[string]string{
			"tempfile": tmpFile.Name(),
		},
	}

	tests := []struct {
		name      string
		expr      string
		wantSatis bool
	}{
		{
			name:      "file exists - true",
			expr:      "file.exists('" + tmpFile.Name() + "')",
			wantSatis: true,
		},
		{
			name:      "file exists - false",
			expr:      "file.exists('/nonexistent/file/path')",
			wantSatis: false,
		},
		{
			name:      "env var equals",
			expr:      "env.TEST_CONDITION_VAR == 'test_value'",
			wantSatis: true,
		},
		{
			name:      "env var not equals",
			expr:      "env.TEST_CONDITION_VAR == 'wrong'",
			wantSatis: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := EvaluateCondition(tt.expr, ctx)
			if err != nil {
				t.Fatalf("EvaluateCondition(%q) error: %v", tt.expr, err)
			}
			if result.Satisfied != tt.wantSatis {
				t.Errorf("Satisfied = %v, want %v (reason: %s)", result.Satisfied, tt.wantSatis, result.Reason)
			}
		})
	}
}

func TestParseCondition_Errors(t *testing.T) {
	tests := []struct {
		name string
		expr string
	}{
		{
			name: "empty",
			expr: "",
		},
		{
			name: "whitespace only",
			expr: "   ",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := ParseCondition(tt.expr)
			if err == nil {
				t.Errorf("ParseCondition(%q) expected error, got nil", tt.expr)
			}
		})
	}
}

func TestCompareOperators(t *testing.T) {
	tests := []struct {
		actual   string
		op       Operator
		expected string
		want     bool
	}{
		{"5", OpGreater, "3", true},
		{"5", OpGreater, "5", false},
		{"5", OpGreaterEqual, "5", true},
		{"3", OpLess, "5", true},
		{"5", OpLess, "5", false},
		{"5", OpLessEqual, "5", true},
		{"abc", OpEqual, "abc", true},
		{"abc", OpNotEqual, "def", true},
	}

	for _, tt := range tests {
		t.Run(string(tt.op), func(t *testing.T) {
			got, _ := compare(tt.actual, tt.op, tt.expected)
			if got != tt.want {
				t.Errorf("compare(%q, %s, %q) = %v, want %v", tt.actual, tt.op, tt.expected, got, tt.want)
			}
		})
	}
}

func TestEvaluateCondition_EmptyChildren(t *testing.T) {
	ctx := &ConditionContext{
		CurrentStep: "parent",
		Steps: map[string]*StepState{
			"parent": {
				ID:       "parent",
				Status:   "in_progress",
				Children: []*StepState{}, // Empty children
			},
		},
	}

	// "all children complete" with no children should be false (not vacuous truth)
	result, err := EvaluateCondition("children(parent).all(status == 'complete')", ctx)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result.Satisfied {
		t.Errorf("empty children 'all' should be false, got true")
	}
}

func TestEvaluateCondition_NilOutput(t *testing.T) {
	ctx := &ConditionContext{
		CurrentStep: "test",
		Steps: map[string]*StepState{
			"test": {
				ID:     "test",
				Status: "complete",
				Output: nil, // No output
			},
		},
	}

	// Comparing nil output field should not panic
	result, err := EvaluateCondition("test.output.missing == 'value'", ctx)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result.Satisfied {
		t.Errorf("nil field comparison should be false")
	}
}

func TestEvaluateCondition_BoolOutput(t *testing.T) {
	ctx := &ConditionContext{
		CurrentStep: "test",
		Steps: map[string]*StepState{
			"test": {
				ID:     "test",
				Status: "complete",
				Output: map[string]interface{}{
					"approved": true, // actual bool, not string
				},
			},
		},
	}

	result, err := EvaluateCondition("test.output.approved == true", ctx)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !result.Satisfied {
		t.Errorf("bool true should match 'true', got: %s", result.Reason)
	}
}

func TestEvaluateCondition_CountError(t *testing.T) {
	ctx := &ConditionContext{
		Steps: map[string]*StepState{
			"s1": {ID: "s1", Status: "complete"},
		},
	}

	// Directly construct a condition with invalid count value
	// (regex validation normally prevents this, but test defensive code)
	cond := &Condition{
		Raw:           "steps.complete >= notanumber",
		Type:          ConditionTypeAggregate,
		AggregateOver: "steps",
		AggregateFunc: "count",
		Field:         "complete",
		Operator:      OpGreaterEqual,
		Value:         "notanumber", // Invalid: not an integer
	}

	_, err := cond.Evaluate(ctx)
	if err == nil {
		t.Error("expected error for non-integer count value")
	}
}
