package doctor

import (
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	_ "github.com/ncruces/go-sqlite3/driver"
	_ "github.com/ncruces/go-sqlite3/embed"
)

// setupTestDatabase creates a minimal valid SQLite database for testing
func setupTestDatabase(t *testing.T, dir string) string {
	t.Helper()
	dbPath := filepath.Join(dir, ".beads", "beads.db")

	db, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		t.Fatalf("failed to create database: %v", err)
	}
	defer db.Close()

	// Create minimal issues table
	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS issues (
		id TEXT PRIMARY KEY,
		title TEXT,
		status TEXT
	)`)
	if err != nil {
		t.Fatalf("failed to create table: %v", err)
	}

	return dbPath
}

func TestCheckDatabaseIntegrity(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		expectedStatus string
		expectMessage  string
	}{
		{
			name: "no database",
			setup: func(t *testing.T, dir string) {
				// No database file created
			},
			expectedStatus: "ok",
			expectMessage:  "N/A (no database)",
		},
		{
			name: "valid database",
			setup: func(t *testing.T, dir string) {
				setupTestDatabase(t, dir)
			},
			expectedStatus: "ok",
			expectMessage:  "No corruption detected",
		},
		{
			name: "corrupt database",
			setup: func(t *testing.T, dir string) {
				dbPath := filepath.Join(dir, ".beads", "beads.db")
				// Write garbage that isn't a valid SQLite file
				if err := os.WriteFile(dbPath, []byte("not a sqlite database"), 0600); err != nil {
					t.Fatalf("failed to create corrupt db: %v", err)
				}
			},
			expectedStatus: "error",
			expectMessage:  "", // message varies based on sqlite driver error
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckDatabaseIntegrity(tmpDir)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q", tt.expectedStatus, check.Status)
			}
			if tt.expectMessage != "" && check.Message != tt.expectMessage {
				t.Errorf("expected message %q, got %q", tt.expectMessage, check.Message)
			}
		})
	}
}

func TestCheckDatabaseJSONLSync(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		expectedStatus string
		expectMessage  string
	}{
		{
			name: "no database",
			setup: func(t *testing.T, dir string) {
				// No database, but create JSONL
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				if err := os.WriteFile(jsonlPath, []byte(`{"id":"test-1","title":"Test"}`+"\n"), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "ok",
			expectMessage:  "N/A (no database)",
		},
		{
			name: "no JSONL",
			setup: func(t *testing.T, dir string) {
				setupTestDatabase(t, dir)
			},
			expectedStatus: "ok",
			expectMessage:  "N/A (no JSONL file)",
		},
		{
			name: "both exist with same count",
			setup: func(t *testing.T, dir string) {
				// Create database with one issue
				dbPath := setupTestDatabase(t, dir)
				db, _ := sql.Open("sqlite3", dbPath)
				defer db.Close()
				_, _ = db.Exec(`INSERT INTO issues (id, title, status) VALUES ('test-1', 'Test Issue', 'open')`)

				// Create JSONL with one issue
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				if err := os.WriteFile(jsonlPath, []byte(`{"id":"test-1","title":"Test Issue","status":"open"}`+"\n"), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "warning", // Warning because db doesn't have full schema for prefix check
			expectMessage:  "",
		},
		{
			name: "count mismatch",
			setup: func(t *testing.T, dir string) {
				// Create database with one issue
				dbPath := setupTestDatabase(t, dir)
				db, _ := sql.Open("sqlite3", dbPath)
				defer db.Close()
				_, _ = db.Exec(`INSERT INTO issues (id, title, status) VALUES ('test-1', 'Test Issue', 'open')`)

				// Create JSONL with two issues
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				content := `{"id":"test-1","title":"Test Issue 1","status":"open"}
{"id":"test-2","title":"Test Issue 2","status":"open"}
`
				if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "warning",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckDatabaseJSONLSync(tmpDir)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
			}
			if tt.expectMessage != "" && check.Message != tt.expectMessage {
				t.Errorf("expected message %q, got %q", tt.expectMessage, check.Message)
			}
		})
	}
}

func TestCheckDatabaseVersion(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		expectedStatus string
	}{
		{
			name: "fresh clone with JSONL",
			setup: func(t *testing.T, dir string) {
				// No database but JSONL exists - fresh clone warning
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				if err := os.WriteFile(jsonlPath, []byte(`{"id":"test-1","title":"Test"}`+"\n"), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "warning", // Warning for fresh clone needing init
		},
		{
			name: "no database no jsonl",
			setup: func(t *testing.T, dir string) {
				// No database, no JSONL - error (need to run bd init)
			},
			expectedStatus: "error",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckDatabaseVersion(tmpDir, "0.1.0")

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
			}
		})
	}
}

func TestCheckSchemaCompatibility(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		expectedStatus string
	}{
		{
			name: "no database",
			setup: func(t *testing.T, dir string) {
				// No database created
			},
			expectedStatus: "ok",
		},
		{
			name: "minimal schema",
			setup: func(t *testing.T, dir string) {
				// Our minimal test database doesn't have full schema
				setupTestDatabase(t, dir)
			},
			expectedStatus: "error", // Error because schema is incomplete
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckSchemaCompatibility(tmpDir)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
			}
		})
	}
}

func TestCountJSONLIssues(t *testing.T) {
	tests := []struct {
		name          string
		content       string
		expectedCount int
		expectError   bool
	}{
		{
			name:          "empty file",
			content:       "",
			expectedCount: 0,
			expectError:   false,
		},
		{
			name:          "single issue",
			content:       `{"id":"test-1","title":"Test"}` + "\n",
			expectedCount: 1,
			expectError:   false,
		},
		{
			name: "multiple issues",
			content: `{"id":"test-1","title":"Test 1"}
{"id":"test-2","title":"Test 2"}
{"id":"test-3","title":"Test 3"}
`,
			expectedCount: 3,
			expectError:   false,
		},
		{
			name: "counts all including tombstones",
			content: `{"id":"test-1","title":"Test 1","status":"open"}
{"id":"test-2","title":"Test 2","status":"tombstone"}
{"id":"test-3","title":"Test 3","status":"closed"}
`,
			expectedCount: 3, // CountJSONLIssues counts all records including tombstones
			expectError:   false,
		},
		{
			name: "skips empty lines",
			content: `{"id":"test-1","title":"Test 1"}

