package main

import (
	"context"
	"os"
	"path/filepath"
	"testing"

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

func TestRepairDeps_NoOrphans(t *testing.T) {
	dir := t.TempDir()
	dbPath := filepath.Join(dir, ".beads", "beads.db")
	if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
		t.Fatal(err)
	}

	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()

	ctx := context.Background()

	// Initialize database
	store.SetConfig(ctx, "issue_prefix", "test-")
	
	// Create two issues with valid dependency
	i1 := &types.Issue{Title: "Issue 1", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i1, "test")
	i2 := &types.Issue{Title: "Issue 2", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i2, "test")
	store.AddDependency(ctx, &types.Dependency{
		IssueID:     i2.ID,
		DependsOnID: i1.ID,
		Type:        types.DepBlocks,
	}, "test")

	// Get all dependency records
	allDeps, err := store.GetAllDependencyRecords(ctx)
	if err != nil {
		t.Fatal(err)
	}

	// Get all issues
	issues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
	if err != nil {
		t.Fatal(err)
	}

	// Build valid ID set
	validIDs := make(map[string]bool)
	for _, issue := range issues {
		validIDs[issue.ID] = true
	}

	// Find orphans
	orphanCount := 0
	for issueID, deps := range allDeps {
		if !validIDs[issueID] {
			continue
		}
		for _, dep := range deps {
			if !validIDs[dep.DependsOnID] {
				orphanCount++
			}
		}
	}

	if orphanCount != 0 {
		t.Errorf("Expected 0 orphans, got %d", orphanCount)
	}
}

func TestRepairDeps_FindOrphans(t *testing.T) {
	dir := t.TempDir()
	dbPath := filepath.Join(dir, ".beads", "beads.db")
	if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
		t.Fatal(err)
	}

	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()

	ctx := context.Background()

	// Initialize database
	store.SetConfig(ctx, "issue_prefix", "test-")
	
	// Create two issues
	i1 := &types.Issue{Title: "Issue 1", Priority: 1, Status: "open", IssueType: "task"}
	if err := store.CreateIssue(ctx, i1, "test"); err != nil {
		t.Fatalf("CreateIssue failed: %v", err)
	}
	t.Logf("Created i1: %s", i1.ID)
	
	i2 := &types.Issue{Title: "Issue 2", Priority: 1, Status: "open", IssueType: "task"}
	if err := store.CreateIssue(ctx, i2, "test"); err != nil {
		t.Fatalf("CreateIssue failed: %v", err)
	}
	t.Logf("Created i2: %s", i2.ID)

	// Add dependency
	err = store.AddDependency(ctx, &types.Dependency{
		IssueID:     i2.ID,
		DependsOnID: i1.ID,
		Type:        types.DepBlocks,
	}, "test")
	if err != nil {
		t.Fatalf("AddDependency failed: %v", err)
	}

	// Manually create orphaned dependency by directly inserting invalid reference
	// This simulates corruption or import errors
	db := store.UnderlyingDB()
	_, err = db.ExecContext(ctx, "PRAGMA foreign_keys = OFF")
	if err != nil {
		t.Fatal(err)
	}
	// Insert a dependency pointing to a non-existent issue
	_, err = db.ExecContext(ctx, `INSERT INTO dependencies (issue_id, depends_on_id, type, created_at, created_by) 
		VALUES (?, 'nonexistent-123', 'blocks', datetime('now'), 'test')`, i2.ID)
	if err != nil {
		t.Fatalf("Failed to insert orphaned dependency: %v", err)
	}
	_, err = db.ExecContext(ctx, "PRAGMA foreign_keys = ON")
	if err != nil {
		t.Fatal(err)
	}
	
	// Verify the orphan was actually inserted
	var count int
	err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dependencies WHERE depends_on_id = 'nonexistent-123'").Scan(&count)
	if err != nil {
		t.Fatal(err)
	}
	if count != 1 {
		t.Fatalf("Orphan dependency not inserted, count=%d", count)
	}

	// Get all dependency records
	allDeps, err := store.GetAllDependencyRecords(ctx)
	if err != nil {
		t.Fatal(err)
	}

	t.Logf("Got %d issues with dependencies", len(allDeps))
	for issueID, deps := range allDeps {
		t.Logf("Issue %s has %d dependencies", issueID, len(deps))
		for _, dep := range deps {
			t.Logf("  -> %s (%s)", dep.DependsOnID, dep.Type)
		}
	}

	// Get all issues
	issues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
	if err != nil {
		t.Fatal(err)
	}

	// Build valid ID set
	validIDs := make(map[string]bool)
	for _, issue := range issues {
		validIDs[issue.ID] = true
	}
	t.Logf("Valid issue IDs: %v", validIDs)

	// Find orphans
	orphanCount := 0
	for issueID, deps := range allDeps {
		if !validIDs[issueID] {
			t.Logf("Skipping %s - issue itself doesn't exist", issueID)
			continue
		}
		for _, dep := range deps {
			if !validIDs[dep.DependsOnID] {
				t.Logf("Found orphan: %s -> %s", dep.IssueID, dep.DependsOnID)
				orphanCount++
			}
		}
	}

	if orphanCount != 1 {
		t.Errorf("Expected 1 orphan, got %d", orphanCount)
	}
}

