package merge

import (
	"encoding/json"
	"os"
	"path/filepath"
	"testing"
	"time"
)

// TestMergeStatus tests the status merging logic with special rules
func TestMergeStatus(t *testing.T) {
	tests := []struct {
		name     string
		base     string
		left     string
		right    string
		expected string
	}{
		{
			name:     "no changes",
			base:     "open",
			left:     "open",
			right:    "open",
			expected: "open",
		},
		{
			name:     "left closed, right open - closed wins",
			base:     "open",
			left:     "closed",
			right:    "open",
			expected: "closed",
		},
		{
			name:     "left open, right closed - closed wins",
			base:     "open",
			left:     "open",
			right:    "closed",
			expected: "closed",
		},
		{
			name:     "both closed",
			base:     "open",
			left:     "closed",
			right:    "closed",
			expected: "closed",
		},
		{
			name:     "base closed, left open, right open - open (standard merge)",
			base:     "closed",
			left:     "open",
			right:    "open",
			expected: "open",
		},
		{
			name:     "base closed, left open, right closed - closed wins",
			base:     "closed",
			left:     "open",
			right:    "closed",
			expected: "closed",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := mergeStatus(tt.base, tt.left, tt.right)
			if result != tt.expected {
				t.Errorf("mergeStatus(%q, %q, %q) = %q, want %q",
					tt.base, tt.left, tt.right, result, tt.expected)
			}
		})
	}
}

// TestMergeField tests the basic field merging logic
func TestMergeField(t *testing.T) {
	tests := []struct {
		name     string
		base     string
		left     string
		right    string
		expected string
	}{
		{
			name:     "no changes",
			base:     "original",
			left:     "original",
			right:    "original",
			expected: "original",
		},
		{
			name:     "left changed",
			base:     "original",
			left:     "left-changed",
			right:    "original",
			expected: "left-changed",
		},
		{
			name:     "right changed",
			base:     "original",
			left:     "original",
			right:    "right-changed",
			expected: "right-changed",
		},
		{
			name:     "both changed to same value",
			base:     "original",
			left:     "both-changed",
			right:    "both-changed",
			expected: "both-changed",
		},
		{
			name:     "both changed to different values - prefers left",
			base:     "original",
			left:     "left-value",
			right:    "right-value",
			expected: "left-value",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := mergeField(tt.base, tt.left, tt.right)
			if result != tt.expected {
				t.Errorf("mergeField() = %q, want %q", result, tt.expected)
			}
		})
	}
}

// TestMergeDependencies tests 3-way dependency merge with removal semantics (bd-ndye)
func TestMergeDependencies(t *testing.T) {
	tests := []struct {
		name     string
		base     []Dependency
		left     []Dependency
		right    []Dependency
		expected []Dependency
	}{
		{
			name:     "empty all sides",
			base:     []Dependency{},
			left:     []Dependency{},
			right:    []Dependency{},
			expected: []Dependency{},
		},
		{
			name: "left adds dep (not in base)",
			base: []Dependency{},
			left: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			right: []Dependency{},
			expected: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
		},
		{
			name: "right adds dep (not in base)",
			base: []Dependency{},
			left: []Dependency{},
			right: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "related", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			expected: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "related", CreatedAt: "2024-01-01T00:00:00Z"},
			},
		},
		{
			name: "both add different deps (not in base)",
			base: []Dependency{},
			left: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			right: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "related", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			expected: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "related", CreatedAt: "2024-01-01T00:00:00Z"},
			},
		},
		{
			name: "both add same dep (not in base) - no duplicates",
			base: []Dependency{},
			left: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			right: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-02T00:00:00Z"},
			},
			expected: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"}, // Left preferred
			},
		},
		{
			name: "left removes dep from base - REMOVAL WINS",
			base: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			left:     []Dependency{}, // Left removed it
			right: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			expected: []Dependency{}, // Should be empty - removal wins
		},
		{
			name: "right removes dep from base - REMOVAL WINS",
			base: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			left: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			right:    []Dependency{}, // Right removed it
			expected: []Dependency{}, // Should be empty - removal wins
		},
		{
			name: "both keep dep from base",
			base: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			left: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			right: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-02T00:00:00Z"},
			},
			expected: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
		},
		{
			name: "complex: left removes one, right adds one",
			base: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
			},
			left: []Dependency{}, // Left removed bd-2
			right: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks", CreatedAt: "2024-01-01T00:00:00Z"},
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "related", CreatedAt: "2024-01-01T00:00:00Z"}, // Right added bd-3
			},
			expected: []Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "related", CreatedAt: "2024-01-01T00:00:00Z"}, // Only the new one
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := mergeDependencies(tt.base, tt.left, tt.right)
			if len(result) != len(tt.expected) {
				t.Errorf("mergeDependencies() returned %d deps, want %d", len(result), len(tt.expected))
				return
			}
			// Check each expected dep is present
			for _, exp := range tt.expected {
				found := false
				for _, res := range result {
					if res.IssueID == exp.IssueID &&
						res.DependsOnID == exp.DependsOnID &&
						res.Type == exp.Type {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("expected dependency %+v not found in result", exp)
				}
			}
		})
	}
}

// TestMaxTime tests timestamp merging (max wins)
func TestMaxTime(t *testing.T) {
	tests := []struct {
		name     string
		t1       string
		t2       string
		expected string
	}{
		{
			name:     "both empty",
			t1:       "",
			t2:       "",
			expected: "",
		},
		{
			name:     "t1 empty",
			t1:       "",
			t2:       "2024-01-02T00:00:00Z",
			expected: "2024-01-02T00:00:00Z",
		},
		{
			name:     "t2 empty",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "",
			expected: "2024-01-01T00:00:00Z",
		},
		{
			name:     "t1 newer",
			t1:       "2024-01-02T00:00:00Z",
			t2:       "2024-01-01T00:00:00Z",
			expected: "2024-01-02T00:00:00Z",
		},
		{
			name:     "t2 newer",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "2024-01-02T00:00:00Z",
			expected: "2024-01-02T00:00:00Z",
		},
		{
			name:     "identical timestamps",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "2024-01-01T00:00:00Z",
			expected: "2024-01-01T00:00:00Z",
		},
		{
			name:     "with fractional seconds (RFC3339Nano)",
			t1:       "2024-01-01T00:00:00.123456Z",
			t2:       "2024-01-01T00:00:00.123455Z",
			expected: "2024-01-01T00:00:00.123456Z",
		},
		{
			name:     "invalid timestamps - returns t2 as fallback",
			t1:       "invalid",
			t2:       "also-invalid",
			expected: "also-invalid",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := maxTime(tt.t1, tt.t2)
			if result != tt.expected {
				t.Errorf("maxTime() = %q, want %q", result, tt.expected)
			}
		})
	}
}

