package main

import (
	"testing"

	"github.com/steveyegge/beads/internal/formula"
	"github.com/steveyegge/beads/internal/types"
)

// =============================================================================
// Cook Tests (gt-8tmz.23: Compile-time vs Runtime Cooking)
// =============================================================================

// TestSubstituteFormulaVars tests variable substitution in formulas
func TestSubstituteFormulaVars(t *testing.T) {
	tests := []struct {
		name        string
		formula     *formula.Formula
		vars        map[string]string
		wantDesc    string
		wantStepTitle string
	}{
		{
			name: "substitute single variable in description",
			formula: &formula.Formula{
				Description: "Build {{feature}} feature",
				Steps:       []*formula.Step{},
			},
			vars:     map[string]string{"feature": "auth"},
			wantDesc: "Build auth feature",
		},
		{
			name: "substitute variable in step title",
			formula: &formula.Formula{
				Description: "Feature work",
				Steps: []*formula.Step{
					{Title: "Implement {{name}}"},
				},
			},
			vars:           map[string]string{"name": "login"},
			wantDesc:       "Feature work",
			wantStepTitle:  "Implement login",
		},
		{
			name: "substitute multiple variables",
			formula: &formula.Formula{
				Description: "Release {{version}} on {{date}}",
				Steps: []*formula.Step{
					{Title: "Tag {{version}}"},
					{Title: "Deploy to {{env}}"},
				},
			},
			vars: map[string]string{
				"version": "1.0.0",
				"date":    "2024-01-15",
				"env":     "production",
			},
			wantDesc:      "Release 1.0.0 on 2024-01-15",
			wantStepTitle: "Tag 1.0.0",
		},
		{
			name: "nested children substitution",
			formula: &formula.Formula{
				Description: "Epic for {{project}}",
				Steps: []*formula.Step{
					{
						Title: "Phase 1: {{project}} design",
						Children: []*formula.Step{
							{Title: "Design {{component}}"},
						},
					},
				},
			},
			vars: map[string]string{
				"project":   "checkout",
				"component": "cart",
			},
			wantDesc:      "Epic for checkout",
			wantStepTitle: "Phase 1: checkout design",
		},
		{
			name: "unsubstituted variable left as-is",
			formula: &formula.Formula{
				Description: "Build {{feature}} with {{extra}}",
				Steps:       []*formula.Step{},
			},
			vars:     map[string]string{"feature": "auth"},
			wantDesc: "Build auth with {{extra}}", // {{extra}} unchanged
		},
		{
			name: "empty vars map",
			formula: &formula.Formula{
				Description: "Keep {{placeholder}} intact",
				Steps:       []*formula.Step{},
			},
			vars:     map[string]string{},
			wantDesc: "Keep {{placeholder}} intact",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			substituteFormulaVars(tt.formula, tt.vars)

			if tt.formula.Description != tt.wantDesc {
				t.Errorf("Description = %q, want %q", tt.formula.Description, tt.wantDesc)
			}

			if tt.wantStepTitle != "" && len(tt.formula.Steps) > 0 {
				if tt.formula.Steps[0].Title != tt.wantStepTitle {
					t.Errorf("Steps[0].Title = %q, want %q", tt.formula.Steps[0].Title, tt.wantStepTitle)
				}
			}
		})
	}
}

