package main

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

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

func TestDepAdd(t *testing.T) {
	tmpDir := t.TempDir()
	dbPath := filepath.Join(tmpDir, ".beads", "beads.db")
	
	sqliteStore := newTestStore(t, dbPath)

	ctx := context.Background()
	
	// Create test issues
	issues := []*types.Issue{
		{
			ID:        "test-1",
			Title:     "Task 1",
			Status:    types.StatusOpen,
			Priority:  1,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
		},
		{
			ID:        "test-2",
			Title:     "Task 2",
			Status:    types.StatusOpen,
			Priority:  1,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
		},
	}

	for _, issue := range issues {
		if err := sqliteStore.CreateIssue(ctx, issue, "test"); err != nil {
			t.Fatal(err)
		}
	}

	// Add dependency
	dep := &types.Dependency{
		IssueID:     "test-1",
		DependsOnID: "test-2",
		Type:        types.DepBlocks,
		CreatedAt:   time.Now(),
	}

	if err := sqliteStore.AddDependency(ctx, dep, "test"); err != nil {
		t.Fatalf("AddDependency failed: %v", err)
	}

	// Verify dependency was added
	deps, err := sqliteStore.GetDependencies(ctx, "test-1")
	if err != nil {
		t.Fatalf("GetDependencies failed: %v", err)
	}

	if len(deps) != 1 {
		t.Fatalf("Expected 1 dependency, got %d", len(deps))
	}

	if deps[0].ID != "test-2" {
		t.Errorf("Expected dependency on test-2, got %s", deps[0].ID)
	}
}

func TestDepTypes(t *testing.T) {
	tmpDir := t.TempDir()
	dbPath := filepath.Join(tmpDir, ".beads", "beads.db")
	
	sqliteStore := newTestStore(t, dbPath)

	ctx := context.Background()
	
	// Create test issues
	for i := 1; i <= 4; i++ {
		issue := &types.Issue{
			ID:        fmt.Sprintf("test-%d", i),
			Title:     fmt.Sprintf("Task %d", i),
			Status:    types.StatusOpen,
			Priority:  1,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
		}
		if err := sqliteStore.CreateIssue(ctx, issue, "test"); err != nil {
			t.Fatal(err)
		}
	}

	// Test different dependency types (without creating cycles)
	depTypes := []struct {
		depType types.DependencyType
		from    string
		to      string
	}{
		{types.DepBlocks, "test-2", "test-1"},
		{types.DepRelated, "test-3", "test-1"},
		{types.DepParentChild, "test-4", "test-1"},
		{types.DepDiscoveredFrom, "test-3", "test-2"},
	}

	for _, dt := range depTypes {
		dep := &types.Dependency{
			IssueID:     dt.from,
			DependsOnID: dt.to,
			Type:        dt.depType,
			CreatedAt:   time.Now(),
		}

		if err := sqliteStore.AddDependency(ctx, dep, "test"); err != nil {
			t.Fatalf("AddDependency failed for type %s: %v", dt.depType, err)
		}
	}
}

func TestDepCycleDetection(t *testing.T) {
	tmpDir := t.TempDir()
	dbPath := filepath.Join(tmpDir, ".beads", "beads.db")
	
	sqliteStore := newTestStore(t, dbPath)

	ctx := context.Background()
	
	// Create test issues
	for i := 1; i <= 3; i++ {
		issue := &types.Issue{
			ID:        fmt.Sprintf("test-%d", i),
			Title:     fmt.Sprintf("Task %d", i),
			Status:    types.StatusOpen,
			Priority:  1,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
		}
		if err := sqliteStore.CreateIssue(ctx, issue, "test"); err != nil {
			t.Fatal(err)
		}
	}

	// Create a cycle: test-1 -> test-2 -> test-3 -> test-1
	// Add first two deps successfully
	deps := []struct {
		from string
		to   string
	}{
		{"test-1", "test-2"},
		{"test-2", "test-3"},
	}

	for _, d := range deps {
		dep := &types.Dependency{
			IssueID:     d.from,
			DependsOnID: d.to,
			Type:        types.DepBlocks,
			CreatedAt:   time.Now(),
		}
		if err := sqliteStore.AddDependency(ctx, dep, "test"); err != nil {
			t.Fatalf("AddDependency failed: %v", err)
		}
	}
	
	// Try to add the third dep which would create a cycle - should fail
	cycleDep := &types.Dependency{
		IssueID:     "test-3",
		DependsOnID: "test-1",
		Type:        types.DepBlocks,
		CreatedAt:   time.Now(),
	}
	if err := sqliteStore.AddDependency(ctx, cycleDep, "test"); err == nil {
		t.Fatal("Expected AddDependency to fail when creating cycle, but it succeeded")
	}

	// Since cycle detection prevented the cycle, DetectCycles should find no cycles
	cycles, err := sqliteStore.DetectCycles(ctx)
	if err != nil {
		t.Fatalf("DetectCycles failed: %v", err)
	}

	if len(cycles) != 0 {
		t.Error("Expected no cycles since cycle was prevented")
	}
}