// TestIsTimeAfter tests timestamp comparison including error handling
func TestIsTimeAfter(t *testing.T) {
	tests := []struct {
		name     string
		t1       string
		t2       string
		expected bool
	}{
		{
			name:     "both empty - prefer left",
			t1:       "",
			t2:       "",
			expected: false,
		},
		{
			name:     "t1 empty - t2 wins",
			t1:       "",
			t2:       "2024-01-02T00:00:00Z",
			expected: false,
		},
		{
			name:     "t2 empty - t1 wins",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "",
			expected: true,
		},
		{
			name:     "t1 newer",
			t1:       "2024-01-02T00:00:00Z",
			t2:       "2024-01-01T00:00:00Z",
			expected: true,
		},
		{
			name:     "t2 newer",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "2024-01-02T00:00:00Z",
			expected: false,
		},
		{
			name:     "identical timestamps - left wins (bd-8nz)",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "2024-01-01T00:00:00Z",
			expected: true,
		},
		{
			name:     "t1 invalid, t2 valid - t2 wins",
			t1:       "not-a-timestamp",
			t2:       "2024-01-01T00:00:00Z",
			expected: false,
		},
		{
			name:     "t1 valid, t2 invalid - t1 wins",
			t1:       "2024-01-01T00:00:00Z",
			t2:       "not-a-timestamp",
			expected: true,
		},
		{
			name:     "both invalid - prefer left",
			t1:       "invalid1",
			t2:       "invalid2",
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isTimeAfter(tt.t1, tt.t2)
			if result != tt.expected {
				t.Errorf("isTimeAfter(%q, %q) = %v, want %v", tt.t1, tt.t2, result, tt.expected)
			}
		})
	}
}

