package main

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

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

// listTestHelper provides test setup and assertion methods
type listTestHelper struct {
	t      *testing.T
	ctx    context.Context
	store  *sqlite.SQLiteStorage
	issues []*types.Issue
}

func newListTestHelper(t *testing.T, store *sqlite.SQLiteStorage) *listTestHelper {
	return &listTestHelper{t: t, ctx: context.Background(), store: store}
}

func (h *listTestHelper) createTestIssues() {
	now := time.Now()
	h.issues = []*types.Issue{
		{
			Title:       "Bug Issue",
			Description: "Test bug",
			Priority:    0,
			IssueType:   types.TypeBug,
			Status:      types.StatusOpen,
		},
		{
			Title:       "Feature Issue",
			Description: "Test feature",
			Priority:    1,
			IssueType:   types.TypeFeature,
			Status:      types.StatusInProgress,
			Assignee:    testUserAlice,
		},
		{
			Title:       "Task Issue",
			Description: "Test task",
			Priority:    2,
			IssueType:   types.TypeTask,
			Status:      types.StatusClosed,
			ClosedAt:    &now,
		},
	}
	for _, issue := range h.issues {
		if err := h.store.CreateIssue(h.ctx, issue, "test-user"); err != nil {
			h.t.Fatalf("Failed to create issue: %v", err)
		}
	}
}

func (h *listTestHelper) addLabel(id, label string) {
	if err := h.store.AddLabel(h.ctx, id, label, "test-user"); err != nil {
		h.t.Fatalf("Failed to add label: %v", err)
	}
}

func (h *listTestHelper) search(filter types.IssueFilter) []*types.Issue {
	results, err := h.store.SearchIssues(h.ctx, "", filter)
	if err != nil {
		h.t.Fatalf("Failed to search issues: %v", err)
	}
	return results
}

func (h *listTestHelper) assertCount(count, expected int, desc string) {
	if count != expected {
		h.t.Errorf("Expected %d %s, got %d", expected, desc, count)
	}
}

func (h *listTestHelper) assertEqual(expected, actual interface{}, field string) {
	if expected != actual {
		h.t.Errorf("Expected %s %v, got %v", field, expected, actual)
	}
}

func (h *listTestHelper) assertAtMost(count, maxCount int, desc string) {
	if count > maxCount {
		h.t.Errorf("Expected at most %d %s, got %d", maxCount, desc, count)
	}
}

func TestListCommand(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "bd-test-list-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	testDB := filepath.Join(tmpDir, "test.db")
	s := newTestStore(t, testDB)
	defer s.Close()

	h := newListTestHelper(t, s)
	h.createTestIssues()
	h.addLabel(h.issues[0].ID, "critical")

	t.Run("list all issues", func(t *testing.T) {
		results := h.search(types.IssueFilter{})
		h.assertCount(len(results), 3, "issues")
	})

	t.Run("filter by status", func(t *testing.T) {
		status := types.StatusOpen
		results := h.search(types.IssueFilter{Status: &status})
		h.assertCount(len(results), 1, "open issues")
		h.assertEqual(types.StatusOpen, results[0].Status, "status")
	})

	t.Run("filter by priority", func(t *testing.T) {
		priority := 0
		results := h.search(types.IssueFilter{Priority: &priority})
		h.assertCount(len(results), 1, "P0 issues")
		h.assertEqual(0, results[0].Priority, "priority")
	})

	t.Run("filter by assignee", func(t *testing.T) {
		assignee := testUserAlice
		results := h.search(types.IssueFilter{Assignee: &assignee})
		h.assertCount(len(results), 1, "issues for alice")
		h.assertEqual(testUserAlice, results[0].Assignee, "assignee")
	})

	t.Run("filter by issue type", func(t *testing.T) {
		issueType := types.TypeBug
		results := h.search(types.IssueFilter{IssueType: &issueType})
		h.assertCount(len(results), 1, "bug issues")
		h.assertEqual(types.TypeBug, results[0].IssueType, "type")
	})

	t.Run("filter by label", func(t *testing.T) {
		results := h.search(types.IssueFilter{Labels: []string{"critical"}})
		h.assertCount(len(results), 1, "issues with critical label")
	})

	t.Run("filter by title search", func(t *testing.T) {
		results := h.search(types.IssueFilter{TitleSearch: "Bug"})
		h.assertCount(len(results), 1, "issues matching 'Bug'")
	})

	t.Run("limit results", func(t *testing.T) {
		results := h.search(types.IssueFilter{Limit: 2})
		h.assertAtMost(len(results), 2, "issues")
	})

	t.Run("normalize labels", func(t *testing.T) {
		labels := []string{" bug ", "critical", "", "bug", "  feature  "}
		normalized := normalizeLabels(labels)
		expected := []string{"bug", "critical", "feature"}
		h.assertCount(len(normalized), len(expected), "normalized labels")

		// Check deduplication and trimming
		seen := make(map[string]bool)
		for _, label := range normalized {
			if label == "" {
				t.Error("Found empty label after normalization")
			}
			if label != strings.TrimSpace(label) {
				t.Errorf("Label not trimmed: '%s'", label)
			}
			if seen[label] {
				t.Errorf("Duplicate label found: %s", label)
			}
			seen[label] = true
		}
	})

	t.Run("output dot format", func(t *testing.T) {
		// Add a dependency to make the graph more interesting
		dep := &types.Dependency{
			IssueID:     h.issues[0].ID,
			DependsOnID: h.issues[1].ID,
			Type:        types.DepBlocks,
		}
		if err := h.store.AddDependency(h.ctx, dep, "test-user"); err != nil {
			t.Fatalf("Failed to add dependency: %v", err)
		}

		err := outputDotFormat(h.ctx, h.store, h.issues)
		if err != nil {
			t.Errorf("outputDotFormat failed: %v", err)
		}
	})

	t.Run("output formatted list dot", func(t *testing.T) {
		err := outputFormattedList(h.ctx, h.store, h.issues, "dot")
		if err != nil {
			t.Errorf("outputFormattedList with dot format failed: %v", err)
		}
	})

	t.Run("output formatted list digraph preset", func(t *testing.T) {
		// Dependency already added in previous test, just use it
		err := outputFormattedList(h.ctx, h.store, h.issues, "digraph")
		if err != nil {
			t.Errorf("outputFormattedList with digraph format failed: %v", err)
		}
	})

	t.Run("output formatted list custom template", func(t *testing.T) {
		err := outputFormattedList(h.ctx, h.store, h.issues, "{{.ID}} {{.Title}}")
		if err != nil {
			t.Errorf("outputFormattedList with custom template failed: %v", err)
		}
	})

	t.Run("output formatted list invalid template", func(t *testing.T) {
		err := outputFormattedList(h.ctx, h.store, h.issues, "{{.ID")
		if err == nil {
			t.Error("Expected error for invalid template")
		}
	})
}
