package sqlite

import (
	"context"
	"os"
	"testing"

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

func TestGetNextChildID(t *testing.T) {
	tmpFile := t.TempDir() + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create a parent issue with hash ID
	parent := &types.Issue{
		ID:          "bd-a3f8e9",
		Title:       "Parent Epic",
		Description: "Parent issue",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeEpic,
	}
	if err := store.CreateIssue(ctx, parent, "test"); err != nil {
		t.Fatalf("failed to create parent: %v", err)
	}

	// Test: Generate first child ID
	childID1, err := store.GetNextChildID(ctx, parent.ID)
	if err != nil {
		t.Fatalf("GetNextChildID failed: %v", err)
	}
	expectedID1 := "bd-a3f8e9.1"
	if childID1 != expectedID1 {
		t.Errorf("expected %s, got %s", expectedID1, childID1)
	}

	// Test: Generate second child ID (sequential)
	childID2, err := store.GetNextChildID(ctx, parent.ID)
	if err != nil {
		t.Fatalf("GetNextChildID failed: %v", err)
	}
	expectedID2 := "bd-a3f8e9.2"
	if childID2 != expectedID2 {
		t.Errorf("expected %s, got %s", expectedID2, childID2)
	}

	// Create the first child and test nested hierarchy
	child1 := &types.Issue{
		ID:          childID1,
		Title:       "Child Task 1",
		Description: "First child",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, child1, "test"); err != nil {
		t.Fatalf("failed to create child: %v", err)
	}

	// Test: Generate nested child (depth 2)
	nestedID1, err := store.GetNextChildID(ctx, childID1)
	if err != nil {
		t.Fatalf("GetNextChildID failed for nested: %v", err)
	}
	expectedNested1 := "bd-a3f8e9.1.1"
	if nestedID1 != expectedNested1 {
		t.Errorf("expected %s, got %s", expectedNested1, nestedID1)
	}

	// Create the nested child
	nested1 := &types.Issue{
		ID:          nestedID1,
		Title:       "Nested Task",
		Description: "Nested child",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, nested1, "test"); err != nil {
		t.Fatalf("failed to create nested child: %v", err)
	}

	// Test: Generate third level (depth 3, maximum)
	deepID1, err := store.GetNextChildID(ctx, nestedID1)
	if err != nil {
		t.Fatalf("GetNextChildID failed for depth 3: %v", err)
	}
	expectedDeep1 := "bd-a3f8e9.1.1.1"
	if deepID1 != expectedDeep1 {
		t.Errorf("expected %s, got %s", expectedDeep1, deepID1)
	}

	// Create the deep child
	deep1 := &types.Issue{
		ID:          deepID1,
		Title:       "Deep Task",
		Description: "Third level",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, deep1, "test"); err != nil {
		t.Fatalf("failed to create deep child: %v", err)
	}

	// Test: Attempt to create fourth level (should fail)
	_, err = store.GetNextChildID(ctx, deepID1)
	if err == nil {
		t.Errorf("expected error for depth 4, got nil")
	}
	if err != nil && err.Error() != "maximum hierarchy depth (3) exceeded for parent bd-a3f8e9.1.1.1" {
		t.Errorf("unexpected error message: %v", err)
	}
}

func TestGetNextChildID_ParentNotExists(t *testing.T) {
	tmpFile := t.TempDir() + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Test: Attempt to get child ID for non-existent parent
	_, err := store.GetNextChildID(ctx, "bd-nonexistent")
	if err == nil {
		t.Errorf("expected error for non-existent parent, got nil")
	}
	// With resurrection feature (bd-dvd fix), error message includes JSONL history check
	expectedErr := "parent issue bd-nonexistent does not exist and could not be resurrected from JSONL history"
	if err != nil && err.Error() != expectedErr {
		t.Errorf("unexpected error message: got %q, want %q", err.Error(), expectedErr)
	}
}

func TestCreateIssue_HierarchicalID(t *testing.T) {
	tmpFile := t.TempDir() + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create parent
	parent := &types.Issue{
		ID:          "bd-parent1",
		Title:       "Parent",
		Description: "Parent issue",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeEpic,
	}
	if err := store.CreateIssue(ctx, parent, "test"); err != nil {
		t.Fatalf("failed to create parent: %v", err)
	}

	// Test: Create child with explicit hierarchical ID
	child := &types.Issue{
		ID:          "bd-parent1.1",
		Title:       "Child",
		Description: "Child issue",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, child, "test"); err != nil {
		t.Fatalf("failed to create child: %v", err)
	}

	// Verify child was created
	retrieved, err := store.GetIssue(ctx, child.ID)
	if err != nil {
		t.Fatalf("failed to retrieve child: %v", err)
	}
	if retrieved.ID != child.ID {
		t.Errorf("expected ID %s, got %s", child.ID, retrieved.ID)
	}
}

// TestExplicitChildIDUpdatesCounter verifies that creating issues with explicit
// hierarchical IDs (e.g., bd-test.1, bd-test.2) updates the child counter so that
// GetNextChildID returns the correct next ID (GH#728 fix)
func TestExplicitChildIDUpdatesCounter(t *testing.T) {
	tmpFile := t.TempDir() + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create parent
	parent := &types.Issue{
		ID:          "bd-test",
		Title:       "Parent",
		Description: "Test parent",
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeEpic,
	}
	if err := store.CreateIssue(ctx, parent, "test"); err != nil {
		t.Fatalf("failed to create parent: %v", err)
	}

	// Create explicit child .1
	child1 := &types.Issue{
		ID:          "bd-test.1",
		Title:       "Existing child 1",
		Description: "Created with explicit ID",
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, child1, "test"); err != nil {
		t.Fatalf("failed to create child1: %v", err)
	}

	// Create explicit child .2
	child2 := &types.Issue{
		ID:          "bd-test.2",
		Title:       "Existing child 2",
		Description: "Created with explicit ID",
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, child2, "test"); err != nil {
		t.Fatalf("failed to create child2: %v", err)
	}

	// Now use GetNextChildID - should return .3 (not .1 which would collide)
	nextID, err := store.GetNextChildID(ctx, "bd-test")
	if err != nil {
		t.Fatalf("GetNextChildID failed: %v", err)
	}

	expected := "bd-test.3"
	if nextID != expected {
		t.Errorf("GetNextChildID returned %s, expected %s (GH#728 - counter should be updated when explicit child IDs are created)", nextID, expected)
	}

	// Verify we can create an issue with the returned ID without collision
	child3 := &types.Issue{
		ID:          nextID,
		Title:       "New child via --parent",
		Description: "Created with GetNextChildID",
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeTask,
	}
	if err := store.CreateIssue(ctx, child3, "test"); err != nil {
		t.Fatalf("failed to create child3 with ID %s: %v", nextID, err)
	}
}

func TestCreateIssue_HierarchicalID_ParentNotExists(t *testing.T) {
	tmpFile := t.TempDir() + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Test: Attempt to create child without parent
	child := &types.Issue{
		ID:          "bd-nonexistent.1",
		Title:       "Child",
		Description: "Child issue",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeTask,
	}
	err := store.CreateIssue(ctx, child, "test")
	if err == nil {
		t.Errorf("expected error for child without parent, got nil")
	}
	// With resurrection feature, error message includes JSONL history check
	expectedErr := "parent issue bd-nonexistent does not exist and could not be resurrected from JSONL history"
	if err != nil && err.Error() != expectedErr {
		t.Errorf("unexpected error message: got %q, want %q", err.Error(), expectedErr)
	}
}

func TestGetNextChildID_ResurrectParent(t *testing.T) {
	tmpDir := t.TempDir()
	tmpFile := tmpDir + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create parent issue
	parent := &types.Issue{
		ID:          "bd-test123",
		ContentHash: "abc123",
		Title:       "Parent Issue",
		Description: "Parent to be resurrected",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeEpic,
	}
	if err := store.CreateIssue(ctx, parent, "test"); err != nil {
		t.Fatalf("failed to create parent: %v", err)
	}

	// Delete the parent from database (simulating deletion)
	if err := store.DeleteIssue(ctx, parent.ID); err != nil {
		t.Fatalf("failed to delete parent: %v", err)
	}

	// Create JSONL file with the deleted parent (simulating JSONL history)
	// Note: This requires the JSONL to be in .beads/issues.jsonl relative to dbPath
	// The resurrection logic looks for issues.jsonl in the same directory as the database
	beadsDir := tmpDir
	jsonlPath := beadsDir + "/issues.jsonl"

	// Write parent to JSONL
	jsonlFile, err := os.Create(jsonlPath)
	if err != nil {
		t.Fatalf("failed to create JSONL file: %v", err)
	}
	parentJSON := `{"id":"bd-test123","content_hash":"abc123","title":"Parent Issue","description":"Parent to be resurrected","status":"open","priority":1,"type":"epic","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}`
	if _, err := jsonlFile.WriteString(parentJSON + "\n"); err != nil {
		jsonlFile.Close()
		t.Fatalf("failed to write to JSONL: %v", err)
	}
	jsonlFile.Close()

	// Now attempt to get next child ID - should resurrect parent
	childID, err := store.GetNextChildID(ctx, parent.ID)
	if err != nil {
		t.Fatalf("GetNextChildID should have resurrected parent, but got error: %v", err)
	}

	expectedID := "bd-test123.1"
	if childID != expectedID {
		t.Errorf("expected child ID %s, got %s", expectedID, childID)
	}

	// Verify parent was resurrected as tombstone
	resurrectedParent, err := store.GetIssue(ctx, parent.ID)
	if err != nil {
		t.Fatalf("failed to get resurrected parent: %v", err)
	}
	if resurrectedParent.Status != types.StatusClosed {
		t.Errorf("expected resurrected parent to be closed, got %s", resurrectedParent.Status)
	}
	if resurrectedParent.Title != "Parent Issue" {
		t.Errorf("expected resurrected parent title to be preserved, got %s", resurrectedParent.Title)
	}
}

// TestGetNextChildID_ResurrectParent_NotInJSONL tests resurrection when parent doesn't exist in JSONL (bd-ar2.7)
func TestGetNextChildID_ResurrectParent_NotInJSONL(t *testing.T) {
	tmpDir := t.TempDir()
	tmpFile := tmpDir + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create empty JSONL file (parent not in history)
	jsonlPath := tmpDir + "/issues.jsonl"
	if err := os.WriteFile(jsonlPath, []byte(""), 0600); err != nil {
		t.Fatalf("failed to create JSONL file: %v", err)
	}

	// Attempt to get child ID for non-existent parent not in JSONL
	_, err := store.GetNextChildID(ctx, "bd-notfound")
	if err == nil {
		t.Errorf("expected error for parent not in JSONL, got nil")
	}
	expectedErr := "parent issue bd-notfound does not exist and could not be resurrected from JSONL history"
	if err != nil && err.Error() != expectedErr {
		t.Errorf("unexpected error: got %q, want %q", err.Error(), expectedErr)
	}
}

// TestGetNextChildID_ResurrectParent_NoJSONL tests resurrection when JSONL file doesn't exist (bd-ar2.7)
func TestGetNextChildID_ResurrectParent_NoJSONL(t *testing.T) {
	tmpDir := t.TempDir()
	tmpFile := tmpDir + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// No JSONL file created
	// Attempt to get child ID for non-existent parent
	_, err := store.GetNextChildID(ctx, "bd-missing")
	if err == nil {
		t.Errorf("expected error for parent with no JSONL, got nil")
	}
	expectedErr := "parent issue bd-missing does not exist and could not be resurrected from JSONL history"
	if err != nil && err.Error() != expectedErr {
		t.Errorf("unexpected error: got %q, want %q", err.Error(), expectedErr)
	}
}

// TestGetNextChildID_ResurrectParent_MalformedJSONL tests resurrection with invalid JSON lines (bd-ar2.7)
func TestGetNextChildID_ResurrectParent_MalformedJSONL(t *testing.T) {
	tmpDir := t.TempDir()
	tmpFile := tmpDir + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create JSONL with malformed lines and one valid parent
	jsonlPath := tmpDir + "/issues.jsonl"
	jsonlContent := `{invalid json
{"id":"bd-test456","content_hash":"def456","title":"Valid Parent","description":"Should be found","status":"open","priority":1,"type":"epic","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
this is not json either
`
	if err := os.WriteFile(jsonlPath, []byte(jsonlContent), 0600); err != nil {
		t.Fatalf("failed to create JSONL file: %v", err)
	}

	// Should successfully resurrect despite malformed lines
	childID, err := store.GetNextChildID(ctx, "bd-test456")
	if err != nil {
		t.Fatalf("GetNextChildID should skip malformed lines and resurrect valid parent, got error: %v", err)
	}

	expectedID := "bd-test456.1"
	if childID != expectedID {
		t.Errorf("expected child ID %s, got %s", expectedID, childID)
	}
}

// TestGetNextChildID_ResurrectParentChain tests resurrection of deeply nested missing parents (bd-ar2.7)
func TestGetNextChildID_ResurrectParentChain(t *testing.T) {
	tmpDir := t.TempDir()
	tmpFile := tmpDir + "/test.db"
	defer os.Remove(tmpFile)
	store := newTestStore(t, tmpFile)
	defer store.Close()
	ctx := context.Background()

	// Create root parent only
	root := &types.Issue{
		ID:          "bd-root",
		ContentHash: "root123",
		Title:       "Root Issue",
		Description: "Root",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeEpic,
	}
	if err := store.CreateIssue(ctx, root, "test"); err != nil {
		t.Fatalf("failed to create root: %v", err)
	}

	// Create JSONL with intermediate parents that are deleted
	jsonlPath := tmpDir + "/issues.jsonl"
	jsonlContent := `{"id":"bd-root","content_hash":"root123","title":"Root Issue","description":"Root","status":"open","priority":1,"type":"epic","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
{"id":"bd-root.1","content_hash":"l1abc","title":"Level 1","description":"First level","status":"open","priority":1,"type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
{"id":"bd-root.1.2","content_hash":"l2abc","title":"Level 2","description":"Second level","status":"open","priority":1,"type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
`
	if err := os.WriteFile(jsonlPath, []byte(jsonlContent), 0600); err != nil {
		t.Fatalf("failed to create JSONL file: %v", err)
	}

	// Try to create child of bd-root.1.2 (which doesn't exist in DB, but its parent bd-root.1 also doesn't exist)
	// With TryResurrectParentChain (bd-ar2.4), this should work
	childID, err := store.GetNextChildID(ctx, "bd-root.1.2")
	if err != nil {
		t.Fatalf("GetNextChildID should resurrect entire parent chain, got error: %v", err)
	}

	expectedID := "bd-root.1.2.1"
	if childID != expectedID {
		t.Errorf("expected child ID %s, got %s", expectedID, childID)
	}

	// Verify both intermediate parents were resurrected
	parent1, err := store.GetIssue(ctx, "bd-root.1")
	if err != nil {
		t.Fatalf("bd-root.1 should have been resurrected: %v", err)
	}
	if parent1.Status != types.StatusClosed {
		t.Errorf("expected resurrected parent to be closed, got %s", parent1.Status)
	}

	parent2, err := store.GetIssue(ctx, "bd-root.1.2")
	if err != nil {
		t.Fatalf("bd-root.1.2 should have been resurrected: %v", err)
	}
	if parent2.Status != types.StatusClosed {
		t.Errorf("expected resurrected parent to be closed, got %s", parent2.Status)
	}
}