// TestMerge3Way_SimpleUpdates tests simple field update scenarios
func TestMerge3Way_SimpleUpdates(t *testing.T) {
	base := []Issue{
		{
			ID:        "bd-abc123",
			Title:     "Original title",
			Status:    "open",
			Priority:  2,
			CreatedAt: "2024-01-01T00:00:00Z",
			UpdatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
			RawLine:   `{"id":"bd-abc123","title":"Original title","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
		},
	}

	t.Run("left updates title", func(t *testing.T) {
		left := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Updated title",
				Status:    "open",
				Priority:  2,
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-02T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Updated title","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := base

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Title != "Updated title" {
			t.Errorf("expected title 'Updated title', got %q", result[0].Title)
		}
	})

	t.Run("right updates status", func(t *testing.T) {
		left := base
		right := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Original title",
				Status:    "in_progress",
				Priority:  2,
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-02T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Original title","status":"in_progress","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != "in_progress" {
			t.Errorf("expected status 'in_progress', got %q", result[0].Status)
		}
	})

	t.Run("both update different fields", func(t *testing.T) {
		left := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Updated title",
				Status:    "open",
				Priority:  2,
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-02T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Updated title","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Original title",
				Status:    "in_progress",
				Priority:  2,
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-02T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Original title","status":"in_progress","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Title != "Updated title" {
			t.Errorf("expected title 'Updated title', got %q", result[0].Title)
		}
		if result[0].Status != "in_progress" {
			t.Errorf("expected status 'in_progress', got %q", result[0].Status)
		}
	})
}

// TestMergePriority tests priority merging including bd-d0t fix
func TestMergePriority(t *testing.T) {
	tests := []struct {
		name     string
		base     int
		left     int
		right    int
		expected int
	}{
		{
			name:     "no changes",
			base:     2,
			left:     2,
			right:    2,
			expected: 2,
		},
		{
			name:     "left changed",
			base:     2,
			left:     1,
			right:    2,
			expected: 1,
		},
		{
			name:     "right changed",
			base:     2,
			left:     2,
			right:    3,
			expected: 3,
		},
		{
			name:     "both changed to same value",
			base:     2,
			left:     1,
			right:    1,
			expected: 1,
		},
		{
			name:     "conflict - higher priority wins (lower number)",
			base:     2,
			left:     3,
			right:    1,
			expected: 1,
		},
		// bd-d0t fix: 0 is treated as "unset"
		{
			name:     "bd-d0t: left unset (0), right has explicit priority",
			base:     2,
			left:     0,
			right:    3,
			expected: 3, // explicit priority wins over unset
		},
		{
			name:     "bd-d0t: left has explicit priority, right unset (0)",
			base:     2,
			left:     3,
			right:    0,
			expected: 3, // explicit priority wins over unset
		},
		{
			name:     "bd-d0t: both unset (0)",
			base:     2,
			left:     0,
			right:    0,
			expected: 0,
		},
		{
			name:     "bd-d0t: base unset, left sets priority, right unchanged",
			base:     0,
			left:     1,
			right:    0,
			expected: 1, // left changed from 0 to 1
		},
		{
			name:     "bd-d0t: base unset, right sets priority, left unchanged",
			base:     0,
			left:     0,
			right:    2,
			expected: 2, // right changed from 0 to 2
		},
		// bd-1kf fix: negative priorities should be handled consistently
		{
			name:     "bd-1kf: negative priority should win over unset (0)",
			base:     2,
			left:     0,
			right:    -1,
			expected: -1, // negative priority is explicit, should win over unset
		},
		{
			name:     "bd-1kf: negative priority on left should win over unset (0) on right",
			base:     2,
			left:     -1,
			right:    0,
			expected: -1, // negative priority is explicit, should win over unset
		},
		{
			name:     "bd-1kf: conflict between negative priorities - lower wins",
			base:     2,
			left:     -2,
			right:    -1,
			expected: -2, // -2 is higher priority (more urgent) than -1
		},
		{
			name:     "bd-1kf: negative vs positive priority conflict",
			base:     2,
			left:     -1,
			right:    1,
			expected: -1, // -1 is higher priority (lower number) than 1
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := mergePriority(tt.base, tt.left, tt.right)
			if result != tt.expected {
				t.Errorf("mergePriority(%d, %d, %d) = %d, want %d",
					tt.base, tt.left, tt.right, result, tt.expected)
			}
		})
	}
}

// TestMerge3Way_AutoResolve tests auto-resolution of conflicts
func TestMerge3Way_AutoResolve(t *testing.T) {
	t.Run("conflicting title changes - latest updated_at wins", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Original",
				UpdatedAt: "2024-01-01T00:00:00Z",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Original","updated_at":"2024-01-01T00:00:00Z","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Left version",
				UpdatedAt: "2024-01-02T00:00:00Z", // Older
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Left version","updated_at":"2024-01-02T00:00:00Z","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Right version",
				UpdatedAt: "2024-01-03T00:00:00Z", // Newer - this should win
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Right version","updated_at":"2024-01-03T00:00:00Z","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts with auto-resolution, got %d", len(conflicts))
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 merged issue, got %d", len(result))
		}
		// Right has newer updated_at, so right's title wins
		if result[0].Title != "Right version" {
			t.Errorf("expected title 'Right version' (newer updated_at), got %q", result[0].Title)
		}
	})

	t.Run("conflicting priority changes - higher priority wins (lower number)", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Priority:  2,
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","priority":2,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{
			{
				ID:        "bd-abc123",
				Priority:  3, // Lower priority (higher number)
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","priority":3,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{
			{
				ID:        "bd-abc123",
				Priority:  1, // Higher priority (lower number) - this should win
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","priority":1,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts with auto-resolution, got %d", len(conflicts))
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 merged issue, got %d", len(result))
		}
		// Lower priority number wins
		if result[0].Priority != 1 {
			t.Errorf("expected priority 1 (higher priority), got %d", result[0].Priority)
		}
	})

	t.Run("conflicting notes - concatenated", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Notes:     "Original notes",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","notes":"Original notes","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{
			{
				ID:        "bd-abc123",
				Notes:     "Left notes",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","notes":"Left notes","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{
			{
				ID:        "bd-abc123",
				Notes:     "Right notes",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","notes":"Right notes","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts with auto-resolution, got %d", len(conflicts))
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 merged issue, got %d", len(result))
		}
		// Notes should be concatenated
		expectedNotes := "Left notes\n\n---\n\nRight notes"
		if result[0].Notes != expectedNotes {
			t.Errorf("expected notes %q, got %q", expectedNotes, result[0].Notes)
		}
	})

	t.Run("conflicting issue_type - local (left) wins", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				IssueType: "task",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","issue_type":"task","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{
			{
				ID:        "bd-abc123",
				IssueType: "bug", // Local change - should win
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","issue_type":"bug","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{
			{
				ID:        "bd-abc123",
				IssueType: "feature",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","issue_type":"feature","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts with auto-resolution, got %d", len(conflicts))
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 merged issue, got %d", len(result))
		}
		// Local (left) wins for issue_type
		if result[0].IssueType != "bug" {
			t.Errorf("expected issue_type 'bug' (local wins), got %q", result[0].IssueType)
		}
	})
}

// TestMerge3Way_Deletions tests deletion detection scenarios
func TestMerge3Way_Deletions(t *testing.T) {
	t.Run("deleted in left, unchanged in right", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Will be deleted",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Will be deleted","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{} // Deleted in left
		right := base     // Unchanged in right

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 0 {
			t.Errorf("expected deletion to be accepted, got %d issues", len(result))
		}
	})

	t.Run("deleted in right, unchanged in left", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Will be deleted",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Will be deleted","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := base     // Unchanged in left
		right := []Issue{} // Deleted in right

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 0 {
			t.Errorf("expected deletion to be accepted, got %d issues", len(result))
		}
	})

	t.Run("deleted in left, modified in right - deletion wins", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Original",
				Status:    "open",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Original","status":"open","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{} // Deleted in left
		right := []Issue{ // Modified in right
			{
				ID:        "bd-abc123",
				Title:     "Modified",
				Status:    "in_progress",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Modified","status":"in_progress","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts, got %d", len(conflicts))
		}
		if len(result) != 0 {
			t.Errorf("expected deletion to win (0 results), got %d", len(result))
		}
	})

	t.Run("deleted in right, modified in left - deletion wins", func(t *testing.T) {
		base := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Original",
				Status:    "open",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Original","status":"open","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		left := []Issue{ // Modified in left
			{
				ID:        "bd-abc123",
				Title:     "Modified",
				Status:    "in_progress",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Modified","status":"in_progress","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{} // Deleted in right

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts, got %d", len(conflicts))
		}
		if len(result) != 0 {
			t.Errorf("expected deletion to win (0 results), got %d", len(result))
		}
	})
}

// TestMerge3Way_Additions tests issue addition scenarios
func TestMerge3Way_Additions(t *testing.T) {
	t.Run("added only in left", func(t *testing.T) {
		base := []Issue{}
		left := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "New issue",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"New issue","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Title != "New issue" {
			t.Errorf("expected title 'New issue', got %q", result[0].Title)
		}
	})

	t.Run("added only in right", func(t *testing.T) {
		base := []Issue{}
		left := []Issue{}
		right := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "New issue",
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"New issue","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Title != "New issue" {
			t.Errorf("expected title 'New issue', got %q", result[0].Title)
		}
	})

	t.Run("added in both with identical content", func(t *testing.T) {
		base := []Issue{}
		issueData := Issue{
			ID:        "bd-abc123",
			Title:     "New issue",
			Status:    "open",
			Priority:  2,
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
			RawLine:   `{"id":"bd-abc123","title":"New issue","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
		}
		left := []Issue{issueData}
		right := []Issue{issueData}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
	})

	t.Run("added in both with different content - auto-resolved", func(t *testing.T) {
		base := []Issue{}
		left := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Left version",
				UpdatedAt: "2024-01-02T00:00:00Z", // Older
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Left version","updated_at":"2024-01-02T00:00:00Z","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		right := []Issue{
			{
				ID:        "bd-abc123",
				Title:     "Right version",
				UpdatedAt: "2024-01-03T00:00:00Z", // Newer - should win
				CreatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-abc123","title":"Right version","updated_at":"2024-01-03T00:00:00Z","created_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("expected no conflicts with auto-resolution, got %d", len(conflicts))
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 merged issue, got %d", len(result))
		}
		// Right has newer updated_at, so right's title wins
		if result[0].Title != "Right version" {
			t.Errorf("expected title 'Right version' (newer updated_at), got %q", result[0].Title)
		}
	})
}

// TestMerge3Way_ResurrectionPrevention tests bd-hv01 regression
func TestMerge3Way_ResurrectionPrevention(t *testing.T) {
	t.Run("bd-pq5k: no invalid state (status=open with closed_at)", func(t *testing.T) {
		// Simulate the broken merge case that was creating invalid data
		// Base: issue is closed
		base := []Issue{
			{
				ID:        "bd-test",
				Title:     "Test issue",
				Status:    "closed",
				ClosedAt:  "2024-01-02T00:00:00Z",
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-02T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-test","title":"Test issue","status":"closed","closed_at":"2024-01-02T00:00:00Z","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}`,
			},
		}
		// Left: still closed with closed_at
		left := base
		// Right: somehow got reopened but WITHOUT removing closed_at (the bug scenario)
		right := []Issue{
			{
				ID:        "bd-test",
				Title:     "Test issue",
				Status:    "open", // reopened
				ClosedAt:  "",     // correctly removed
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-03T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-test","title":"Test issue","status":"open","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-03T00:00:00Z","created_by":"user1"}`,
			},
		}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}

		// CRITICAL: Status should be closed (closed wins over open)
		if result[0].Status != "closed" {
			t.Errorf("expected status 'closed', got %q", result[0].Status)
		}

		// CRITICAL: If status is closed, closed_at MUST be set
		if result[0].Status == "closed" && result[0].ClosedAt == "" {
			t.Error("INVALID STATE: status='closed' but closed_at is empty")
		}

		// CRITICAL: If status is open, closed_at MUST be empty
		if result[0].Status == "open" && result[0].ClosedAt != "" {
			t.Errorf("INVALID STATE: status='open' but closed_at='%s'", result[0].ClosedAt)
		}
	})

	t.Run("bd-hv01 regression: closed issue not resurrected", func(t *testing.T) {
		// Base: issue is open
		base := []Issue{
			{
				ID:        "bd-hv01",
				Title:     "Test issue",
				Status:    "open",
				ClosedAt:  "",
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-01T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-hv01","title":"Test issue","status":"open","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z","created_by":"user1"}`,
			},
		}
		// Left: issue is closed (newer)
		left := []Issue{
			{
				ID:        "bd-hv01",
				Title:     "Test issue",
				Status:    "closed",
				ClosedAt:  "2024-01-02T00:00:00Z",
				CreatedAt: "2024-01-01T00:00:00Z",
				UpdatedAt: "2024-01-02T00:00:00Z",
				CreatedBy: "user1",
				RawLine:   `{"id":"bd-hv01","title":"Test issue","status":"closed","closed_at":"2024-01-02T00:00:00Z","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}`,
			},
		}
		// Right: issue is still open (stale)
		right := base

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		// Issue should remain closed (left's version)
		if result[0].Status != "closed" {
			t.Errorf("expected status 'closed', got %q - issue was resurrected!", result[0].Status)
		}
		if result[0].ClosedAt == "" {
			t.Error("expected closed_at to be set, got empty string")
		}
		// UpdatedAt should be the max (left's newer timestamp)
		if result[0].UpdatedAt != "2024-01-02T00:00:00Z" {
			t.Errorf("expected updated_at '2024-01-02T00:00:00Z', got %q", result[0].UpdatedAt)
		}
	})
}