func TestRepairDeps_FixOrphans(t *testing.T) {
	dir := t.TempDir()
	dbPath := filepath.Join(dir, ".beads", "beads.db")
	if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
		t.Fatal(err)
	}

	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()

	ctx := context.Background()

	// Initialize database
	store.SetConfig(ctx, "issue_prefix", "test-")
	
	// Create three issues
	i1 := &types.Issue{Title: "Issue 1", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i1, "test")
	i2 := &types.Issue{Title: "Issue 2", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i2, "test")
	i3 := &types.Issue{Title: "Issue 3", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i3, "test")

	// Add dependencies
	store.AddDependency(ctx, &types.Dependency{
		IssueID:     i2.ID,
		DependsOnID: i1.ID,
		Type:        types.DepBlocks,
	}, "test")
	store.AddDependency(ctx, &types.Dependency{
		IssueID:     i3.ID,
		DependsOnID: i1.ID,
		Type:        types.DepBlocks,
	}, "test")

	// Manually create orphaned dependencies by inserting invalid references
	db := store.UnderlyingDB()
	db.Exec("PRAGMA foreign_keys = OFF")
	_, err = db.ExecContext(ctx, `INSERT INTO dependencies (issue_id, depends_on_id, type, created_at, created_by) 
		VALUES (?, 'nonexistent-123', 'blocks', datetime('now'), 'test')`, i2.ID)
	if err != nil {
		t.Fatal(err)
	}
	_, err = db.ExecContext(ctx, `INSERT INTO dependencies (issue_id, depends_on_id, type, created_at, created_by) 
		VALUES (?, 'nonexistent-456', 'blocks', datetime('now'), 'test')`, i3.ID)
	if err != nil {
		t.Fatal(err)
	}
	db.Exec("PRAGMA foreign_keys = ON")

	// Find and fix orphans
	allDeps, _ := store.GetAllDependencyRecords(ctx)
	issues, _ := store.SearchIssues(ctx, "", types.IssueFilter{})

	validIDs := make(map[string]bool)
	for _, issue := range issues {
		validIDs[issue.ID] = true
	}

	type orphan struct {
		issueID     string
		dependsOnID string
	}
	var orphans []orphan

	for issueID, deps := range allDeps {
		if !validIDs[issueID] {
			continue
		}
		for _, dep := range deps {
			if !validIDs[dep.DependsOnID] {
				orphans = append(orphans, orphan{
					issueID:     dep.IssueID,
					dependsOnID: dep.DependsOnID,
				})
			}
		}
	}

	if len(orphans) != 2 {
		t.Fatalf("Expected 2 orphans before fix, got %d", len(orphans))
	}

	// Fix orphans using direct SQL (like the command does)
	for _, o := range orphans {
		_, delErr := db.ExecContext(ctx, "DELETE FROM dependencies WHERE issue_id = ? AND depends_on_id = ?",
			o.issueID, o.dependsOnID)
		if delErr != nil {
			t.Errorf("Failed to remove orphan: %v", delErr)
		}
	}

	// Verify orphans removed
	allDeps, _ = store.GetAllDependencyRecords(ctx)
	orphanCount := 0
	for issueID, deps := range allDeps {
		if !validIDs[issueID] {
			continue
		}
		for _, dep := range deps {
			if !validIDs[dep.DependsOnID] {
				orphanCount++
			}
		}
	}

	if orphanCount != 0 {
		t.Errorf("Expected 0 orphans after fix, got %d", orphanCount)
	}
}