func TestDepCommandsInit(t *testing.T) {
	if depCmd == nil {
		t.Fatal("depCmd should be initialized")
	}
	
	if depCmd.Use != "dep" {
		t.Errorf("Expected Use='dep', got %q", depCmd.Use)
	}

	if depAddCmd == nil {
		t.Fatal("depAddCmd should be initialized")
	}

	if depRemoveCmd == nil {
		t.Fatal("depRemoveCmd should be initialized")
	}
}

func TestDepRemove(t *testing.T) {
	tmpDir := t.TempDir()
	dbPath := filepath.Join(tmpDir, ".beads", "beads.db")
	
	sqliteStore := newTestStore(t, dbPath)

	ctx := context.Background()
	
	// Create test issues
	issues := []*types.Issue{
		{
			ID:        "test-1",
			Title:     "Task 1",
			Status:    types.StatusOpen,
			Priority:  1,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
		},
		{
			ID:        "test-2",
			Title:     "Task 2",
			Status:    types.StatusOpen,
			Priority:  1,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
		},
	}

	for _, issue := range issues {
		if err := sqliteStore.CreateIssue(ctx, issue, "test"); err != nil {
			t.Fatal(err)
		}
	}

	// Add dependency
	dep := &types.Dependency{
		IssueID:     "test-1",
		DependsOnID: "test-2",
		Type:        types.DepBlocks,
		CreatedAt:   time.Now(),
	}

	if err := sqliteStore.AddDependency(ctx, dep, "test"); err != nil {
		t.Fatal(err)
	}

	// Remove dependency
	if err := sqliteStore.RemoveDependency(ctx, "test-1", "test-2", "test"); err != nil {
		t.Fatalf("RemoveDependency failed: %v", err)
	}

	// Verify dependency was removed
	deps, err := sqliteStore.GetDependencies(ctx, "test-1")
	if err != nil {
		t.Fatalf("GetDependencies failed: %v", err)
	}

	if len(deps) != 0 {
		t.Errorf("Expected 0 dependencies after removal, got %d", len(deps))
	}
}

func TestDepTreeFormatFlag(t *testing.T) {
	// Test that the --format flag exists on depTreeCmd
	flag := depTreeCmd.Flags().Lookup("format")
	if flag == nil {
		t.Fatal("depTreeCmd should have --format flag")
	}

	// Test default value is empty string
	if flag.DefValue != "" {
		t.Errorf("Expected default format='', got %q", flag.DefValue)
	}

	// Test usage text mentions mermaid
	if !strings.Contains(flag.Usage, "mermaid") {
		t.Errorf("Expected flag usage to mention 'mermaid', got %q", flag.Usage)
	}
}

func TestGetStatusEmoji(t *testing.T) {
	tests := []struct {
		status types.Status
		want   string
	}{
		{types.StatusOpen, "☐"},
		{types.StatusInProgress, "◧"},
		{types.StatusBlocked, "⚠"},
		{types.StatusClosed, "☑"},
		{types.Status("unknown"), "?"},
	}

	for _, tt := range tests {
		t.Run(string(tt.status), func(t *testing.T) {
			got := getStatusEmoji(tt.status)
			if got != tt.want {
				t.Errorf("getStatusEmoji(%q) = %q, want %q", tt.status, got, tt.want)
			}
		})
	}
}