// TestMerge3Way_Integration tests full merge scenarios with file I/O
func TestMerge3Way_Integration(t *testing.T) {
	t.Run("full merge workflow", func(t *testing.T) {
		tmpDir := t.TempDir()

		// Create test files
		baseFile := filepath.Join(tmpDir, "base.jsonl")
		leftFile := filepath.Join(tmpDir, "left.jsonl")
		rightFile := filepath.Join(tmpDir, "right.jsonl")
		outputFile := filepath.Join(tmpDir, "output.jsonl")

		// Base: two issues
		baseData := `{"id":"bd-1","title":"Issue 1","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}
{"id":"bd-2","title":"Issue 2","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}
`
		if err := os.WriteFile(baseFile, []byte(baseData), 0644); err != nil {
			t.Fatalf("failed to write base file: %v", err)
		}

		// Left: update bd-1 title, add bd-3
		leftData := `{"id":"bd-1","title":"Updated Issue 1","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}
{"id":"bd-2","title":"Issue 2","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}
{"id":"bd-3","title":"New Issue 3","status":"open","priority":1,"created_at":"2024-01-02T00:00:00Z","created_by":"user1"}
`
		if err := os.WriteFile(leftFile, []byte(leftData), 0644); err != nil {
			t.Fatalf("failed to write left file: %v", err)
		}

		// Right: update bd-2 status, add bd-4
		rightData := `{"id":"bd-1","title":"Issue 1","status":"open","priority":2,"created_at":"2024-01-01T00:00:00Z","created_by":"user1"}
{"id":"bd-2","title":"Issue 2","status":"in_progress","priority":2,"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-02T00:00:00Z","created_by":"user1"}
{"id":"bd-4","title":"New Issue 4","status":"open","priority":3,"created_at":"2024-01-02T00:00:00Z","created_by":"user1"}
`
		if err := os.WriteFile(rightFile, []byte(rightData), 0644); err != nil {
			t.Fatalf("failed to write right file: %v", err)
		}

		// Perform merge
		err := Merge3Way(outputFile, baseFile, leftFile, rightFile, false)
		if err != nil {
			t.Fatalf("merge failed: %v", err)
		}

		// Read result
		content, err := os.ReadFile(outputFile)
		if err != nil {
			t.Fatalf("failed to read output file: %v", err)
		}

		// Parse result
		var results []Issue
		for _, line := range splitLines(string(content)) {
			if line == "" {
				continue
			}
			var issue Issue
			if err := json.Unmarshal([]byte(line), &issue); err != nil {
				t.Fatalf("failed to parse output line: %v", err)
			}
			results = append(results, issue)
		}

		// Should have 4 issues: bd-1 (updated), bd-2 (updated), bd-3 (new), bd-4 (new)
		if len(results) != 4 {
			t.Fatalf("expected 4 issues, got %d", len(results))
		}

		// Verify bd-1 has updated title from left
		found1 := false
		for _, issue := range results {
			if issue.ID == "bd-1" {
				found1 = true
				if issue.Title != "Updated Issue 1" {
					t.Errorf("bd-1 title: expected 'Updated Issue 1', got %q", issue.Title)
				}
			}
		}
		if !found1 {
			t.Error("bd-1 not found in results")
		}

		// Verify bd-2 has updated status from right
		found2 := false
		for _, issue := range results {
			if issue.ID == "bd-2" {
				found2 = true
				if issue.Status != "in_progress" {
					t.Errorf("bd-2 status: expected 'in_progress', got %q", issue.Status)
				}
			}
		}
		if !found2 {
			t.Error("bd-2 not found in results")
		}
	})
}

// TestIsTombstone tests the tombstone detection helper
func TestIsTombstone(t *testing.T) {
	tests := []struct {
		name     string
		status   string
		expected bool
	}{
		{
			name:     "tombstone status",
			status:   "tombstone",
			expected: true,
		},
		{
			name:     "open status",
			status:   "open",
			expected: false,
		},
		{
			name:     "closed status",
			status:   "closed",
			expected: false,
		},
		{
			name:     "in_progress status",
			status:   "in_progress",
			expected: false,
		},
		{
			name:     "empty status",
			status:   "",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			issue := Issue{Status: tt.status}
			result := IsTombstone(issue)
			if result != tt.expected {
				t.Errorf("IsTombstone() = %v, want %v", result, tt.expected)
			}
		})
	}
}

// TestMergeTombstones tests merging two tombstones
func TestMergeTombstones(t *testing.T) {
	tests := []struct {
		name            string
		leftDeletedAt   string
		rightDeletedAt  string
		expectedSide    string // "left" or "right"
	}{
		{
			name:           "left deleted later",
			leftDeletedAt:  "2024-01-02T00:00:00Z",
			rightDeletedAt: "2024-01-01T00:00:00Z",
			expectedSide:   "left",
		},
		{
			name:           "right deleted later",
			leftDeletedAt:  "2024-01-01T00:00:00Z",
			rightDeletedAt: "2024-01-02T00:00:00Z",
			expectedSide:   "right",
		},
		{
			name:           "same timestamp - left wins (tie breaker)",
			leftDeletedAt:  "2024-01-01T00:00:00Z",
			rightDeletedAt: "2024-01-01T00:00:00Z",
			expectedSide:   "left",
		},
		{
			name:           "with fractional seconds",
			leftDeletedAt:  "2024-01-01T00:00:00.123456Z",
			rightDeletedAt: "2024-01-01T00:00:00.123455Z",
			expectedSide:   "left",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			left := Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: tt.leftDeletedAt,
				DeletedBy: "user-left",
			}
			right := Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: tt.rightDeletedAt,
				DeletedBy: "user-right",
			}
			result := mergeTombstones(left, right)
			if tt.expectedSide == "left" && result.DeletedBy != "user-left" {
				t.Errorf("expected left tombstone to win, got right")
			}
			if tt.expectedSide == "right" && result.DeletedBy != "user-right" {
				t.Errorf("expected right tombstone to win, got left")
			}
		})
	}
}

