package sqlite

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

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

// TestTryResurrectParent_AlreadyExists verifies that resurrection is a no-op when parent exists
func TestTryResurrectParent_AlreadyExists(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create parent issue
	parent := &types.Issue{
		ID:        "bd-abc",
		Title:     "Parent Issue",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeEpic,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	if err := s.CreateIssue(ctx, parent, "test"); err != nil {
		t.Fatalf("Failed to create parent: %v", err)
	}

	// Try to resurrect - should succeed without doing anything
	resurrected, err := s.TryResurrectParent(ctx, "bd-abc")
	if err != nil {
		t.Fatalf("TryResurrectParent failed: %v", err)
	}
	if !resurrected {
		t.Fatal("Expected resurrected=true for existing parent")
	}

	// Verify parent is still the original (not a tombstone)
	retrieved, err := s.GetIssue(ctx, "bd-abc")
	if err != nil {
		t.Fatalf("Failed to retrieve parent: %v", err)
	}
	if retrieved.Status != types.StatusOpen {
		t.Errorf("Expected status=%s, got %s", types.StatusOpen, retrieved.Status)
	}
	if retrieved.Priority != 1 {
		t.Errorf("Expected priority=1, got %d", retrieved.Priority)
	}
}

// TestTryResurrectParent_FoundInJSONL verifies successful resurrection from JSONL
func TestTryResurrectParent_FoundInJSONL(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create a JSONL file with the parent issue
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	parentIssue := types.Issue{
		ID:          "test-parent",
		ContentHash: "hash123",
		Title:       "Original Parent",
		Description: "Original description text",
		Status:      types.StatusOpen,
		Priority:    1,
		IssueType:   types.TypeEpic,
		CreatedAt:   time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
		UpdatedAt:   time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
	}

	// Write parent to JSONL
	if err := writeIssuesToJSONL(jsonlPath, []types.Issue{parentIssue}); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Try to resurrect
	resurrected, err := s.TryResurrectParent(ctx, "test-parent")
	if err != nil {
		t.Fatalf("TryResurrectParent failed: %v", err)
	}
	if !resurrected {
		t.Fatal("Expected successful resurrection")
	}

	// Verify tombstone was created
	tombstone, err := s.GetIssue(ctx, "test-parent")
	if err != nil {
		t.Fatalf("Failed to retrieve resurrected parent: %v", err)
	}

	// Check tombstone properties
	if tombstone.Status != types.StatusClosed {
		t.Errorf("Expected status=closed, got %s", tombstone.Status)
	}
	if tombstone.Priority != 4 {
		t.Errorf("Expected priority=4, got %d", tombstone.Priority)
	}
	if tombstone.ClosedAt == nil {
		t.Error("Expected ClosedAt to be set")
	}
	if tombstone.Title != "Original Parent" {
		t.Errorf("Expected title preserved, got %s", tombstone.Title)
	}
	if !contains(tombstone.Description, "[RESURRECTED]") {
		t.Error("Expected [RESURRECTED] marker in description")
	}
	if !contains(tombstone.Description, "Original description text") {
		t.Error("Expected original description appended to tombstone")
	}
	if tombstone.IssueType != types.TypeEpic {
		t.Errorf("Expected type=%s, got %s", types.TypeEpic, tombstone.IssueType)
	}
	if !tombstone.CreatedAt.Equal(parentIssue.CreatedAt) {
		t.Error("Expected CreatedAt to be preserved from original")
	}
}

// TestTryResurrectParent_NotFoundInJSONL verifies proper handling when parent not in JSONL
func TestTryResurrectParent_NotFoundInJSONL(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create a JSONL file with different issue
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	otherIssue := types.Issue{
		ID:        "test-other",
		Title:     "Other Issue",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := writeIssuesToJSONL(jsonlPath, []types.Issue{otherIssue}); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Try to resurrect non-existent parent
	resurrected, err := s.TryResurrectParent(ctx, "test-missing")
	if err != nil {
		t.Fatalf("TryResurrectParent should not error on missing parent: %v", err)
	}
	if resurrected {
		t.Error("Expected resurrected=false for missing parent")
	}

	// Verify parent was not created
	issue, err := s.GetIssue(ctx, "test-missing")
	if err == nil && issue != nil {
		t.Error("Expected nil issue when retrieving non-existent parent")
	}
}

// TestTryResurrectParent_NoJSONLFile verifies graceful handling when JSONL file missing
func TestTryResurrectParent_NoJSONLFile(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Don't create JSONL file

	// Try to resurrect - should return false (not found) without error
	resurrected, err := s.TryResurrectParent(ctx, "test-parent")
	if err != nil {
		t.Fatalf("TryResurrectParent should not error when JSONL missing: %v", err)
	}
	if resurrected {
		t.Error("Expected resurrected=false when JSONL missing")
	}
}

// TestTryResurrectParent_MalformedJSONL verifies handling of malformed JSONL lines
func TestTryResurrectParent_MalformedJSONL(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create JSONL file with malformed lines and one valid entry
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	validIssue := types.Issue{
		ID:        "test-valid",
		Title:     "Valid Issue",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	validJSON, _ := json.Marshal(validIssue)

	content := "this is not valid json\n" +
		"{\"id\": \"incomplete\"\n" +
		string(validJSON) + "\n" +
		"\n" // empty line

	if err := os.WriteFile(jsonlPath, []byte(content), 0644); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Try to resurrect valid issue - should succeed despite malformed lines
	resurrected, err := s.TryResurrectParent(ctx, "test-valid")
	if err != nil {
		t.Fatalf("TryResurrectParent failed: %v", err)
	}
	if !resurrected {
		t.Error("Expected successful resurrection of valid issue")
	}

	// Try to resurrect from malformed line - should return false
	resurrected, err = s.TryResurrectParent(ctx, "incomplete")
	if err != nil {
		t.Fatalf("TryResurrectParent should not error on malformed JSON: %v", err)
	}
	if resurrected {
		t.Error("Expected resurrected=false for malformed JSON")
	}
}

// TestTryResurrectParent_WithDependencies verifies dependency resurrection
func TestTryResurrectParent_WithDependencies(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create dependency target in database
	target := &types.Issue{
		ID:        "bd-target",
		Title:     "Target Issue",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	if err := s.CreateIssue(ctx, target, "test"); err != nil {
		t.Fatalf("Failed to create target: %v", err)
	}

	// Create JSONL with parent that has dependencies
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	parentIssue := types.Issue{
		ID:        "test-parent",
		Title:     "Parent with Deps",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeEpic,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
		Dependencies: []*types.Dependency{
			{IssueID: "test-parent", DependsOnID: "bd-target", Type: types.DepBlocks},
			{IssueID: "test-parent", DependsOnID: "test-missing", Type: types.DepBlocks},
		},
	}

	if err := writeIssuesToJSONL(jsonlPath, []types.Issue{parentIssue}); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Resurrect parent
	resurrected, err := s.TryResurrectParent(ctx, "test-parent")
	if err != nil {
		t.Fatalf("TryResurrectParent failed: %v", err)
	}
	if !resurrected {
		t.Fatal("Expected successful resurrection")
	}

	// Verify dependency to existing target was resurrected
	_, err = s.GetIssue(ctx, "test-parent")
	if err != nil {
		t.Fatalf("Failed to retrieve tombstone: %v", err)
	}

	// Get dependencies separately (GetIssue doesn't load them)
	depIssues, err := s.GetDependencies(ctx, "test-parent")
	if err != nil {
		t.Fatalf("Failed to get dependencies: %v", err)
	}
	if len(depIssues) != 1 {
		t.Fatalf("Expected 1 dependency (only the valid one), got %d", len(depIssues))
	}
	if depIssues[0].ID != "bd-target" {
		t.Errorf("Expected dependency to bd-target, got %s", depIssues[0].ID)
	}
}

// TestTryResurrectParentChain_MultiLevel verifies recursive chain resurrection
func TestTryResurrectParentChain_MultiLevel(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create JSONL with multi-level hierarchy
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	root := types.Issue{
		ID:        "test-root",
		Title:     "Root Epic",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeEpic,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	level1 := types.Issue{
		ID:        "test-root.1",
		Title:     "Level 1 Task",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	level2 := types.Issue{
		ID:        "test-root.1.1",
		Title:     "Level 2 Subtask",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := writeIssuesToJSONL(jsonlPath, []types.Issue{root, level1, level2}); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Resurrect entire chain for deepest child
	resurrected, err := s.TryResurrectParentChain(ctx, "test-root.1.1")
	if err != nil {
		t.Fatalf("TryResurrectParentChain failed: %v", err)
	}
	if !resurrected {
		t.Fatal("Expected successful chain resurrection")
	}

	// Verify all parents were created
	for _, id := range []string{"test-root", "test-root.1"} {
		issue, err := s.GetIssue(ctx, id)
		if err != nil {
			t.Errorf("Failed to retrieve %s: %v", id, err)
			continue
		}
		if issue.Status != types.StatusClosed {
			t.Errorf("Expected %s to be closed tombstone, got %s", id, issue.Status)
		}
		if !contains(issue.Description, "[RESURRECTED]") {
			t.Errorf("Expected %s to have [RESURRECTED] marker", id)
		}
	}
}

// TestTryResurrectParentChain_PartialChainMissing verifies behavior when some parents missing
func TestTryResurrectParentChain_PartialChainMissing(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create JSONL with only root, missing intermediate level
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	root := types.Issue{
		ID:        "test-root",
		Title:     "Root Epic",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeEpic,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// Note: test-root.1 is NOT in JSONL

	if err := writeIssuesToJSONL(jsonlPath, []types.Issue{root}); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Try to resurrect chain - should fail when intermediate parent not found
	resurrected, err := s.TryResurrectParentChain(ctx, "test-root.1.1")
	if err != nil {
		t.Fatalf("TryResurrectParentChain should not error: %v", err)
	}
	if resurrected {
		t.Error("Expected resurrected=false when intermediate parent missing")
	}

	// Verify root was created (first in chain)
	rootIssue, err := s.GetIssue(ctx, "test-root")
	if err != nil {
		t.Error("Expected root to be resurrected before failure")
	} else if rootIssue.Status != types.StatusClosed {
		t.Error("Expected root to be tombstone")
	}

	// Verify intermediate level was NOT created
	midIssue, err := s.GetIssue(ctx, "test-root.1")
	if err == nil && midIssue != nil {
		t.Error("Expected nil issue when retrieving missing intermediate parent")
	}
}

// TestExtractParentChain verifies parent chain extraction logic
func TestExtractParentChain(t *testing.T) {
	tests := []struct {
		name     string
		id       string
		expected []string
	}{
		{
			name:     "top-level ID",
			id:       "test-abc",
			expected: nil,
		},
		{
			name:     "one level deep",
			id:       "test-abc.1",
			expected: []string{"test-abc"},
		},
		{
			name:     "two levels deep",
			id:       "test-abc.1.2",
			expected: []string{"test-abc", "test-abc.1"},
		},
		{
			name:     "three levels deep",
			id:       "test-abc.1.2.3",
			expected: []string{"test-abc", "test-abc.1", "test-abc.1.2"},
		},
		// GH#664: Prefixes with dots should be handled correctly
		{
			name:     "prefix with dot - top-level",
			id:       "test.example-abc",
			expected: nil, // No numeric suffix, not hierarchical
		},
		{
			name:     "prefix with dot - one level deep",
			id:       "test.example-abc.1",
			expected: []string{"test.example-abc"},
		},
		{
			name:     "prefix with dot - two levels deep",
			id:       "test.example-abc.1.2",
			expected: []string{"test.example-abc", "test.example-abc.1"},
		},
		{
			name:     "prefix with multiple dots - one level deep",
			id:       "my.company.project-xyz.1",
			expected: []string{"my.company.project-xyz"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := extractParentChain(tt.id)
			if len(result) != len(tt.expected) {
				t.Fatalf("Expected %d parents, got %d", len(tt.expected), len(result))
			}
			for i := range result {
				if result[i] != tt.expected[i] {
					t.Errorf("Parent[%d]: expected %s, got %s", i, tt.expected[i], result[i])
				}
			}
		})
	}
}

// TestTryResurrectParent_Idempotent verifies resurrection can be called multiple times safely
func TestTryResurrectParent_Idempotent(t *testing.T) {
	s := newTestStore(t, "")

	ctx := context.Background()

	// Create JSONL with parent
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	parentIssue := types.Issue{
		ID:        "test-parent",
		Title:     "Parent Issue",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeEpic,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := writeIssuesToJSONL(jsonlPath, []types.Issue{parentIssue}); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// First resurrection
	resurrected, err := s.TryResurrectParent(ctx, "test-parent")
	if err != nil {
		t.Fatalf("First resurrection failed: %v", err)
	}
	if !resurrected {
		t.Fatal("Expected first resurrection to succeed")
	}

	firstTombstone, err := s.GetIssue(ctx, "test-parent")
	if err != nil {
		t.Fatalf("Failed to retrieve first tombstone: %v", err)
	}

	// Second resurrection (should be no-op)
	resurrected, err = s.TryResurrectParent(ctx, "test-parent")
	if err != nil {
		t.Fatalf("Second resurrection failed: %v", err)
	}
	if !resurrected {
		t.Fatal("Expected second resurrection to succeed (already exists)")
	}

	// Verify tombstone unchanged
	secondTombstone, err := s.GetIssue(ctx, "test-parent")
	if err != nil {
		t.Fatalf("Failed to retrieve second tombstone: %v", err)
	}

	if firstTombstone.UpdatedAt != secondTombstone.UpdatedAt {
		t.Error("Expected tombstone to be unchanged by second resurrection")
	}
}

// Helper function to write issues to JSONL file
func writeIssuesToJSONL(path string, issues []types.Issue) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	for _, issue := range issues {
		if err := encoder.Encode(issue); err != nil {
			return err
		}
	}

	return nil
}

// TestTryResurrectParent_MultipleVersionsInJSONL verifies that the LAST occurrence is used
func TestTryResurrectParent_MultipleVersionsInJSONL(t *testing.T) {
	s := newTestStore(t, "")
	ctx := context.Background()

	// Create JSONL with multiple versions of the same issue (append-only semantics)
	dbDir := filepath.Dir(s.dbPath)
	jsonlPath := filepath.Join(dbDir, "issues.jsonl")

	// First version: priority 3, title "Old Version"
	v1 := &types.Issue{
		ID:        "bd-multi",
		Title:     "Old Version",
		Status:    types.StatusOpen,
		Priority:  3,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	v1JSON, _ := json.Marshal(v1)

	// Second version: priority 2, title "Updated Version"
	time.Sleep(10 * time.Millisecond) // Ensure different timestamp
	v2 := &types.Issue{
		ID:        "bd-multi",
		Title:     "Updated Version",
		Status:    types.StatusInProgress,
		Priority:  2,
		IssueType: types.TypeTask,
		CreatedAt: v1.CreatedAt, // Same creation time
		UpdatedAt: time.Now(),
	}
	v2JSON, _ := json.Marshal(v2)

	// Third version: priority 1, title "Latest Version"
	time.Sleep(10 * time.Millisecond)
	v3 := &types.Issue{
		ID:        "bd-multi",
		Title:     "Latest Version",
		Status:    types.StatusClosed,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: v1.CreatedAt,
		UpdatedAt: time.Now(),
	}
	v3JSON, _ := json.Marshal(v3)

	// Write all three versions (append-only)
	content := string(v1JSON) + "\n" + string(v2JSON) + "\n" + string(v3JSON) + "\n"
	if err := os.WriteFile(jsonlPath, []byte(content), 0644); err != nil {
		t.Fatalf("Failed to create JSONL: %v", err)
	}

	// Resurrect - should get the LAST version (v3)
	resurrected, err := s.TryResurrectParent(ctx, "bd-multi")
	if err != nil {
		t.Fatalf("TryResurrectParent failed: %v", err)
	}
	if !resurrected {
		t.Error("Expected successful resurrection")
	}

	// Verify we got the latest version's data
	retrieved, err := s.GetIssue(ctx, "bd-multi")
	if err != nil {
		t.Fatalf("Failed to retrieve resurrected issue: %v", err)
	}

	// Most important: title should be from LAST occurrence (v3)
	if retrieved.Title != "Latest Version" {
		t.Errorf("Expected title 'Latest Version', got '%s' (should use LAST occurrence in JSONL)", retrieved.Title)
	}
	
	// CreatedAt should be preserved from original (all versions share this)
	if !retrieved.CreatedAt.Equal(v1.CreatedAt) {
		t.Errorf("Expected CreatedAt %v, got %v", v1.CreatedAt, retrieved.CreatedAt)
	}
	
	// Note: Priority, Status, and Description are modified by tombstone logic
	// (Priority=4, Status=Closed, Description="[RESURRECTED]...")
	// This is expected behavior - the test verifies we read the LAST occurrence
	// before creating the tombstone.
}

// Helper function to check if string contains substring
func contains(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 && (s == substr || len(s) >= len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr)))
}

func containsMiddle(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
