package cmd

import (
	"testing"

	"github.com/steveyegge/gastown/internal/beads"
)

func TestExtractMoleculeIDFromStep(t *testing.T) {
	tests := []struct {
		name     string
		stepID   string
		expected string
	}{
		{
			name:     "simple step",
			stepID:   "gt-abc.1",
			expected: "gt-abc",
		},
		{
			name:     "multi-digit step number",
			stepID:   "gt-xyz.12",
			expected: "gt-xyz",
		},
		{
			name:     "molecule with dash",
			stepID:   "gt-my-mol.3",
			expected: "gt-my-mol",
		},
		{
			name:     "bd prefix",
			stepID:   "bd-mol-abc.2",
			expected: "bd-mol-abc",
		},
		{
			name:     "complex id",
			stepID:   "gt-some-complex-id.99",
			expected: "gt-some-complex-id",
		},
		{
			name:     "not a step - no suffix",
			stepID:   "gt-5gq8r",
			expected: "",
		},
		{
			name:     "not a step - non-numeric suffix",
			stepID:   "gt-abc.xyz",
			expected: "",
		},
		{
			name:     "not a step - mixed suffix",
			stepID:   "gt-abc.1a",
			expected: "",
		},
		{
			name:     "empty string",
			stepID:   "",
			expected: "",
		},
		{
			name:     "just a dot",
			stepID:   ".",
			expected: "",
		},
		{
			name:     "trailing dot",
			stepID:   "gt-abc.",
			expected: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := extractMoleculeIDFromStep(tt.stepID)
			if result != tt.expected {
				t.Errorf("extractMoleculeIDFromStep(%q) = %q, want %q", tt.stepID, result, tt.expected)
			}
		})
	}
}

// mockBeadsForStep extends mockBeads with parent filtering for step tests
type mockBeadsForStep struct {
	issues map[string]*beads.Issue
}

func newMockBeadsForStep() *mockBeadsForStep {
	return &mockBeadsForStep{
		issues: make(map[string]*beads.Issue),
	}
}

func (m *mockBeadsForStep) addIssue(issue *beads.Issue) {
	m.issues[issue.ID] = issue
}

func (m *mockBeadsForStep) Show(id string) (*beads.Issue, error) {
	if issue, ok := m.issues[id]; ok {
		return issue, nil
	}
	return nil, beads.ErrNotFound
}

func (m *mockBeadsForStep) List(opts beads.ListOptions) ([]*beads.Issue, error) {
	var result []*beads.Issue
	for _, issue := range m.issues {
		// Filter by parent
		if opts.Parent != "" && issue.Parent != opts.Parent {
			continue
		}
		// Filter by status (unless "all")
		if opts.Status != "" && opts.Status != "all" && issue.Status != opts.Status {
			continue
		}
		result = append(result, issue)
	}
	return result, nil
}

func (m *mockBeadsForStep) Close(ids ...string) error {
	for _, id := range ids {
		if issue, ok := m.issues[id]; ok {
			issue.Status = "closed"
		} else {
			return beads.ErrNotFound
		}
	}
	return nil
}

// makeStepIssue creates a test step issue
func makeStepIssue(id, title, parent, status string, dependsOn []string) *beads.Issue {
	return &beads.Issue{
		ID:        id,
		Title:     title,
		Type:      "task",
		Status:    status,
		Priority:  2,
		Parent:    parent,
		DependsOn: dependsOn,
		CreatedAt: "2025-01-01T12:00:00Z",
		UpdatedAt: "2025-01-01T12:00:00Z",
	}
}

func TestFindNextReadyStep(t *testing.T) {
	tests := []struct {
		name           string
		moleculeID     string
		setupFunc      func(*mockBeadsForStep)
		wantStepID     string
		wantComplete   bool
		wantNilStep    bool
	}{
		{
			name:       "no steps - molecule complete",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				// Empty molecule - no children
			},
			wantComplete: true,
			wantNilStep:  true,
		},
		{
			name:       "all steps closed - molecule complete",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "closed", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "closed", []string{"gt-mol.1"}))
			},
			wantComplete: true,
			wantNilStep:  true,
		},
		{
			name:       "first step ready - no dependencies",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "open", []string{"gt-mol.1"}))
			},
			wantStepID:   "gt-mol.1",
			wantComplete: false,
		},
		{
			name:       "second step ready - first closed",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "closed", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "open", []string{"gt-mol.1"}))
			},
			wantStepID:   "gt-mol.2",
			wantComplete: false,
		},
		{
			name:       "all blocked - waiting on dependencies",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "in_progress", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "open", []string{"gt-mol.1"}))
				m.addIssue(makeStepIssue("gt-mol.3", "Step 3", "gt-mol", "open", []string{"gt-mol.2"}))
			},
			wantComplete: false,
			wantNilStep:  true, // No ready steps (all blocked or in-progress)
		},
		{
			name:       "parallel steps - multiple ready",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				// Both step 1 and 2 have no deps, so both are ready
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.3", "Synthesis", "gt-mol", "open", []string{"gt-mol.1", "gt-mol.2"}))
			},
			wantComplete: false,
			// Should return one of the ready steps (implementation returns first found)
		},
		{
			name:       "diamond dependency - synthesis blocked",
			moleculeID: "gt-mol",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step A", "gt-mol", "closed", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step B", "gt-mol", "open", nil)) // still open
				m.addIssue(makeStepIssue("gt-mol.3", "Synthesis", "gt-mol", "open", []string{"gt-mol.1", "gt-mol.2"}))
			},
			wantStepID:   "gt-mol.2", // B is ready (no deps)
			wantComplete: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := newMockBeadsForStep()
			tt.setupFunc(m)

			// Create a real Beads instance but we'll use our mock
			// For now, we test the logic by calling the actual function with mock data
			// This requires refactoring findNextReadyStep to accept an interface
			// For now, we'll test the logic inline

			// Get children from mock
			children, _ := m.List(beads.ListOptions{Parent: tt.moleculeID, Status: "all"})

			// Build closed IDs set - only "open" steps are candidates
			closedIDs := make(map[string]bool)
			var openSteps []*beads.Issue
			hasNonClosedSteps := false
			for _, child := range children {
				switch child.Status {
				case "closed":
					closedIDs[child.ID] = true
				case "open":
					openSteps = append(openSteps, child)
					hasNonClosedSteps = true
				default:
					// in_progress or other - not closed, not available
					hasNonClosedSteps = true
				}
			}

			// Check complete
			allComplete := !hasNonClosedSteps

			if allComplete != tt.wantComplete {
				t.Errorf("allComplete = %v, want %v", allComplete, tt.wantComplete)
			}

			if tt.wantComplete {
				return
			}

			// Find ready step
			var readyStep *beads.Issue
			for _, step := range openSteps {
				allDepsClosed := true
				for _, depID := range step.DependsOn {
					if !closedIDs[depID] {
						allDepsClosed = false
						break
					}
				}
				if len(step.DependsOn) == 0 || allDepsClosed {
					readyStep = step
					break
				}
			}

			if tt.wantNilStep {
				if readyStep != nil {
					t.Errorf("expected nil step, got %s", readyStep.ID)
				}
				return
			}

			if readyStep == nil {
				if tt.wantStepID != "" {
					t.Errorf("expected step %s, got nil", tt.wantStepID)
				}
				return
			}

			if tt.wantStepID != "" && readyStep.ID != tt.wantStepID {
				t.Errorf("readyStep.ID = %s, want %s", readyStep.ID, tt.wantStepID)
			}
		})
	}
}