// TestMerge3Way_TombstoneVsLive tests tombstone vs live issue scenarios
func TestMerge3Way_TombstoneVsLive(t *testing.T) {
	// Base issue (live)
	baseIssue := Issue{
		ID:        "bd-abc123",
		Title:     "Original title",
		Status:    "open",
		Priority:  2,
		CreatedAt: "2024-01-01T00:00:00Z",
		UpdatedAt: "2024-01-01T00:00:00Z",
		CreatedBy: "user1",
	}

	// Recent tombstone (not expired)
	recentTombstone := Issue{
		ID:           "bd-abc123",
		Title:        "Original title",
		Status:       StatusTombstone,
		Priority:     2,
		CreatedAt:    "2024-01-01T00:00:00Z",
		UpdatedAt:    "2024-01-02T00:00:00Z",
		CreatedBy:    "user1",
		DeletedAt:    time.Now().Add(-24 * time.Hour).Format(time.RFC3339), // 1 day ago
		DeletedBy:    "user2",
		DeleteReason: "Duplicate issue",
		OriginalType: "task",
	}

	// Expired tombstone (older than TTL)
	expiredTombstone := Issue{
		ID:           "bd-abc123",
		Title:        "Original title",
		Status:       StatusTombstone,
		Priority:     2,
		CreatedAt:    "2024-01-01T00:00:00Z",
		UpdatedAt:    "2024-01-02T00:00:00Z",
		CreatedBy:    "user1",
		DeletedAt:    time.Now().Add(-60 * 24 * time.Hour).Format(time.RFC3339), // 60 days ago
		DeletedBy:    "user2",
		DeleteReason: "Duplicate issue",
		OriginalType: "task",
	}

	// Modified live issue
	modifiedLive := Issue{
		ID:        "bd-abc123",
		Title:     "Updated title",
		Status:    "in_progress",
		Priority:  1,
		CreatedAt: "2024-01-01T00:00:00Z",
		UpdatedAt: "2024-01-03T00:00:00Z",
		CreatedBy: "user1",
	}

	t.Run("recent tombstone in left wins over live in right", func(t *testing.T) {
		base := []Issue{baseIssue}
		left := []Issue{recentTombstone}
		right := []Issue{modifiedLive}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to win, got status %q", result[0].Status)
		}
	})

	t.Run("recent tombstone in right wins over live in left", func(t *testing.T) {
		base := []Issue{baseIssue}
		left := []Issue{modifiedLive}
		right := []Issue{recentTombstone}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to win, got status %q", result[0].Status)
		}
	})

	t.Run("expired tombstone in left loses to live in right (resurrection)", func(t *testing.T) {
		base := []Issue{baseIssue}
		left := []Issue{expiredTombstone}
		right := []Issue{modifiedLive}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != "in_progress" {
			t.Errorf("expected live issue to win over expired tombstone, got status %q", result[0].Status)
		}
		if result[0].Title != "Updated title" {
			t.Errorf("expected live issue's title, got %q", result[0].Title)
		}
	})

	t.Run("expired tombstone in right loses to live in left (resurrection)", func(t *testing.T) {
		base := []Issue{baseIssue}
		left := []Issue{modifiedLive}
		right := []Issue{expiredTombstone}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != "in_progress" {
			t.Errorf("expected live issue to win over expired tombstone, got status %q", result[0].Status)
		}
	})
}

// TestMerge3Way_TombstoneVsTombstone tests merging two tombstones
func TestMerge3Way_TombstoneVsTombstone(t *testing.T) {
	baseIssue := Issue{
		ID:        "bd-abc123",
		Title:     "Original title",
		Status:    "open",
		CreatedAt: "2024-01-01T00:00:00Z",
		CreatedBy: "user1",
	}

	t.Run("later tombstone wins", func(t *testing.T) {
		leftTombstone := Issue{
			ID:           "bd-abc123",
			Title:        "Original title",
			Status:       StatusTombstone,
			CreatedAt:    "2024-01-01T00:00:00Z",
			CreatedBy:    "user1",
			DeletedAt:    "2024-01-02T00:00:00Z",
			DeletedBy:    "user-left",
			DeleteReason: "Left reason",
		}
		rightTombstone := Issue{
			ID:           "bd-abc123",
			Title:        "Original title",
			Status:       StatusTombstone,
			CreatedAt:    "2024-01-01T00:00:00Z",
			CreatedBy:    "user1",
			DeletedAt:    "2024-01-03T00:00:00Z", // Later
			DeletedBy:    "user-right",
			DeleteReason: "Right reason",
		}

		base := []Issue{baseIssue}
		left := []Issue{leftTombstone}
		right := []Issue{rightTombstone}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].DeletedBy != "user-right" {
			t.Errorf("expected right tombstone to win (later deleted_at), got DeletedBy %q", result[0].DeletedBy)
		}
		if result[0].DeleteReason != "Right reason" {
			t.Errorf("expected right tombstone's reason, got %q", result[0].DeleteReason)
		}
	})
}

// TestMerge3Way_TombstoneNoBase tests tombstone scenarios without a base
func TestMerge3Way_TombstoneNoBase(t *testing.T) {
	t.Run("tombstone added only in left", func(t *testing.T) {
		tombstone := Issue{
			ID:        "bd-abc123",
			Title:     "New tombstone",
			Status:    StatusTombstone,
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
			DeletedAt: "2024-01-02T00:00:00Z",
			DeletedBy: "user1",
		}

		result, conflicts := merge3Way([]Issue{}, []Issue{tombstone}, []Issue{})
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone, got status %q", result[0].Status)
		}
	})

	t.Run("tombstone added only in right", func(t *testing.T) {
		tombstone := Issue{
			ID:        "bd-abc123",
			Title:     "New tombstone",
			Status:    StatusTombstone,
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
			DeletedAt: "2024-01-02T00:00:00Z",
			DeletedBy: "user1",
		}

		result, conflicts := merge3Way([]Issue{}, []Issue{}, []Issue{tombstone})
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone, got status %q", result[0].Status)
		}
	})

	t.Run("tombstone in left vs live in right (no base)", func(t *testing.T) {
		recentTombstone := Issue{
			ID:        "bd-abc123",
			Title:     "Issue",
			Status:    StatusTombstone,
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
			DeletedAt: time.Now().Add(-24 * time.Hour).Format(time.RFC3339),
			DeletedBy: "user1",
		}
		live := Issue{
			ID:        "bd-abc123",
			Title:     "Issue",
			Status:    "open",
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
		}

		result, conflicts := merge3Way([]Issue{}, []Issue{recentTombstone}, []Issue{live})
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		// Recent tombstone should win
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to win, got status %q", result[0].Status)
		}
	})
}