func TestRepairDeps_MultipleTypes(t *testing.T) {
	dir := t.TempDir()
	dbPath := filepath.Join(dir, ".beads", "beads.db")
	if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
		t.Fatal(err)
	}

	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()

	ctx := context.Background()

	// Initialize database
	store.SetConfig(ctx, "issue_prefix", "test-")
	
	// Create issues
	i1 := &types.Issue{Title: "Issue 1", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i1, "test")
	i2 := &types.Issue{Title: "Issue 2", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i2, "test")
	i3 := &types.Issue{Title: "Issue 3", Priority: 1, Status: "open", IssueType: "task"}
	store.CreateIssue(ctx, i3, "test")

	// Add different dependency types
	store.AddDependency(ctx, &types.Dependency{
		IssueID:     i2.ID,
		DependsOnID: i1.ID,
		Type:        types.DepBlocks,
	}, "test")
	store.AddDependency(ctx, &types.Dependency{
		IssueID:     i3.ID,
		DependsOnID: i1.ID,
		Type:        types.DepRelated,
	}, "test")

	// Manually create orphaned dependencies with different types
	db := store.UnderlyingDB()
	db.Exec("PRAGMA foreign_keys = OFF")
	_, err = db.ExecContext(ctx, `INSERT INTO dependencies (issue_id, depends_on_id, type, created_at, created_by) 
		VALUES (?, 'nonexistent-blocks', 'blocks', datetime('now'), 'test')`, i2.ID)
	if err != nil {
		t.Fatal(err)
	}
	_, err = db.ExecContext(ctx, `INSERT INTO dependencies (issue_id, depends_on_id, type, created_at, created_by) 
		VALUES (?, 'nonexistent-related', 'related', datetime('now'), 'test')`, i3.ID)
	if err != nil {
		t.Fatal(err)
	}
	db.Exec("PRAGMA foreign_keys = ON")

	// Find orphans
	allDeps, _ := store.GetAllDependencyRecords(ctx)
	issues, _ := store.SearchIssues(ctx, "", types.IssueFilter{})

	validIDs := make(map[string]bool)
	for _, issue := range issues {
		validIDs[issue.ID] = true
	}

	orphanCount := 0
	depTypes := make(map[types.DependencyType]int)
	for issueID, deps := range allDeps {
		if !validIDs[issueID] {
			continue
		}
		for _, dep := range deps {
			if !validIDs[dep.DependsOnID] {
				orphanCount++
				depTypes[dep.Type]++
			}
		}
	}

	if orphanCount != 2 {
		t.Errorf("Expected 2 orphans, got %d", orphanCount)
	}
	if depTypes[types.DepBlocks] != 1 {
		t.Errorf("Expected 1 blocks orphan, got %d", depTypes[types.DepBlocks])
	}
	if depTypes[types.DepRelated] != 1 {
		t.Errorf("Expected 1 related orphan, got %d", depTypes[types.DepRelated])
	}
}