// TestSubstituteStepVarsRecursive tests deep nesting works correctly
func TestSubstituteStepVarsRecursive(t *testing.T) {
	steps := []*formula.Step{
		{
			Title: "Root: {{name}}",
			Children: []*formula.Step{
				{
					Title: "Level 1: {{name}}",
					Children: []*formula.Step{
						{
							Title: "Level 2: {{name}}",
							Children: []*formula.Step{
								{Title: "Level 3: {{name}}"},
							},
						},
					},
				},
			},
		},
	}

	vars := map[string]string{"name": "test"}
	substituteStepVars(steps, vars)

	// Check all levels got substituted
	if steps[0].Title != "Root: test" {
		t.Errorf("Root title = %q, want %q", steps[0].Title, "Root: test")
	}
	if steps[0].Children[0].Title != "Level 1: test" {
		t.Errorf("Level 1 title = %q, want %q", steps[0].Children[0].Title, "Level 1: test")
	}
	if steps[0].Children[0].Children[0].Title != "Level 2: test" {
		t.Errorf("Level 2 title = %q, want %q", steps[0].Children[0].Children[0].Title, "Level 2: test")
	}
	if steps[0].Children[0].Children[0].Children[0].Title != "Level 3: test" {
		t.Errorf("Level 3 title = %q, want %q", steps[0].Children[0].Children[0].Children[0].Title, "Level 3: test")
	}
}

// TestCompileTimeVsRuntimeMode tests that compile-time preserves placeholders
// and runtime mode substitutes them
func TestCompileTimeVsRuntimeMode(t *testing.T) {
	// Simulate compile-time mode (no variable substitution)
	compileFormula := &formula.Formula{
		Description: "Feature: {{name}}",
		Steps: []*formula.Step{
			{Title: "Implement {{name}}"},
		},
	}

	// In compile-time mode, don't call substituteFormulaVars
	// Placeholders should remain intact
	if compileFormula.Description != "Feature: {{name}}" {
		t.Errorf("Compile-time: Description should preserve placeholder, got %q", compileFormula.Description)
	}

	// Simulate runtime mode (with variable substitution)
	runtimeFormula := &formula.Formula{
		Description: "Feature: {{name}}",
		Steps: []*formula.Step{
			{Title: "Implement {{name}}"},
		},
	}
	vars := map[string]string{"name": "auth"}
	substituteFormulaVars(runtimeFormula, vars)

	if runtimeFormula.Description != "Feature: auth" {
		t.Errorf("Runtime: Description = %q, want %q", runtimeFormula.Description, "Feature: auth")
	}
	if runtimeFormula.Steps[0].Title != "Implement auth" {
		t.Errorf("Runtime: Steps[0].Title = %q, want %q", runtimeFormula.Steps[0].Title, "Implement auth")
	}
}

// =============================================================================
// Gate Bead Tests (bd-4k3c: Gate beads created during cook)
// =============================================================================

// TestCreateGateIssue tests that createGateIssue creates proper gate issues
func TestCreateGateIssue(t *testing.T) {
	tests := []struct {
		name        string
		step        *formula.Step
		parentID    string
		wantID      string
		wantTitle   string
		wantAwaitType string
		wantAwaitID string
	}{
		{
			name: "gh:run gate with ID",
			step: &formula.Step{
				ID:    "await-ci",
				Title: "Wait for CI",
				Gate: &formula.Gate{
					Type: "gh:run",
					ID:   "release-build",
				},
			},
			parentID:      "mol-release",
			wantID:        "mol-release.gate-await-ci",
			wantTitle:     "Gate: gh:run release-build",
			wantAwaitType: "gh:run",
			wantAwaitID:   "release-build",
		},
		{
			name: "gh:pr gate without ID",
			step: &formula.Step{
				ID:    "await-pr",
				Title: "Wait for PR",
				Gate: &formula.Gate{
					Type: "gh:pr",
				},
			},
			parentID:      "mol-feature",
			wantID:        "mol-feature.gate-await-pr",
			wantTitle:     "Gate: gh:pr",
			wantAwaitType: "gh:pr",
			wantAwaitID:   "",
		},
		{
			name: "timer gate",
			step: &formula.Step{
				ID:    "cooldown",
				Title: "Wait for cooldown",
				Gate: &formula.Gate{
					Type:    "timer",
					Timeout: "30m",
				},
			},
			parentID:      "mol-deploy",
			wantID:        "mol-deploy.gate-cooldown",
			wantTitle:     "Gate: timer",
			wantAwaitType: "timer",
			wantAwaitID:   "",
		},
		{
			name: "human gate",
			step: &formula.Step{
				ID:    "approval",
				Title: "Manual approval",
				Gate: &formula.Gate{
					Type:    "human",
					Timeout: "24h",
				},
			},
			parentID:      "mol-release",
			wantID:        "mol-release.gate-approval",
			wantTitle:     "Gate: human",
			wantAwaitType: "human",
			wantAwaitID:   "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gateIssue := createGateIssue(tt.step, tt.parentID)

			if gateIssue == nil {
				t.Fatal("createGateIssue returned nil")
			}

			if gateIssue.ID != tt.wantID {
				t.Errorf("ID = %q, want %q", gateIssue.ID, tt.wantID)
			}
			if gateIssue.Title != tt.wantTitle {
				t.Errorf("Title = %q, want %q", gateIssue.Title, tt.wantTitle)
			}
			if gateIssue.AwaitType != tt.wantAwaitType {
				t.Errorf("AwaitType = %q, want %q", gateIssue.AwaitType, tt.wantAwaitType)
			}
			if gateIssue.AwaitID != tt.wantAwaitID {
				t.Errorf("AwaitID = %q, want %q", gateIssue.AwaitID, tt.wantAwaitID)
			}
			if gateIssue.IssueType != "gate" {
				t.Errorf("IssueType = %q, want %q", gateIssue.IssueType, "gate")
			}
			if !gateIssue.IsTemplate {
				t.Error("IsTemplate should be true")
			}
		})
	}
}