// TestMerge3WayWithTTL tests the TTL-configurable merge function
func TestMerge3WayWithTTL(t *testing.T) {
	baseIssue := Issue{
		ID:        "bd-abc123",
		Title:     "Original",
		Status:    "open",
		CreatedAt: "2024-01-01T00:00:00Z",
		CreatedBy: "user1",
	}

	// Tombstone deleted 10 days ago
	tombstone := Issue{
		ID:        "bd-abc123",
		Title:     "Original",
		Status:    StatusTombstone,
		CreatedAt: "2024-01-01T00:00:00Z",
		CreatedBy: "user1",
		DeletedAt: time.Now().Add(-10 * 24 * time.Hour).Format(time.RFC3339),
		DeletedBy: "user2",
	}

	liveIssue := Issue{
		ID:        "bd-abc123",
		Title:     "Updated",
		Status:    "open",
		CreatedAt: "2024-01-01T00:00:00Z",
		CreatedBy: "user1",
	}

	t.Run("with short TTL tombstone is expired", func(t *testing.T) {
		// 7 day TTL + 1 hour grace = tombstone (10 days old) is expired
		shortTTL := 7 * 24 * time.Hour
		base := []Issue{baseIssue}
		left := []Issue{tombstone}
		right := []Issue{liveIssue}

		result, _ := Merge3WayWithTTL(base, left, right, shortTTL)
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		// With short TTL, tombstone is expired, live issue wins
		if result[0].Status != "open" {
			t.Errorf("expected live issue to win with short TTL, got status %q", result[0].Status)
		}
	})

	t.Run("with long TTL tombstone is not expired", func(t *testing.T) {
		// 30 day TTL = tombstone (10 days old) is NOT expired
		longTTL := 30 * 24 * time.Hour
		base := []Issue{baseIssue}
		left := []Issue{tombstone}
		right := []Issue{liveIssue}

		result, _ := Merge3WayWithTTL(base, left, right, longTTL)
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		// With long TTL, tombstone is NOT expired, tombstone wins
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to win with long TTL, got status %q", result[0].Status)
		}
	})
}

// TestMergeStatus_Tombstone tests status merging with tombstone
func TestMergeStatus_Tombstone(t *testing.T) {
	tests := []struct {
		name     string
		base     string
		left     string
		right    string
		expected string
	}{
		{
			name:     "tombstone in left wins over open in right",
			base:     "open",
			left:     StatusTombstone,
			right:    "open",
			expected: StatusTombstone,
		},
		{
			name:     "tombstone in right wins over open in left",
			base:     "open",
			left:     "open",
			right:    StatusTombstone,
			expected: StatusTombstone,
		},
		{
			name:     "tombstone in left wins over closed in right",
			base:     "open",
			left:     StatusTombstone,
			right:    "closed",
			expected: StatusTombstone,
		},
		{
			name:     "both tombstone",
			base:     "open",
			left:     StatusTombstone,
			right:    StatusTombstone,
			expected: StatusTombstone,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := mergeStatus(tt.base, tt.left, tt.right)
			if result != tt.expected {
				t.Errorf("mergeStatus(%q, %q, %q) = %q, want %q",
					tt.base, tt.left, tt.right, result, tt.expected)
			}
		})
	}
}

// TestMerge3Way_TombstoneWithImplicitDeletion tests bd-ki14 fix:
// tombstones should be preserved even when the other side implicitly deleted
func TestMerge3Way_TombstoneWithImplicitDeletion(t *testing.T) {
	baseIssue := Issue{
		ID:        "bd-abc123",
		Title:     "Original",
		Status:    "open",
		CreatedAt: "2024-01-01T00:00:00Z",
		CreatedBy: "user1",
	}

	tombstone := Issue{
		ID:           "bd-abc123",
		Title:        "Original",
		Status:       StatusTombstone,
		CreatedAt:    "2024-01-01T00:00:00Z",
		CreatedBy:    "user1",
		DeletedAt:    time.Now().Add(-24 * time.Hour).Format(time.RFC3339),
		DeletedBy:    "user2",
		DeleteReason: "Duplicate",
	}

	t.Run("bd-ki14: tombstone in left preserved when right implicitly deleted", func(t *testing.T) {
		base := []Issue{baseIssue}
		left := []Issue{tombstone}
		right := []Issue{} // Implicitly deleted in right

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue (tombstone preserved), got %d", len(result))
		}
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to be preserved, got status %q", result[0].Status)
		}
		if result[0].DeletedBy != "user2" {
			t.Errorf("expected tombstone fields preserved, got DeletedBy %q", result[0].DeletedBy)
		}
	})

	t.Run("bd-ki14: tombstone in right preserved when left implicitly deleted", func(t *testing.T) {
		base := []Issue{baseIssue}
		left := []Issue{} // Implicitly deleted in left
		right := []Issue{tombstone}

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue (tombstone preserved), got %d", len(result))
		}
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to be preserved, got status %q", result[0].Status)
		}
	})

	t.Run("bd-ki14: live issue in left still deleted when right implicitly deleted", func(t *testing.T) {
		base := []Issue{baseIssue}
		modifiedLive := Issue{
			ID:        "bd-abc123",
			Title:     "Modified",
			Status:    "in_progress",
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
		}
		left := []Issue{modifiedLive}
		right := []Issue{} // Implicitly deleted in right

		result, conflicts := merge3Way(base, left, right)
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		// Live issue should be deleted (implicit deletion wins for non-tombstones)
		if len(result) != 0 {
			t.Errorf("expected implicit deletion to win for live issue, got %d results", len(result))
		}
	})
}

// TestMergeTombstones_EmptyDeletedAt tests bd-6x5 fix:
// handling empty DeletedAt timestamps in tombstone merging
func TestMergeTombstones_EmptyDeletedAt(t *testing.T) {
	tests := []struct {
		name           string
		leftDeletedAt  string
		rightDeletedAt string
		expectedSide   string // "left" or "right"
	}{
		{
			name:           "bd-6x5: both empty - left wins as tie-breaker",
			leftDeletedAt:  "",
			rightDeletedAt: "",
			expectedSide:   "left",
		},
		{
			name:           "bd-6x5: left empty, right valid - right wins",
			leftDeletedAt:  "",
			rightDeletedAt: "2024-01-01T00:00:00Z",
			expectedSide:   "right",
		},
		{
			name:           "bd-6x5: left valid, right empty - left wins",
			leftDeletedAt:  "2024-01-01T00:00:00Z",
			rightDeletedAt: "",
			expectedSide:   "left",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			left := Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: tt.leftDeletedAt,
				DeletedBy: "user-left",
			}
			right := Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: tt.rightDeletedAt,
				DeletedBy: "user-right",
			}
			result := mergeTombstones(left, right)
			if tt.expectedSide == "left" && result.DeletedBy != "user-left" {
				t.Errorf("expected left tombstone to win, got DeletedBy %q", result.DeletedBy)
			}
			if tt.expectedSide == "right" && result.DeletedBy != "user-right" {
				t.Errorf("expected right tombstone to win, got DeletedBy %q", result.DeletedBy)
			}
		})
	}
}

