package sqlite

import (
	"context"
	"testing"
	"time"

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

// TestRelatesTo verifies relates-to dependencies work via the dependency API.
// Per Decision 004, relates-to links are now stored in the dependencies table.
func TestRelatesTo(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create two issues
	issue1 := &types.Issue{
		Title:     "Issue 1",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	issue2 := &types.Issue{
		Title:     "Issue 2",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := store.CreateIssue(ctx, issue1, "test"); err != nil {
		t.Fatalf("Failed to create issue1: %v", err)
	}
	if err := store.CreateIssue(ctx, issue2, "test"); err != nil {
		t.Fatalf("Failed to create issue2: %v", err)
	}

	// Add relates-to dependency (bidirectional)
	dep1 := &types.Dependency{
		IssueID:     issue1.ID,
		DependsOnID: issue2.ID,
		Type:        types.DepRelatesTo,
	}
	if err := store.AddDependency(ctx, dep1, "test"); err != nil {
		t.Fatalf("Failed to add relates-to dep1: %v", err)
	}
	dep2 := &types.Dependency{
		IssueID:     issue2.ID,
		DependsOnID: issue1.ID,
		Type:        types.DepRelatesTo,
	}
	if err := store.AddDependency(ctx, dep2, "test"); err != nil {
		t.Fatalf("Failed to add relates-to dep2: %v", err)
	}

	// Verify links via GetDependenciesWithMetadata
	deps1, err := store.GetDependenciesWithMetadata(ctx, issue1.ID)
	if err != nil {
		t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
	}
	found1 := false
	for _, d := range deps1 {
		if d.ID == issue2.ID && d.DependencyType == types.DepRelatesTo {
			found1 = true
		}
	}
	if !found1 {
		t.Errorf("issue1 should have relates-to link to issue2")
	}

	deps2, err := store.GetDependenciesWithMetadata(ctx, issue2.ID)
	if err != nil {
		t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
	}
	found2 := false
	for _, d := range deps2 {
		if d.ID == issue1.ID && d.DependencyType == types.DepRelatesTo {
			found2 = true
		}
	}
	if !found2 {
		t.Errorf("issue2 should have relates-to link to issue1")
	}
}

func TestRelatesTo_MultipleLinks(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create three issues
	issues := make([]*types.Issue, 3)
	for i := range issues {
		issues[i] = &types.Issue{
			Title:     "Issue",
			Status:    types.StatusOpen,
			Priority:  2,
			IssueType: types.TypeTask,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}
		if err := store.CreateIssue(ctx, issues[i], "test"); err != nil {
			t.Fatalf("Failed to create issue %d: %v", i, err)
		}
	}

	// Link issue0 to both issue1 and issue2
	for _, targetIssue := range []*types.Issue{issues[1], issues[2]} {
		dep := &types.Dependency{
			IssueID:     issues[0].ID,
			DependsOnID: targetIssue.ID,
			Type:        types.DepRelatesTo,
		}
		if err := store.AddDependency(ctx, dep, "test"); err != nil {
			t.Fatalf("Failed to add relates-to: %v", err)
		}
	}

	// Verify
	deps, err := store.GetDependenciesWithMetadata(ctx, issues[0].ID)
	if err != nil {
		t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
	}
	relatesCount := 0
	for _, d := range deps {
		if d.DependencyType == types.DepRelatesTo {
			relatesCount++
		}
	}
	if relatesCount != 2 {
		t.Errorf("RelatesTo has %d links, want 2", relatesCount)
	}
}

// TestDuplicateOf verifies duplicates dependencies work via the dependency API.
func TestDuplicateOf(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create canonical and duplicate issues
	canonical := &types.Issue{
		Title:     "Canonical Issue",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeBug,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	duplicate := &types.Issue{
		Title:     "Duplicate Issue",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeBug,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := store.CreateIssue(ctx, canonical, "test"); err != nil {
		t.Fatalf("Failed to create canonical: %v", err)
	}
	if err := store.CreateIssue(ctx, duplicate, "test"); err != nil {
		t.Fatalf("Failed to create duplicate: %v", err)
	}

	// Add duplicates dependency
	dep := &types.Dependency{
		IssueID:     duplicate.ID,
		DependsOnID: canonical.ID,
		Type:        types.DepDuplicates,
	}
	if err := store.AddDependency(ctx, dep, "test"); err != nil {
		t.Fatalf("Failed to add duplicates dep: %v", err)
	}

	// Close the duplicate
	if err := store.CloseIssue(ctx, duplicate.ID, "Closed as duplicate", "test"); err != nil {
		t.Fatalf("Failed to close duplicate: %v", err)
	}

	// Verify dependency
	deps, err := store.GetDependenciesWithMetadata(ctx, duplicate.ID)
	if err != nil {
		t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
	}
	found := false
	for _, d := range deps {
		if d.ID == canonical.ID && d.DependencyType == types.DepDuplicates {
			found = true
		}
	}
	if !found {
		t.Errorf("duplicate should have duplicates link to canonical")
	}

	// Verify closed status
	updated, err := store.GetIssue(ctx, duplicate.ID)
	if err != nil {
		t.Fatalf("GetIssue failed: %v", err)
	}
	if updated.Status != types.StatusClosed {
		t.Errorf("Status = %q, want %q", updated.Status, types.StatusClosed)
	}
}

// TestSupersededBy verifies supersedes dependencies work via the dependency API.
func TestSupersededBy(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create old and new versions
	oldVersion := &types.Issue{
		Title:     "Design Doc v1",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	newVersion := &types.Issue{
		Title:     "Design Doc v2",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := store.CreateIssue(ctx, oldVersion, "test"); err != nil {
		t.Fatalf("Failed to create old version: %v", err)
	}
	if err := store.CreateIssue(ctx, newVersion, "test"); err != nil {
		t.Fatalf("Failed to create new version: %v", err)
	}

	// Add supersedes dependency (newVersion supersedes oldVersion)
	// Stored as: oldVersion depends on newVersion with type supersedes
	dep := &types.Dependency{
		IssueID:     oldVersion.ID,
		DependsOnID: newVersion.ID,
		Type:        types.DepSupersedes,
	}
	if err := store.AddDependency(ctx, dep, "test"); err != nil {
		t.Fatalf("Failed to add supersedes dep: %v", err)
	}

	// Close old version
	if err := store.CloseIssue(ctx, oldVersion.ID, "Superseded by v2", "test"); err != nil {
		t.Fatalf("Failed to close old version: %v", err)
	}

	// Verify dependency
	deps, err := store.GetDependenciesWithMetadata(ctx, oldVersion.ID)
	if err != nil {
		t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
	}
	found := false
	for _, d := range deps {
		if d.ID == newVersion.ID && d.DependencyType == types.DepSupersedes {
			found = true
		}
	}
	if !found {
		t.Errorf("oldVersion should have supersedes link to newVersion")
	}

	// Verify closed status
	updated, err := store.GetIssue(ctx, oldVersion.ID)
	if err != nil {
		t.Fatalf("GetIssue failed: %v", err)
	}
	if updated.Status != types.StatusClosed {
		t.Errorf("Status = %q, want %q", updated.Status, types.StatusClosed)
	}
}

// TestRepliesTo verifies replies-to dependencies work via the dependency API.
func TestRepliesTo(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create original message and reply
	original := &types.Issue{
		Title:       "Original Message",
		Description: "Original content",
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeMessage,
		Sender:      "alice",
		Assignee:    "bob",
		Ephemeral:        true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	reply := &types.Issue{
		Title:       "Re: Original Message",
		Description: "Reply content",
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeMessage,
		Sender:      "bob",
		Assignee:    "alice",
		Ephemeral:        true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	if err := store.CreateIssue(ctx, original, "test"); err != nil {
		t.Fatalf("Failed to create original: %v", err)
	}
	if err := store.CreateIssue(ctx, reply, "test"); err != nil {
		t.Fatalf("Failed to create reply: %v", err)
	}

	// Add replies-to dependency
	dep := &types.Dependency{
		IssueID:     reply.ID,
		DependsOnID: original.ID,
		Type:        types.DepRepliesTo,
		ThreadID:    original.ID, // Thread root is the original message
	}
	if err := store.AddDependency(ctx, dep, "test"); err != nil {
		t.Fatalf("Failed to add replies-to dep: %v", err)
	}

	// Verify thread link
	deps, err := store.GetDependenciesWithMetadata(ctx, reply.ID)
	if err != nil {
		t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
	}
	found := false
	for _, d := range deps {
		if d.ID == original.ID && d.DependencyType == types.DepRepliesTo {
			found = true
		}
	}
	if !found {
		t.Errorf("reply should have replies-to link to original")
	}
}

func TestRepliesTo_Chain(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create a chain of replies
	messages := make([]*types.Issue, 3)
	var prevID string

	for i := range messages {
		messages[i] = &types.Issue{
			Title:     "Message",
			Status:    types.StatusOpen,
			Priority:  2,
			IssueType: types.TypeMessage,
			Sender:    "user",
			Assignee:  "inbox",
			Ephemeral: true,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}
		if err := store.CreateIssue(ctx, messages[i], "test"); err != nil {
			t.Fatalf("Failed to create message %d: %v", i, err)
		}

		// Add replies-to dependency for subsequent messages
		if prevID != "" {
			dep := &types.Dependency{
				IssueID:     messages[i].ID,
				DependsOnID: prevID,
				Type:        types.DepRepliesTo,
				ThreadID:    messages[0].ID, // Thread root is the first message
			}
			if err := store.AddDependency(ctx, dep, "test"); err != nil {
				t.Fatalf("Failed to add replies-to dep for message %d: %v", i, err)
			}
		}
		prevID = messages[i].ID
	}

	// Verify chain by checking dependents
	for i := 0; i < len(messages)-1; i++ {
		dependents, err := store.GetDependentsWithMetadata(ctx, messages[i].ID)
		if err != nil {
			t.Fatalf("GetDependentsWithMetadata failed for message %d: %v", i, err)
		}
		found := false
		for _, d := range dependents {
			if d.ID == messages[i+1].ID && d.DependencyType == types.DepRepliesTo {
				found = true
			}
		}
		if !found {
			t.Errorf("Message %d should have reply from message %d", i, i+1)
		}
	}
}

func TestWispField(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create wisp issue
	wisp := &types.Issue{
		Title:     "Wisp Issue",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeMessage,
		Ephemeral:      true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// Create non-wisp issue
	permanent := &types.Issue{
		Title:     "Permanent Issue",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeTask,
		Ephemeral:      false,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := store.CreateIssue(ctx, wisp, "test"); err != nil {
		t.Fatalf("Failed to create wisp: %v", err)
	}
	if err := store.CreateIssue(ctx, permanent, "test"); err != nil {
		t.Fatalf("Failed to create permanent: %v", err)
	}

	// Verify wisp flag
	savedWisp, err := store.GetIssue(ctx, wisp.ID)
	if err != nil {
		t.Fatalf("GetIssue failed: %v", err)
	}
	if !savedWisp.Ephemeral {
		t.Error("Wisp issue should have Wisp=true")
	}

	savedPermanent, err := store.GetIssue(ctx, permanent.ID)
	if err != nil {
		t.Fatalf("GetIssue failed: %v", err)
	}
	if savedPermanent.Ephemeral {
		t.Error("Permanent issue should have Wisp=false")
	}
}

func TestWispFilter(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create mix of wisp and non-wisp issues
	for i := 0; i < 3; i++ {
		wisp := &types.Issue{
			Title:     "Wisp",
			Status:    types.StatusClosed, // Closed for cleanup test
			Priority:  2,
			IssueType: types.TypeMessage,
			Ephemeral:      true,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}
		if err := store.CreateIssue(ctx, wisp, "test"); err != nil {
			t.Fatalf("Failed to create wisp %d: %v", i, err)
		}
	}

	for i := 0; i < 2; i++ {
		permanent := &types.Issue{
			Title:     "Permanent",
			Status:    types.StatusClosed,
			Priority:  2,
			IssueType: types.TypeTask,
			Ephemeral:      false,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}
		if err := store.CreateIssue(ctx, permanent, "test"); err != nil {
			t.Fatalf("Failed to create permanent %d: %v", i, err)
		}
	}

	// Filter for wisp only
	wispTrue := true
	closedStatus := types.StatusClosed
	wispFilter := types.IssueFilter{
		Status: &closedStatus,
		Ephemeral:   &wispTrue,
	}

	wispIssues, err := store.SearchIssues(ctx, "", wispFilter)
	if err != nil {
		t.Fatalf("SearchIssues failed: %v", err)
	}
	if len(wispIssues) != 3 {
		t.Errorf("Expected 3 wisp issues, got %d", len(wispIssues))
	}

	// Filter for non-wisp only
	wispFalse := false
	nonWispFilter := types.IssueFilter{
		Status: &closedStatus,
		Ephemeral:   &wispFalse,
	}

	permanentIssues, err := store.SearchIssues(ctx, "", nonWispFilter)
	if err != nil {
		t.Fatalf("SearchIssues failed: %v", err)
	}
	if len(permanentIssues) != 2 {
		t.Errorf("Expected 2 non-wisp issues, got %d", len(permanentIssues))
	}
}

func TestSenderField(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create issue with sender
	msg := &types.Issue{
		Title:     "Message",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeMessage,
		Sender:    "alice@example.com",
		Assignee:  "bob@example.com",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := store.CreateIssue(ctx, msg, "test"); err != nil {
		t.Fatalf("Failed to create message: %v", err)
	}

	// Verify sender is preserved
	saved, err := store.GetIssue(ctx, msg.ID)
	if err != nil {
		t.Fatalf("GetIssue failed: %v", err)
	}
	if saved.Sender != "alice@example.com" {
		t.Errorf("Sender = %q, want %q", saved.Sender, "alice@example.com")
	}
}

func TestMessageType(t *testing.T) {
	store, cleanup := setupTestDB(t)
	defer cleanup()
	ctx := context.Background()

	// Create a message type issue
	msg := &types.Issue{
		Title:     "Test Message",
		Status:    types.StatusOpen,
		Priority:  2,
		IssueType: types.TypeMessage,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := store.CreateIssue(ctx, msg, "test"); err != nil {
		t.Fatalf("Failed to create message: %v", err)
	}

	// Verify type is preserved
	saved, err := store.GetIssue(ctx, msg.ID)
	if err != nil {
		t.Fatalf("GetIssue failed: %v", err)
	}
	if saved.IssueType != types.TypeMessage {
		t.Errorf("IssueType = %q, want %q", saved.IssueType, types.TypeMessage)
	}

	// Filter by message type
	messageType := types.TypeMessage
	filter := types.IssueFilter{
		IssueType: &messageType,
	}

	messages, err := store.SearchIssues(ctx, "", filter)
	if err != nil {
		t.Fatalf("SearchIssues failed: %v", err)
	}
	if len(messages) != 1 {
		t.Errorf("Expected 1 message, got %d", len(messages))
	}
}