// TestCreateGateIssue_NilGate tests that nil Gate returns nil
func TestCreateGateIssue_NilGate(t *testing.T) {
	step := &formula.Step{
		ID:    "no-gate",
		Title: "Step without gate",
		Gate:  nil,
	}

	gateIssue := createGateIssue(step, "mol-test")
	if gateIssue != nil {
		t.Errorf("Expected nil for step without Gate, got %+v", gateIssue)
	}
}

// TestCreateGateIssue_Timeout tests that timeout is parsed correctly
func TestCreateGateIssue_Timeout(t *testing.T) {
	tests := []struct {
		name        string
		timeout     string
		wantMinutes int
	}{
		{"30 minutes", "30m", 30},
		{"1 hour", "1h", 60},
		{"24 hours", "24h", 1440},
		{"invalid timeout", "invalid", 0},
		{"empty timeout", "", 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			step := &formula.Step{
				ID:    "timed-step",
				Title: "Timed step",
				Gate: &formula.Gate{
					Type:    "timer",
					Timeout: tt.timeout,
				},
			}

			gateIssue := createGateIssue(step, "mol-test")
			gotMinutes := int(gateIssue.Timeout.Minutes())

			if gotMinutes != tt.wantMinutes {
				t.Errorf("Timeout minutes = %d, want %d", gotMinutes, tt.wantMinutes)
			}
		})
	}
}

// TestCookFormulaToSubgraph_GateBeads tests that gate beads are created in subgraph
func TestCookFormulaToSubgraph_GateBeads(t *testing.T) {
	f := &formula.Formula{
		Formula:     "mol-test-gate",
		Description: "Test gate creation",
		Version:     1,
		Type:        formula.TypeWorkflow,
		Steps: []*formula.Step{
			{
				ID:    "build",
				Title: "Build project",
			},
			{
				ID:    "await-ci",
				Title: "Wait for CI",
				Gate: &formula.Gate{
					Type: "gh:run",
					ID:   "ci-workflow",
				},
			},
			{
				ID:    "verify",
				Title: "Verify deployment",
				DependsOn: []string{"await-ci"},
			},
		},
	}

	subgraph, err := cookFormulaToSubgraph(f, "mol-test-gate")
	if err != nil {
		t.Fatalf("cookFormulaToSubgraph failed: %v", err)
	}

	// Should have: root + 3 steps + 1 gate = 5 issues
	if len(subgraph.Issues) != 5 {
		t.Errorf("Expected 5 issues, got %d", len(subgraph.Issues))
		for _, issue := range subgraph.Issues {
			t.Logf("  Issue: %s (%s)", issue.ID, issue.IssueType)
		}
	}

	// Find the gate issue
	var gateIssue *types.Issue
	for _, issue := range subgraph.Issues {
		if issue.IssueType == "gate" {
			gateIssue = issue
			break
		}
	}

	if gateIssue == nil {
		t.Fatal("Gate issue not found in subgraph")
	}

	if gateIssue.ID != "mol-test-gate.gate-await-ci" {
		t.Errorf("Gate ID = %q, want %q", gateIssue.ID, "mol-test-gate.gate-await-ci")
	}
	if gateIssue.AwaitType != "gh:run" {
		t.Errorf("Gate AwaitType = %q, want %q", gateIssue.AwaitType, "gh:run")
	}
	if gateIssue.AwaitID != "ci-workflow" {
		t.Errorf("Gate AwaitID = %q, want %q", gateIssue.AwaitID, "ci-workflow")
	}
}

