package scanner_test

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

	"github.com/ozgurcd/gograph/internal/scanner"
)

func TestShouldIgnoreDir(t *testing.T) {
	cases := []struct {
		dir    string
		expect bool
	}{
		{".git", true},
		{".gograph", true},
		{"vendor", true},
		{"node_modules", true},
		{"dist", true},
		{"build", true},
		{".terraform", true},
		// AI agent directories added for issue #17.
		{".claude", true},
		{".cursor", true},
		{".agents", true},
		// Normal directories must not be ignored.
		{"internal", false},
		{"cmd", false},
		{"mypkg", false},
	}
	for _, tc := range cases {
		got := scanner.ShouldIgnoreDir(tc.dir)
		if got != tc.expect {
			t.Errorf("ShouldIgnoreDir(%q) = %v, want %v", tc.dir, got, tc.expect)
		}
	}
}

func TestShouldIgnoreFile_Suffixes(t *testing.T) {
	dir := t.TempDir()

	pb := filepath.Join(dir, "foo.pb.go")
	mustWrite(t, pb, "package foo\n")
	if skip, _ := scanner.ShouldIgnoreFile(pb); !skip {
		t.Error("expected .pb.go to be ignored")
	}

	gen := filepath.Join(dir, "bar_generated.go")
	mustWrite(t, gen, "package foo\n")
	if skip, _ := scanner.ShouldIgnoreFile(gen); !skip {
		t.Error("expected _generated.go to be ignored")
	}
}

func TestShouldIgnoreFile_CodeGeneratedComment(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "normal.go")
	mustWrite(t, path, "// Code generated by protoc. DO NOT EDIT.\npackage foo\n")
	if skip, _ := scanner.ShouldIgnoreFile(path); !skip {
		t.Error("expected file with 'Code generated' comment to be ignored")
	}
}

func TestShouldIgnoreFile_Normal(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "normal.go")
	mustWrite(t, path, "package foo\n\nfunc Hello() {}\n")
	if skip, _ := scanner.ShouldIgnoreFile(path); skip {
		t.Error("expected normal Go file not to be ignored")
	}
}

func TestWalk_SkipsIgnoredDirs(t *testing.T) {
	root := t.TempDir()
	mustWrite(t, filepath.Join(root, "main.go"), "package main\nfunc main(){}\n")

	vendorDir := filepath.Join(root, "vendor", "pkg")
	if err := os.MkdirAll(vendorDir, 0o750); err != nil {
		t.Fatal(err)
	}
	mustWrite(t, filepath.Join(vendorDir, "vendor.go"), "package pkg\n")

	paths, errs := scanner.Walk(root)
	if len(errs) > 0 {
		t.Fatalf("unexpected errors: %v", errs)
	}
	if len(paths) != 1 {
		t.Fatalf("expected 1 path, got %d: %v", len(paths), paths)
	}
}

// TestWalk_SkipsClaudeWorktrees is the regression test for GitHub issue #17.
// Claude Code creates worktrees inside .claude/worktrees/agent-<id>/ which are
// verbatim copies of the project directory. Without exclusion, every symbol and
// call edge in the project is duplicated in the graph.
//
// The .claude directory is in the hardcoded ignoredDirs blocklist, so this test
// does NOT require git to be present.
func TestWalk_SkipsClaudeWorktrees(t *testing.T) {
	root := t.TempDir()

	// Real project file — must be included.
	mustWrite(t, filepath.Join(root, "main.go"), "package main\nfunc main(){}\n")

	// Simulated Claude worktree — must be excluded entirely.
	worktreeFile := filepath.Join(root, ".claude", "worktrees", "agent-a18fea2ec1ebda290", "internal", "pkg", "foo.go")
	mustWrite(t, worktreeFile, "package pkg\nfunc Foo(){}\n")

	paths, errs := scanner.Walk(root)
	if len(errs) > 0 {
		t.Fatalf("unexpected errors: %v", errs)
	}
	if len(paths) != 1 {
		t.Fatalf("expected exactly 1 path (main.go), got %d: %v", len(paths), paths)
	}
	if filepath.Base(paths[0]) != "main.go" {
		t.Errorf("expected main.go, got %s", paths[0])
	}
}

// TestWalk_SkipsCursorDirs verifies that .cursor AI agent directories are also
// excluded via the hardcoded blocklist.
func TestWalk_SkipsCursorDirs(t *testing.T) {
	root := t.TempDir()
	mustWrite(t, filepath.Join(root, "app.go"), "package main\n")
	mustWrite(t, filepath.Join(root, ".cursor", "scratch", "temp.go"), "package scratch\n")

	paths, errs := scanner.Walk(root)
	if len(errs) > 0 {
		t.Fatalf("unexpected errors: %v", errs)
	}
	if len(paths) != 1 || filepath.Base(paths[0]) != "app.go" {
		t.Fatalf("expected only app.go, got %v", paths)
	}
}

func mustWrite(t *testing.T, path, content string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(path, []byte(content), 0o640); err != nil {
		t.Fatal(err)
	}
}

// TestWalk_SkipsAgentsDirs verifies .agents directories are excluded.
func TestWalk_SkipsAgentsDirs(t *testing.T) {
	root := t.TempDir()
	mustWrite(t, filepath.Join(root, "app.go"), "package main\n")
	mustWrite(t, filepath.Join(root, ".agents", "scratch", "temp.go"), "package scratch\n")

	paths, errs := scanner.Walk(root)
	if len(errs) > 0 {
		t.Fatalf("unexpected errors: %v", errs)
	}
	if len(paths) != 1 || filepath.Base(paths[0]) != "app.go" {
		t.Fatalf("expected only app.go, got %v", paths)
	}
}