{"id":"test-2","title":"Test 2"}
`,
			expectedCount: 2,
			expectError:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			jsonlPath := filepath.Join(tmpDir, "issues.jsonl")
			if err := os.WriteFile(jsonlPath, []byte(tt.content), 0600); err != nil {
				t.Fatalf("failed to create JSONL: %v", err)
			}

			count, _, err := CountJSONLIssues(jsonlPath)

			if tt.expectError && err == nil {
				t.Error("expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if count != tt.expectedCount {
				t.Errorf("expected count %d, got %d", tt.expectedCount, count)
			}
		})
	}
}

func TestCountJSONLIssues_NonexistentFile(t *testing.T) {
	count, _, err := CountJSONLIssues("/nonexistent/path/issues.jsonl")
	if err == nil {
		t.Error("expected error for nonexistent file")
	}
	if count != 0 {
		t.Errorf("expected count 0, got %d", count)
	}
}

func TestCountJSONLIssues_ExtractsPrefixes(t *testing.T) {
	tmpDir := t.TempDir()
	jsonlPath := filepath.Join(tmpDir, "issues.jsonl")
	content := `{"id":"bd-123","title":"Test 1"}
{"id":"bd-456","title":"Test 2"}
{"id":"proj-789","title":"Test 3"}
`
	if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
		t.Fatalf("failed to create JSONL: %v", err)
	}

	count, prefixes, err := CountJSONLIssues(jsonlPath)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if count != 3 {
		t.Errorf("expected count 3, got %d", count)
	}

	// Check prefixes were extracted
	if _, ok := prefixes["bd"]; !ok {
		t.Error("expected 'bd' prefix to be detected")
	}
	if _, ok := prefixes["proj"]; !ok {
		t.Error("expected 'proj' prefix to be detected")
	}
}

// Edge case tests

func TestCheckDatabaseIntegrity_EdgeCases(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string) string
		expectedStatus string
	}{
		{
			name: "locked database file",
			setup: func(t *testing.T, dir string) string {
				dbPath := setupTestDatabase(t, dir)

				// Open a connection with an exclusive lock
				db, err := sql.Open("sqlite3", dbPath)
				if err != nil {
					t.Fatalf("failed to open database: %v", err)
				}

				// Start a transaction to hold a lock
				tx, err := db.Begin()
				if err != nil {
					db.Close()
					t.Fatalf("failed to begin transaction: %v", err)
				}

				// Write some data to ensure the lock is held
				_, err = tx.Exec("INSERT INTO issues (id, title, status) VALUES ('lock-test', 'Lock Test', 'open')")
				if err != nil {
					tx.Rollback()
					db.Close()
					t.Fatalf("failed to insert test data: %v", err)
				}

				// Keep the transaction open by returning a cleanup function via test context
				t.Cleanup(func() {
					tx.Rollback()
					db.Close()
				})

				return dbPath
			},
			expectedStatus: "ok", // Should still succeed with busy_timeout
		},
		{
			name: "read-only database file",
			setup: func(t *testing.T, dir string) string {
				dbPath := setupTestDatabase(t, dir)

				// Make the database file read-only
				if err := os.Chmod(dbPath, 0400); err != nil {
					t.Fatalf("failed to chmod database: %v", err)
				}

				return dbPath
			},
			expectedStatus: "ok", // Integrity check uses read-only mode
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckDatabaseIntegrity(tmpDir)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
			}
		})
	}
}

func TestCheckDatabaseJSONLSync_EdgeCases(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		expectedStatus string
	}{
		{
			name: "malformed JSONL with some valid entries",
			setup: func(t *testing.T, dir string) {
				dbPath := setupTestDatabase(t, dir)
				db, _ := sql.Open("sqlite3", dbPath)
				defer db.Close()

				// Insert test issue into database
				_, _ = db.Exec(`INSERT INTO issues (id, title, status) VALUES ('test-1', 'Test Issue', 'open')`)

				// Create JSONL with malformed entries
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				content := `{"id":"test-1","title":"Valid Entry"}
{malformed json without quotes
{"id":"test-2","incomplete
{"id":"test-3","title":"Another Valid Entry"}
`
				if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "warning", // Should warn about malformed lines
		},
		{
			name: "JSONL with mixed valid and invalid JSON",
			setup: func(t *testing.T, dir string) {
				setupTestDatabase(t, dir)

				// Create JSONL with some invalid JSON lines
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				content := `{"id":"test-1","title":"Valid"}
not json at all
{"id":"test-2","title":"Also Valid"}
{"broken": json}
`
				if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "warning",
		},
		{
			name: "JSONL with entries missing id field",
			setup: func(t *testing.T, dir string) {
				dbPath := setupTestDatabase(t, dir)
				db, _ := sql.Open("sqlite3", dbPath)
				defer db.Close()
				_, _ = db.Exec(`INSERT INTO issues (id, title, status) VALUES ('test-1', 'Test', 'open')`)

				// Create JSONL where some entries don't have id field
				jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
				content := `{"id":"test-1","title":"Has ID"}
{"title":"No ID field","status":"open"}
{"id":"test-2","title":"Has ID"}
`
				if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
					t.Fatalf("failed to create JSONL: %v", err)
				}
			},
			expectedStatus: "warning", // Count mismatch: db has 1, jsonl counts only 2 valid
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckDatabaseJSONLSync(tmpDir)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
			}
		})
	}
}

func TestCheckDatabaseVersion_EdgeCases(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		cliVersion     string
		expectedStatus string
		expectMessage  string
	}{
		{
			name: "future database version",
			setup: func(t *testing.T, dir string) {
				dbPath := filepath.Join(dir, ".beads", "beads.db")
				db, err := sql.Open("sqlite3", dbPath)
				if err != nil {
					t.Fatalf("failed to create database: %v", err)
				}
				defer db.Close()

				// Create metadata table with future version
				_, err = db.Exec(`CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT)`)
				if err != nil {
					t.Fatalf("failed to create metadata table: %v", err)
				}
				_, err = db.Exec(`INSERT INTO metadata (key, value) VALUES ('bd_version', '99.99.99')`)
				if err != nil {
					t.Fatalf("failed to insert version: %v", err)
				}
			},
			cliVersion:     "0.1.0",
			expectedStatus: "warning",
			expectMessage:  "version 99.99.99 (CLI: 0.1.0)",
		},
		{
			name: "database with metadata table but no version",
			setup: func(t *testing.T, dir string) {
				dbPath := filepath.Join(dir, ".beads", "beads.db")
				db, err := sql.Open("sqlite3", dbPath)
				if err != nil {
					t.Fatalf("failed to create database: %v", err)
				}
				defer db.Close()

				// Create metadata table but don't insert version
				_, err = db.Exec(`CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT)`)
				if err != nil {
					t.Fatalf("failed to create metadata table: %v", err)
				}
			},
			cliVersion:     "0.1.0",
			expectedStatus: "error",
			expectMessage:  "Unable to read database version",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckDatabaseVersion(tmpDir, tt.cliVersion)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s)", tt.expectedStatus, check.Status, check.Message)
			}
			if tt.expectMessage != "" && check.Message != tt.expectMessage {
				t.Errorf("expected message %q, got %q", tt.expectMessage, check.Message)
			}
		})
	}
}

func TestCheckSchemaCompatibility_EdgeCases(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(t *testing.T, dir string)
		expectedStatus string
		expectInDetail string
	}{
		{
			name: "partial schema - missing dependencies table",
			setup: func(t *testing.T, dir string) {
				dbPath := filepath.Join(dir, ".beads", "beads.db")
				db, err := sql.Open("sqlite3", dbPath)
				if err != nil {
					t.Fatalf("failed to create database: %v", err)
				}
				defer db.Close()

				// Create only issues table, missing other required tables
				_, err = db.Exec(`CREATE TABLE issues (
					id TEXT PRIMARY KEY,
					title TEXT,
					content_hash TEXT,
					external_ref TEXT,
					compacted_at INTEGER,
					close_reason TEXT
				)`)
				if err != nil {
					t.Fatalf("failed to create issues table: %v", err)
				}
			},
			expectedStatus: "error",
			expectInDetail: "table:dependencies",
		},
		{
			name: "partial schema - missing columns in issues table",
			setup: func(t *testing.T, dir string) {
				dbPath := filepath.Join(dir, ".beads", "beads.db")
				db, err := sql.Open("sqlite3", dbPath)
				if err != nil {
					t.Fatalf("failed to create database: %v", err)
				}
				defer db.Close()

				// Create issues table missing some required columns
				_, err = db.Exec(`CREATE TABLE issues (
					id TEXT PRIMARY KEY,
					title TEXT
				)`)
				if err != nil {
					t.Fatalf("failed to create issues table: %v", err)
				}

				// Create other tables to avoid those errors
				_, err = db.Exec(`CREATE TABLE dependencies (
					issue_id TEXT,
					depends_on_id TEXT,
					type TEXT
				)`)
				if err != nil {
					t.Fatalf("failed to create dependencies table: %v", err)
				}

				_, err = db.Exec(`CREATE TABLE child_counters (
					parent_id TEXT,
					last_child INTEGER
				)`)
				if err != nil {
					t.Fatalf("failed to create child_counters table: %v", err)
				}

				_, err = db.Exec(`CREATE TABLE export_hashes (
					issue_id TEXT,
					content_hash TEXT
				)`)
				if err != nil {
					t.Fatalf("failed to create export_hashes table: %v", err)
				}
			},
			expectedStatus: "error",
			expectInDetail: "issues.content_hash",
		},
		{
			name: "database with no tables",
			setup: func(t *testing.T, dir string) {
				dbPath := filepath.Join(dir, ".beads", "beads.db")
				db, err := sql.Open("sqlite3", dbPath)
				if err != nil {
					t.Fatalf("failed to create database: %v", err)
				}
				// Execute a query to ensure the database file is created
				_, err = db.Exec("SELECT 1")
				if err != nil {
					t.Fatalf("failed to initialize database: %v", err)
				}
				db.Close()
			},
			expectedStatus: "error",
			expectInDetail: "table:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			beadsDir := filepath.Join(tmpDir, ".beads")
			if err := os.MkdirAll(beadsDir, 0755); err != nil {
				t.Fatal(err)
			}

			tt.setup(t, tmpDir)

			check := CheckSchemaCompatibility(tmpDir)

			if check.Status != tt.expectedStatus {
				t.Errorf("expected status %q, got %q (message: %s, detail: %s)",
					tt.expectedStatus, check.Status, check.Message, check.Detail)
			}
			if tt.expectInDetail != "" && !strings.Contains(check.Detail, tt.expectInDetail) {
				t.Errorf("expected detail to contain %q, got %q", tt.expectInDetail, check.Detail)
			}
		})
	}
}

func TestCountJSONLIssues_EdgeCases(t *testing.T) {
	tests := []struct {
		name          string
		setupContent  func() string
		expectedCount int
		expectError   bool
		errorContains string
	}{
		{
			name: "malformed JSON lines",
			setupContent: func() string {
				return `{"id":"valid-1","title":"Valid"}
{this is not json
{"id":"valid-2","title":"Also Valid"}
{malformed: true}
{"id":"valid-3","title":"Third Valid"}
`
			},
			expectedCount: 3,
			expectError:   true,
			errorContains: "malformed",
		},
		{
			name: "very large file with 10000 issues",
			setupContent: func() string {
				var sb strings.Builder
				for i := 0; i < 10000; i++ {
					sb.WriteString(fmt.Sprintf(`{"id":"issue-%d","title":"Issue %d","status":"open"}`, i, i))
					sb.WriteString("\n")
				}
				return sb.String()
			},
			expectedCount: 10000,
			expectError:   false,
		},
		{
			name: "file with unicode and special characters",
			setupContent: func() string {
				return `{"id":"test-1","title":"Issue with émojis 🎉","description":"Unicode: 日本語"}
{"id":"test-2","title":"Quotes \"escaped\" and 'mixed'","status":"open"}
{"id":"test-3","title":"Newlines\nand\ttabs","status":"closed"}
`
			},
			expectedCount: 3,
			expectError:   false,
		},
		{
			name: "file with trailing whitespace",
			setupContent: func() string {
				return `{"id":"test-1","title":"Test"}
  {"id":"test-2","title":"Test 2"}
{"id":"test-3","title":"Test 3"}
`
			},
			expectedCount: 3,
			expectError:   false,
		},
		{
			name: "all lines are malformed",
			setupContent: func() string {
				return `not json
also not json
{still: not valid}
`
			},
			expectedCount: 0,
			expectError:   true,
			errorContains: "malformed",
		},
		{
			name: "valid JSON but missing id in all entries",
			setupContent: func() string {
				return `{"title":"No ID 1","status":"open"}
{"title":"No ID 2","status":"closed"}
{"title":"No ID 3","status":"pending"}
`
			},
			expectedCount: 0,
			expectError:   false,
		},
		{
			name: "entries with numeric ids",
			setupContent: func() string {
				return `{"id":123,"title":"Numeric ID"}
{"id":"valid-1","title":"String ID"}
{"id":null,"title":"Null ID"}
`
			},
			expectedCount: 1, // Only the string ID counts
			expectError:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			jsonlPath := filepath.Join(tmpDir, "issues.jsonl")
			content := tt.setupContent()
			if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
				t.Fatalf("failed to create JSONL: %v", err)
			}

			count, _, err := CountJSONLIssues(jsonlPath)

			if tt.expectError && err == nil {
				t.Error("expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if tt.expectError && err != nil && tt.errorContains != "" {
				if !strings.Contains(err.Error(), tt.errorContains) {
					t.Errorf("expected error to contain %q, got %q", tt.errorContains, err.Error())
				}
			}
			if count != tt.expectedCount {
				t.Errorf("expected count %d, got %d", tt.expectedCount, count)
			}
		})
	}
}

func TestCountJSONLIssues_Performance(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping performance test in short mode")
	}

	tmpDir := t.TempDir()
	jsonlPath := filepath.Join(tmpDir, "large.jsonl")

	// Create a very large JSONL file (100k issues)
	file, err := os.Create(jsonlPath)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}

	for i := 0; i < 100000; i++ {
		line := fmt.Sprintf(`{"id":"perf-%d","title":"Performance Test Issue %d","status":"open","description":"Testing performance with large files"}`, i, i)
		if _, err := file.WriteString(line + "\n"); err != nil {
			file.Close()
			t.Fatalf("failed to write line: %v", err)
		}
	}
	file.Close()

	// Measure time to count issues
	start := time.Now()
	count, prefixes, err := CountJSONLIssues(jsonlPath)
	duration := time.Since(start)

	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if count != 100000 {
		t.Errorf("expected count 100000, got %d", count)
	}
	if len(prefixes) != 1 || prefixes["perf"] != 100000 {
		t.Errorf("expected single prefix 'perf' with count 100000, got %v", prefixes)
	}

	// Performance should be reasonable (< 5 seconds for 100k issues)
	if duration > 5*time.Second {
		t.Logf("Warning: counting 100k issues took %v (expected < 5s)", duration)
	} else {
		t.Logf("Performance: counted 100k issues in %v", duration)
	}
}