func TestOutputMermaidTree(t *testing.T) {
	tests := []struct {
		name   string
		tree   []*types.TreeNode
		rootID string
		want   []string // Lines that must appear in output
	}{
		{
			name:   "empty tree",
			tree:   []*types.TreeNode{},
			rootID: "test-1",
			want: []string{
				"flowchart TD",
				`test-1["No dependencies"]`,
			},
		},
		{
			name: "single dependency",
			tree: []*types.TreeNode{
				{
					Issue:    types.Issue{ID: "test-1", Title: "Task 1", Status: types.StatusInProgress},
					Depth:    0,
					ParentID: "",
				},
				{
					Issue:    types.Issue{ID: "test-2", Title: "Task 2", Status: types.StatusClosed},
					Depth:    1,
					ParentID: "test-1",
				},
			},
			rootID: "test-1",
			want: []string{
				"flowchart TD",
				`test-1["◧ test-1: Task 1"]`,
				`test-2["☑ test-2: Task 2"]`,
				"test-1 --> test-2",
			},
		},
		{
			name: "multiple dependencies",
			tree: []*types.TreeNode{
				{
					Issue:    types.Issue{ID: "test-1", Title: "Main", Status: types.StatusOpen},
					Depth:    0,
					ParentID: "",
				},
				{
					Issue:    types.Issue{ID: "test-2", Title: "Sub 1", Status: types.StatusClosed},
					Depth:    1,
					ParentID: "test-1",
				},
				{
					Issue:    types.Issue{ID: "test-3", Title: "Sub 2", Status: types.StatusBlocked},
					Depth:    1,
					ParentID: "test-1",
				},
			},
			rootID: "test-1",
			want: []string{
				"flowchart TD",
				`test-1["☐ test-1: Main"]`,
				`test-2["☑ test-2: Sub 1"]`,
				`test-3["⚠ test-3: Sub 2"]`,
				"test-1 --> test-2",
				"test-1 --> test-3",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Capture stdout
			old := os.Stdout
			r, w, _ := os.Pipe()
			os.Stdout = w

			outputMermaidTree(tt.tree, tt.rootID)

			w.Close()
			os.Stdout = old

			var buf bytes.Buffer
			io.Copy(&buf, r)
			output := buf.String()

			// Verify all expected lines appear
			for _, line := range tt.want {
				if !strings.Contains(output, line) {
					t.Errorf("expected output to contain %q, got:\n%s", line, output)
				}
			}
		})
	}
}

func TestOutputMermaidTree_Siblings(t *testing.T) {
	// Test case: Siblings with children (reproduces issue with wrong parent inference)
	// Structure:
	//   BD-1 (root)
	//   ├── BD-2 (sibling 1)
	//   │   └── BD-4 (child of BD-2)
	//   └── BD-3 (sibling 2)
	//       └── BD-5 (child of BD-3)
	tree := []*types.TreeNode{
		{
			Issue:    types.Issue{ID: "BD-1", Title: "Parent", Status: types.StatusOpen},
			Depth:    0,
			ParentID: "",
		},
		{
			Issue:    types.Issue{ID: "BD-2", Title: "Sibling 1", Status: types.StatusOpen},
			Depth:    1,
			ParentID: "BD-1",
		},
		{
			Issue:    types.Issue{ID: "BD-3", Title: "Sibling 2", Status: types.StatusOpen},
			Depth:    1,
			ParentID: "BD-1",
		},
		{
			Issue:    types.Issue{ID: "BD-4", Title: "Child of Sibling 1", Status: types.StatusOpen},
			Depth:    2,
			ParentID: "BD-2",
		},
		{
			Issue:    types.Issue{ID: "BD-5", Title: "Child of Sibling 2", Status: types.StatusOpen},
			Depth:    2,
			ParentID: "BD-3",
		},
	}

	// Capture stdout
	old := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	outputMermaidTree(tree, "BD-1")

	w.Close()
	os.Stdout = old

	var buf bytes.Buffer
	io.Copy(&buf, r)
	output := buf.String()

	// Verify correct edges exist
	correctEdges := []string{
		"BD-1 --> BD-2",
		"BD-1 --> BD-3",
		"BD-2 --> BD-4",
		"BD-3 --> BD-5",
	}

	for _, edge := range correctEdges {
		if !strings.Contains(output, edge) {
			t.Errorf("expected edge %q to be present, got:\n%s", edge, output)
		}
	}

	// Verify incorrect edges do NOT exist (siblings shouldn't be connected)
	incorrectEdges := []string{
		"BD-2 --> BD-3",   // Siblings shouldn't be connected
		"BD-3 --> BD-4",   // BD-4's parent is BD-2, not BD-3
		"BD-4 --> BD-3",   // Wrong direction
		"BD-4 --> BD-5",   // These are cousins, not parent-child
	}

	for _, edge := range incorrectEdges {
		if strings.Contains(output, edge) {
			t.Errorf("incorrect edge %q should NOT be present, got:\n%s", edge, output)
		}
	}
}