// TestMergeIssue_TombstoneFields tests bd-1sn fix:
// tombstone fields should be copied when status becomes tombstone via safety fallback
func TestMergeIssue_TombstoneFields(t *testing.T) {
	t.Run("bd-1sn: tombstone fields copied from left when tombstone via mergeStatus", func(t *testing.T) {
		base := Issue{
			ID:        "bd-test",
			Status:    "open",
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
		}
		left := Issue{
			ID:           "bd-test",
			Status:       StatusTombstone,
			CreatedAt:    "2024-01-01T00:00:00Z",
			CreatedBy:    "user1",
			DeletedAt:    "2024-01-02T00:00:00Z",
			DeletedBy:    "user2",
			DeleteReason: "Duplicate",
			OriginalType: "task",
		}
		right := Issue{
			ID:        "bd-test",
			Status:    "open",
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
		}

		result, _ := mergeIssue(base, left, right)
		if result.Status != StatusTombstone {
			t.Errorf("expected tombstone status, got %q", result.Status)
		}
		if result.DeletedAt != "2024-01-02T00:00:00Z" {
			t.Errorf("expected DeletedAt to be copied, got %q", result.DeletedAt)
		}
		if result.DeletedBy != "user2" {
			t.Errorf("expected DeletedBy to be copied, got %q", result.DeletedBy)
		}
		if result.DeleteReason != "Duplicate" {
			t.Errorf("expected DeleteReason to be copied, got %q", result.DeleteReason)
		}
		if result.OriginalType != "task" {
			t.Errorf("expected OriginalType to be copied, got %q", result.OriginalType)
		}
	})

	t.Run("bd-1sn: tombstone fields copied from right when it has later deleted_at", func(t *testing.T) {
		base := Issue{
			ID:        "bd-test",
			Status:    "open",
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1",
		}
		left := Issue{
			ID:           "bd-test",
			Status:       StatusTombstone,
			CreatedAt:    "2024-01-01T00:00:00Z",
			CreatedBy:    "user1",
			DeletedAt:    "2024-01-02T00:00:00Z",
			DeletedBy:    "user-left",
			DeleteReason: "Left reason",
		}
		right := Issue{
			ID:           "bd-test",
			Status:       StatusTombstone,
			CreatedAt:    "2024-01-01T00:00:00Z",
			CreatedBy:    "user1",
			DeletedAt:    "2024-01-03T00:00:00Z", // Later
			DeletedBy:    "user-right",
			DeleteReason: "Right reason",
		}

		result, _ := mergeIssue(base, left, right)
		if result.Status != StatusTombstone {
			t.Errorf("expected tombstone status, got %q", result.Status)
		}
		// Right has later deleted_at, so right's fields should be used
		if result.DeletedBy != "user-right" {
			t.Errorf("expected DeletedBy from right (later), got %q", result.DeletedBy)
		}
		if result.DeleteReason != "Right reason" {
			t.Errorf("expected DeleteReason from right, got %q", result.DeleteReason)
		}
	})
}

// TestIsExpiredTombstone tests edge cases for the IsExpiredTombstone function (bd-fmo)
func TestIsExpiredTombstone(t *testing.T) {
	now := time.Now()

	tests := []struct {
		name     string
		issue    Issue
		ttl      time.Duration
		expected bool
	}{
		{
			name: "non-tombstone returns false",
			issue: Issue{
				ID:        "bd-test",
				Status:    "open",
				DeletedAt: now.Add(-100 * 24 * time.Hour).Format(time.RFC3339),
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "closed status returns false",
			issue: Issue{
				ID:        "bd-test",
				Status:    "closed",
				DeletedAt: now.Add(-100 * 24 * time.Hour).Format(time.RFC3339),
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "tombstone with empty deleted_at returns false",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: "",
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "tombstone with invalid timestamp returns false (safety)",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: "not-a-valid-date",
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "tombstone with malformed RFC3339 returns false",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: "2024-13-45T99:99:99Z",
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "recent tombstone (within TTL) returns false",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-1 * time.Hour).Format(time.RFC3339),
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "old tombstone (beyond TTL) returns true",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-48 * time.Hour).Format(time.RFC3339),
			},
			ttl:      24 * time.Hour,
			expected: true,
		},
		{
			name: "tombstone just inside TTL boundary (with clock skew grace) returns false",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-24 * time.Hour).Format(time.RFC3339),
			},
			ttl:      24 * time.Hour,
			expected: false,
		},
		{
			name: "tombstone just past TTL boundary (with clock skew grace) returns true",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-26 * time.Hour).Format(time.RFC3339),
			},
			ttl:      24 * time.Hour,
			expected: true,
		},
		{
			name: "ttl=0 falls back to DefaultTombstoneTTL (30 days)",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-20 * 24 * time.Hour).Format(time.RFC3339),
			},
			ttl:      0,
			expected: false,
		},
		{
			name: "ttl=0 with old tombstone (beyond default TTL) returns true",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-60 * 24 * time.Hour).Format(time.RFC3339),
			},
			ttl:      0,
			expected: true,
		},
		{
			name: "RFC3339Nano format is supported",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-48 * time.Hour).Format(time.RFC3339Nano),
			},
			ttl:      24 * time.Hour,
			expected: true,
		},
		{
			name: "very short TTL (1 minute) works correctly",
			issue: Issue{
				ID:        "bd-test",
				Status:    StatusTombstone,
				DeletedAt: now.Add(-2 * time.Hour).Format(time.RFC3339),
			},
			ttl:      1 * time.Minute,
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsExpiredTombstone(tt.issue, tt.ttl)
			if result != tt.expected {
				t.Errorf("IsExpiredTombstone() = %v, want %v (deleted_at=%q, ttl=%v)",
					result, tt.expected, tt.issue.DeletedAt, tt.ttl)
			}
		})
	}
}

// TestMerge3Way_TombstoneBaseBothLiveResurrection tests the scenario where
// the base version is a tombstone but both left and right have live versions.
// This can happen if Clone A deletes an issue, Clones B and C sync (getting tombstone),
// then both B and C independently recreate an issue with same ID. (bd-bob)
func TestMerge3Way_TombstoneBaseBothLiveResurrection(t *testing.T) {
	// Base is a tombstone (issue was deleted)
	baseTombstone := Issue{
		ID:           "bd-abc123",
		Title:        "Original title",
		Status:       StatusTombstone,
		Priority:     2,
		CreatedAt:    "2024-01-01T00:00:00Z",
		UpdatedAt:    "2024-01-05T00:00:00Z",
		CreatedBy:    "user1",
		DeletedAt:    time.Now().Add(-10 * 24 * time.Hour).Format(time.RFC3339), // 10 days ago
		DeletedBy:    "user2",
		DeleteReason: "Obsolete",
		OriginalType: "task",
	}

	// Left resurrects the issue with new content
	leftLive := Issue{
		ID:        "bd-abc123",
		Title:     "Resurrected by left",
		Status:    "open",
		Priority:  2,
		IssueType: "task",
		CreatedAt: "2024-01-01T00:00:00Z",
		UpdatedAt: "2024-01-10T00:00:00Z", // Left is older
		CreatedBy: "user1",
	}

	// Right also resurrects with different content
	rightLive := Issue{
		ID:        "bd-abc123",
		Title:     "Resurrected by right",
		Status:    "in_progress",
		Priority:  1, // Higher priority (lower number)
		IssueType: "bug",
		CreatedAt: "2024-01-01T00:00:00Z",
		UpdatedAt: "2024-01-15T00:00:00Z", // Right is newer
		CreatedBy: "user1",
	}

	t.Run("both sides resurrect with different content - standard merge applies", func(t *testing.T) {
		base := []Issue{baseTombstone}
		left := []Issue{leftLive}
		right := []Issue{rightLive}

		result, conflicts := merge3Way(base, left, right)

		// Should not have conflicts - merge rules apply
		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}

		merged := result[0]

		// Issue should be live (not tombstone)
		if merged.Status == StatusTombstone {
			t.Error("expected live issue after both sides resurrected, got tombstone")
		}

		// Title: right wins because it has later UpdatedAt
		if merged.Title != "Resurrected by right" {
			t.Errorf("expected title from right (later UpdatedAt), got %q", merged.Title)
		}

		// Priority: higher priority wins (lower number = more urgent)
		if merged.Priority != 1 {
			t.Errorf("expected priority 1 (higher), got %d", merged.Priority)
		}

		// Status: standard 3-way merge applies. When both sides changed from base,
		// left wins (standard merge conflict resolution). Note: status does NOT use
		// UpdatedAt tiebreaker like title does - it uses mergeField which picks left.
		if merged.Status != "open" {
			t.Errorf("expected status 'open' from left (both changed from base), got %q", merged.Status)
		}

		// Tombstone fields should NOT be present on merged result
		if merged.DeletedAt != "" {
			t.Errorf("expected empty DeletedAt on resurrected issue, got %q", merged.DeletedAt)
		}
		if merged.DeletedBy != "" {
			t.Errorf("expected empty DeletedBy on resurrected issue, got %q", merged.DeletedBy)
		}
	})

	t.Run("both resurrect with same status - no conflict", func(t *testing.T) {
		leftOpen := leftLive
		leftOpen.Status = "open"
		rightOpen := rightLive
		rightOpen.Status = "open"

		base := []Issue{baseTombstone}
		left := []Issue{leftOpen}
		right := []Issue{rightOpen}

		result, conflicts := merge3Way(base, left, right)

		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		if result[0].Status != "open" {
			t.Errorf("expected status 'open', got %q", result[0].Status)
		}
	})

	t.Run("one side closes after resurrection", func(t *testing.T) {
		// Left resurrects and keeps open
		leftOpen := leftLive
		leftOpen.Status = "open"

		// Right resurrects and then closes
		rightClosed := rightLive
		rightClosed.Status = "closed"
		rightClosed.ClosedAt = "2024-01-16T00:00:00Z"

		base := []Issue{baseTombstone}
		left := []Issue{leftOpen}
		right := []Issue{rightClosed}

		result, conflicts := merge3Way(base, left, right)

		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}
		if len(result) != 1 {
			t.Fatalf("expected 1 issue, got %d", len(result))
		}
		// Closed should win over open
		if result[0].Status != "closed" {
			t.Errorf("expected closed to win over open, got %q", result[0].Status)
		}
	})
}

