package main

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

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

type stateTestHelper struct {
	s   *sqlite.SQLiteStorage
	ctx context.Context
	t   *testing.T
}

func (h *stateTestHelper) createIssue(title string, issueType types.IssueType, priority int) *types.Issue {
	issue := &types.Issue{
		Title:     title,
		Priority:  priority,
		IssueType: issueType,
		Status:    types.StatusOpen,
	}
	if err := h.s.CreateIssue(h.ctx, issue, "test-user"); err != nil {
		h.t.Fatalf("Failed to create issue: %v", err)
	}
	return issue
}

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

func (h *stateTestHelper) removeLabel(issueID, label string) {
	if err := h.s.RemoveLabel(h.ctx, issueID, label, "test-user"); err != nil {
		h.t.Fatalf("Failed to remove label '%s': %v", label, err)
	}
}

func (h *stateTestHelper) getLabels(issueID string) []string {
	labels, err := h.s.GetLabels(h.ctx, issueID)
	if err != nil {
		h.t.Fatalf("Failed to get labels: %v", err)
	}
	return labels
}

// getStateValue extracts the value for a dimension from labels
func (h *stateTestHelper) getStateValue(issueID, dimension string) string {
	labels := h.getLabels(issueID)
	prefix := dimension + ":"
	for _, label := range labels {
		if strings.HasPrefix(label, prefix) {
			return strings.TrimPrefix(label, prefix)
		}
	}
	return ""
}

// getStates extracts all dimension:value labels as a map
func (h *stateTestHelper) getStates(issueID string) map[string]string {
	labels := h.getLabels(issueID)
	states := make(map[string]string)
	for _, label := range labels {
		if idx := strings.Index(label, ":"); idx > 0 {
			dimension := label[:idx]
			value := label[idx+1:]
			states[dimension] = value
		}
	}
	return states
}

func (h *stateTestHelper) assertStateValue(issueID, dimension, expected string) {
	actual := h.getStateValue(issueID, dimension)
	if actual != expected {
		h.t.Errorf("Expected %s=%s, got %s=%s", dimension, expected, dimension, actual)
	}
}

func (h *stateTestHelper) assertNoState(issueID, dimension string) {
	value := h.getStateValue(issueID, dimension)
	if value != "" {
		h.t.Errorf("Expected no %s state, got %s", dimension, value)
	}
}

func (h *stateTestHelper) assertStateCount(issueID string, expected int) {
	states := h.getStates(issueID)
	if len(states) != expected {
		h.t.Errorf("Expected %d states, got %d: %v", expected, len(states), states)
	}
}

func TestStateQueries(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "bd-test-state-*")
	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()

	ctx := context.Background()
	h := &stateTestHelper{s: s, ctx: ctx, t: t}

	t.Run("query state from label", func(t *testing.T) {
		issue := h.createIssue("Role Test", types.TypeTask, 1)
		h.addLabel(issue.ID, "patrol:active")
		h.assertStateValue(issue.ID, "patrol", "active")
	})

	t.Run("query multiple states", func(t *testing.T) {
		issue := h.createIssue("Multi State Test", types.TypeTask, 1)
		h.addLabel(issue.ID, "patrol:active")
		h.addLabel(issue.ID, "mode:normal")
		h.addLabel(issue.ID, "health:healthy")
		h.assertStateValue(issue.ID, "patrol", "active")
		h.assertStateValue(issue.ID, "mode", "normal")
		h.assertStateValue(issue.ID, "health", "healthy")
		h.assertStateCount(issue.ID, 3)
	})

	t.Run("query missing state returns empty", func(t *testing.T) {
		issue := h.createIssue("No State Test", types.TypeTask, 1)
		h.assertNoState(issue.ID, "patrol")
	})

	t.Run("state labels mixed with regular labels", func(t *testing.T) {
		issue := h.createIssue("Mixed Labels Test", types.TypeTask, 1)
		h.addLabel(issue.ID, "patrol:active")
		h.addLabel(issue.ID, "backend")  // Not a state label
		h.addLabel(issue.ID, "mode:normal")
		h.addLabel(issue.ID, "urgent")   // Not a state label
		h.assertStateValue(issue.ID, "patrol", "active")
		h.assertStateValue(issue.ID, "mode", "normal")
		h.assertStateCount(issue.ID, 2)
	})

	t.Run("state with colon in value", func(t *testing.T) {
		issue := h.createIssue("Colon Value Test", types.TypeTask, 1)
		h.addLabel(issue.ID, "error:code:500")
		value := h.getStateValue(issue.ID, "error")
		if value != "code:500" {
			t.Errorf("Expected 'code:500', got '%s'", value)
		}
	})
}

func TestStateTransitions(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "bd-test-state-transition-*")
	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()

	ctx := context.Background()
	h := &stateTestHelper{s: s, ctx: ctx, t: t}

	t.Run("change state value", func(t *testing.T) {
		issue := h.createIssue("Transition Test", types.TypeTask, 1)

		// Initial state
		h.addLabel(issue.ID, "patrol:active")
		h.assertStateValue(issue.ID, "patrol", "active")

		// Transition to muted (remove old, add new)
		h.removeLabel(issue.ID, "patrol:active")
		h.addLabel(issue.ID, "patrol:muted")
		h.assertStateValue(issue.ID, "patrol", "muted")
	})

	t.Run("prevent duplicate dimension values", func(t *testing.T) {
		issue := h.createIssue("Duplicate Prevention Test", types.TypeTask, 1)

		// Add initial state
		h.addLabel(issue.ID, "patrol:active")

		// If we add another value without removing, we'd have both
		// This is what the set-state command prevents
		h.addLabel(issue.ID, "patrol:muted")

		// Now we have both - this is the anti-pattern
		labels := h.getLabels(issue.ID)
		count := 0
		for _, l := range labels {
			if strings.HasPrefix(l, "patrol:") {
				count++
			}
		}
		if count != 2 {
			t.Errorf("Expected 2 patrol labels (anti-pattern), got %d", count)
		}

		// The getStateValue only returns the first one found
		// This demonstrates why proper transitions (remove then add) are needed
	})
}

func TestStatePatterns(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "bd-test-state-patterns-*")
	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()

	ctx := context.Background()
	h := &stateTestHelper{s: s, ctx: ctx, t: t}

	t.Run("common operational dimensions", func(t *testing.T) {
		issue := h.createIssue("Operations Role", types.TypeTask, 1)

		// Set up typical operational state
		h.addLabel(issue.ID, "patrol:active")
		h.addLabel(issue.ID, "mode:normal")
		h.addLabel(issue.ID, "health:healthy")
		h.addLabel(issue.ID, "sync:current")

		states := h.getStates(issue.ID)
		expected := map[string]string{
			"patrol": "active",
			"mode":   "normal",
			"health": "healthy",
			"sync":   "current",
		}

		for dim, val := range expected {
			if states[dim] != val {
				t.Errorf("Expected %s=%s, got %s=%s", dim, val, dim, states[dim])
			}
		}
	})

	t.Run("degraded mode example", func(t *testing.T) {
		issue := h.createIssue("Degraded Role", types.TypeTask, 1)

		// Start healthy
		h.addLabel(issue.ID, "health:healthy")
		h.addLabel(issue.ID, "mode:normal")

		// Degrade
		h.removeLabel(issue.ID, "mode:normal")
		h.addLabel(issue.ID, "mode:degraded")

		h.assertStateValue(issue.ID, "mode", "degraded")
		h.assertStateValue(issue.ID, "health", "healthy") // Health unchanged
	})
}