// TestCookFormulaToSubgraph_GateDependencies tests that step depends on its gate
func TestCookFormulaToSubgraph_GateDependencies(t *testing.T) {
	f := &formula.Formula{
		Formula:     "mol-gate-deps",
		Description: "Test gate dependencies",
		Version:     1,
		Type:        formula.TypeWorkflow,
		Steps: []*formula.Step{
			{
				ID:    "await-approval",
				Title: "Wait for approval",
				Gate: &formula.Gate{
					Type:    "human",
					Timeout: "24h",
				},
			},
		},
	}

	subgraph, err := cookFormulaToSubgraph(f, "mol-gate-deps")
	if err != nil {
		t.Fatalf("cookFormulaToSubgraph failed: %v", err)
	}

	// Find the blocking dependency: step -> gate
	stepID := "mol-gate-deps.await-approval"
	gateID := "mol-gate-deps.gate-await-approval"

	var foundBlockingDep bool
	for _, dep := range subgraph.Dependencies {
		if dep.IssueID == stepID && dep.DependsOnID == gateID && dep.Type == "blocks" {
			foundBlockingDep = true
			break
		}
	}

	if !foundBlockingDep {
		t.Error("Expected blocking dependency from step to gate not found")
		t.Log("Dependencies found:")
		for _, dep := range subgraph.Dependencies {
			t.Logf("  %s -> %s (%s)", dep.IssueID, dep.DependsOnID, dep.Type)
		}
	}
}

// TestCookFormulaToSubgraph_GateParentChild tests that gate is a child of the parent
func TestCookFormulaToSubgraph_GateParentChild(t *testing.T) {
	f := &formula.Formula{
		Formula:     "mol-gate-parent",
		Description: "Test gate parent-child relationship",
		Version:     1,
		Type:        formula.TypeWorkflow,
		Steps: []*formula.Step{
			{
				ID:    "gated-step",
				Title: "Gated step",
				Gate: &formula.Gate{
					Type: "mail",
				},
			},
		},
	}

	subgraph, err := cookFormulaToSubgraph(f, "mol-gate-parent")
	if err != nil {
		t.Fatalf("cookFormulaToSubgraph failed: %v", err)
	}

	// Find the parent-child dependency: gate -> root
	gateID := "mol-gate-parent.gate-gated-step"
	rootID := "mol-gate-parent"

	var foundParentChildDep bool
	for _, dep := range subgraph.Dependencies {
		if dep.IssueID == gateID && dep.DependsOnID == rootID && dep.Type == "parent-child" {
			foundParentChildDep = true
			break
		}
	}

	if !foundParentChildDep {
		t.Error("Expected parent-child dependency for gate not found")
		t.Log("Dependencies found:")
		for _, dep := range subgraph.Dependencies {
			t.Logf("  %s -> %s (%s)", dep.IssueID, dep.DependsOnID, dep.Type)
		}
	}
}