// TestMerge3Way_TombstoneVsLiveTimestampPrecisionMismatch tests bd-ncwo:
// When the same issue has different CreatedAt timestamp precision (e.g., with/without nanoseconds),
// the tombstone should still win over the live version.
func TestMerge3Way_TombstoneVsLiveTimestampPrecisionMismatch(t *testing.T) {
	// This test simulates the ghost resurrection bug where timestamp precision
	// differences caused the same issue to be treated as two different issues.
	// The key fix (bd-ncwo) adds ID-based fallback matching when keys don't match.

	t.Run("tombstone wins despite different CreatedAt precision", func(t *testing.T) {
		// Base: issue with status=closed
		baseIssue := Issue{
			ID:        "bd-ghost1",
			Title:     "Original title",
			Status:    "closed",
			Priority:  2,
			CreatedAt: "2024-01-01T00:00:00Z", // No fractional seconds
			UpdatedAt: "2024-01-10T00:00:00Z",
			CreatedBy: "user1",
		}

		// Left: tombstone with DIFFERENT timestamp precision (has microseconds)
		tombstone := Issue{
			ID:           "bd-ghost1",
			Title:        "(deleted)",
			Status:       StatusTombstone,
			Priority:     2,
			CreatedAt:    "2024-01-01T00:00:00.000000Z", // WITH fractional seconds
			UpdatedAt:    "2024-01-15T00:00:00Z",
			CreatedBy:    "user1",
			DeletedAt:    time.Now().Add(-24 * time.Hour).Format(time.RFC3339),
			DeletedBy:    "user2",
			DeleteReason: "Duplicate issue",
		}

		// Right: same closed issue (same precision as base)
		closedIssue := Issue{
			ID:        "bd-ghost1",
			Title:     "Original title",
			Status:    "closed",
			Priority:  2,
			CreatedAt: "2024-01-01T00:00:00Z", // No fractional seconds
			UpdatedAt: "2024-01-12T00:00:00Z",
			CreatedBy: "user1",
		}

		base := []Issue{baseIssue}
		left := []Issue{tombstone}
		right := []Issue{closedIssue}

		result, conflicts := merge3Way(base, left, right)

		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}

		// CRITICAL: Should have exactly 1 issue, not 2 (no duplicates)
		if len(result) != 1 {
			t.Fatalf("expected 1 issue (no duplicates), got %d - this suggests ID-based matching failed", len(result))
		}

		// Tombstone should win over closed
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to win, got status %q", result[0].Status)
		}
		if result[0].DeletedBy != "user2" {
			t.Errorf("expected tombstone fields preserved, got DeletedBy %q", result[0].DeletedBy)
		}
	})

	t.Run("tombstone wins with CreatedBy mismatch", func(t *testing.T) {
		// Test case where CreatedBy differs (e.g., empty vs populated)
		tombstone := Issue{
			ID:           "bd-ghost2",
			Title:        "(deleted)",
			Status:       StatusTombstone,
			Priority:     2,
			CreatedAt:    "2024-01-01T00:00:00Z",
			CreatedBy:    "", // Empty CreatedBy
			DeletedAt:    time.Now().Add(-24 * time.Hour).Format(time.RFC3339),
			DeletedBy:    "user2",
			DeleteReason: "Cleanup",
		}

		closedIssue := Issue{
			ID:        "bd-ghost2",
			Title:     "Original title",
			Status:    "closed",
			Priority:  2,
			CreatedAt: "2024-01-01T00:00:00Z",
			CreatedBy: "user1", // Non-empty CreatedBy
		}

		base := []Issue{}
		left := []Issue{tombstone}
		right := []Issue{closedIssue}

		result, conflicts := merge3Way(base, left, right)

		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}

		// Should have exactly 1 issue
		if len(result) != 1 {
			t.Fatalf("expected 1 issue (no duplicates), got %d", len(result))
		}

		// Tombstone should win
		if result[0].Status != StatusTombstone {
			t.Errorf("expected tombstone to win despite CreatedBy mismatch, got status %q", result[0].Status)
		}
	})

	t.Run("no duplicates when both have same ID but different keys", func(t *testing.T) {
		// Ensure we don't create duplicate entries
		liveLeft := Issue{
			ID:        "bd-ghost3",
			Title:     "Left version",
			Status:    "open",
			CreatedAt: "2024-01-01T00:00:00.123456Z", // With nanoseconds
			CreatedBy: "user1",
		}

		liveRight := Issue{
			ID:        "bd-ghost3",
			Title:     "Right version",
			Status:    "in_progress",
			CreatedAt: "2024-01-01T00:00:00Z", // Without nanoseconds
			CreatedBy: "user1",
		}

		base := []Issue{}
		left := []Issue{liveLeft}
		right := []Issue{liveRight}

		result, conflicts := merge3Way(base, left, right)

		if len(conflicts) != 0 {
			t.Errorf("unexpected conflicts: %v", conflicts)
		}

		// CRITICAL: Should have exactly 1 issue, not 2
		if len(result) != 1 {
			t.Fatalf("expected 1 issue (no duplicates for same ID), got %d", len(result))
		}
	})
}