// TestStepDoneScenarios tests complete step-done scenarios
func TestStepDoneScenarios(t *testing.T) {
	tests := []struct {
		name           string
		stepID         string
		setupFunc      func(*mockBeadsForStep)
		wantAction     string // "continue", "done", "no_more_ready"
		wantNextStep   string
	}{
		{
			name:   "complete step, continue to next",
			stepID: "gt-mol.1",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "open", []string{"gt-mol.1"}))
			},
			wantAction:   "continue",
			wantNextStep: "gt-mol.2",
		},
		{
			name:   "complete final step, molecule done",
			stepID: "gt-mol.2",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "closed", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "open", []string{"gt-mol.1"}))
			},
			wantAction: "done",
		},
		{
			name:   "complete step, remaining blocked",
			stepID: "gt-mol.1",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Step 1", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Step 2", "gt-mol", "in_progress", nil)) // another parallel task
				m.addIssue(makeStepIssue("gt-mol.3", "Synthesis", "gt-mol", "open", []string{"gt-mol.1", "gt-mol.2"}))
			},
			wantAction: "no_more_ready", // .2 is in_progress, .3 blocked
		},
		{
			name:   "parallel workflow - complete one, next ready",
			stepID: "gt-mol.1",
			setupFunc: func(m *mockBeadsForStep) {
				m.addIssue(makeStepIssue("gt-mol.1", "Parallel A", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.2", "Parallel B", "gt-mol", "open", nil))
				m.addIssue(makeStepIssue("gt-mol.3", "Synthesis", "gt-mol", "open", []string{"gt-mol.1", "gt-mol.2"}))
			},
			wantAction:   "continue",
			wantNextStep: "gt-mol.2", // B is still ready
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := newMockBeadsForStep()
			tt.setupFunc(m)

			// Extract molecule ID
			moleculeID := extractMoleculeIDFromStep(tt.stepID)
			if moleculeID == "" {
				t.Fatalf("could not extract molecule ID from %s", tt.stepID)
			}

			// Simulate closing the step
			if err := m.Close(tt.stepID); err != nil {
				t.Fatalf("failed to close step: %v", err)
			}

			// Now find next ready step
			children, _ := m.List(beads.ListOptions{Parent: moleculeID, Status: "all"})

			closedIDs := make(map[string]bool)
			var openSteps []*beads.Issue
			hasNonClosedSteps := false
			for _, child := range children {
				switch child.Status {
				case "closed":
					closedIDs[child.ID] = true
				case "open":
					openSteps = append(openSteps, child)
					hasNonClosedSteps = true
				default:
					// in_progress or other - not closed, not available
					hasNonClosedSteps = true
				}
			}

			allComplete := !hasNonClosedSteps

			var action string
			var nextStepID string

			if allComplete {
				action = "done"
			} else {
				// Find ready step
				var readyStep *beads.Issue
				for _, step := range openSteps {
					allDepsClosed := true
					for _, depID := range step.DependsOn {
						if !closedIDs[depID] {
							allDepsClosed = false
							break
						}
					}
					if len(step.DependsOn) == 0 || allDepsClosed {
						readyStep = step
						break
					}
				}

				if readyStep != nil {
					action = "continue"
					nextStepID = readyStep.ID
				} else {
					action = "no_more_ready"
				}
			}

			if action != tt.wantAction {
				t.Errorf("action = %s, want %s", action, tt.wantAction)
			}

			if tt.wantNextStep != "" && nextStepID != tt.wantNextStep {
				t.Errorf("nextStep = %s, want %s", nextStepID, tt.wantNextStep)
			}
		})
	}
}
