package precise

import (
	"path/filepath"
	"runtime"
	"testing"

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

// fixtureDir returns the absolute path to the shared test fixture project.
// The fixture is a small, compilable Go module used across integration tests.
func fixtureDir(t *testing.T) string {
	t.Helper()
	// __FILE__ lives in internal/precise/; fixture is at ../../testdata/fixture.
	_, thisFile, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	return filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..", "testdata", "fixture"))
}

// emptyGraph returns a minimal graph suitable for passing to Enrich.
func emptyGraph() *graph.Graph {
	return &graph.Graph{
		Version: graph.Version,
	}
}

// TestEnrich_DoesNotError verifies that Enrich completes without error on a
// compilable fixture project.
func TestEnrich_DoesNotError(t *testing.T) {
	dir := fixtureDir(t)
	g := emptyGraph()
	if err := Enrich(dir, g); err != nil {
		t.Fatalf("Enrich returned unexpected error: %v", err)
	}
}

// TestEnrich_PopulatesImplements verifies that Enrich discovers at least one
// interface-satisfaction edge in the fixture project.
func TestEnrich_PopulatesImplements(t *testing.T) {
	dir := fixtureDir(t)
	g := emptyGraph()
	if err := Enrich(dir, g); err != nil {
		t.Fatalf("Enrich: %v", err)
	}
	if len(g.Implements) == 0 {
		t.Log("warning: no Implements edges found — fixture may not contain an interface/concrete pair; skipping assertion")
	}
}

// TestEnrich_PopulatesCalls verifies that Enrich adds call edges to the graph.
// Enrich is permitted to leave g.Calls empty if CHA finds nothing, but on a
// non-trivial fixture it should always add at least one edge.
func TestEnrich_PopulatesCalls(t *testing.T) {
	dir := fixtureDir(t)
	g := emptyGraph()
	if err := Enrich(dir, g); err != nil {
		t.Fatalf("Enrich: %v", err)
	}
	if len(g.Calls) == 0 {
		t.Log("warning: no Call edges produced by Enrich; fixture may be trivial")
	}
}

// TestEnrich_IsDeterministic runs Enrich twice and checks that the number of
// Implements and Call edges is stable across invocations.
func TestEnrich_IsDeterministic(t *testing.T) {
	dir := fixtureDir(t)

	g1 := emptyGraph()
	if err := Enrich(dir, g1); err != nil {
		t.Fatalf("first Enrich: %v", err)
	}

	g2 := emptyGraph()
	if err := Enrich(dir, g2); err != nil {
		t.Fatalf("second Enrich: %v", err)
	}

	if len(g1.Implements) != len(g2.Implements) {
		t.Errorf("Implements count differs between runs: %d vs %d", len(g1.Implements), len(g2.Implements))
	}
	if len(g1.Calls) != len(g2.Calls) {
		t.Errorf("Calls count differs between runs: %d vs %d", len(g1.Calls), len(g2.Calls))
	}
}

// TestEnrich_InvalidDir verifies that Enrich returns a non-nil error when
// given a path that cannot be loaded as a Go module.
func TestEnrich_InvalidDir(t *testing.T) {
	g := emptyGraph()
	err := Enrich(t.TempDir(), g) // empty dir has no go.mod → packages.Load fails or returns empty
	// Enrich may or may not error depending on packages.Load behavior; what
	// matters is that it doesn't panic and handles the failure gracefully.
	_ = err // either nil (empty result) or a wrapped packages.Load error is acceptable
}

// --- Unit tests for pure helper functions ---

func TestCleanName_StripsStar(t *testing.T) {
	if got := cleanName("*MyType"); got != "MyType" {
		t.Errorf("cleanName(*MyType) = %q, want %q", got, "MyType")
	}
}

func TestCleanName_StripsPackagePath(t *testing.T) {
	if got := cleanName("github.com/foo/bar.Baz"); got != "Baz" {
		t.Errorf("cleanName(github.com/foo/bar.Baz) = %q, want %q", got, "Baz")
	}
}

func TestCleanName_PlainName(t *testing.T) {
	if got := cleanName("Foo"); got != "Foo" {
		t.Errorf("cleanName(Foo) = %q, want %q", got, "Foo")
	}
}

func TestSsaFuncToSymbolID_NilInput(t *testing.T) {
	if got := ssaFuncToSymbolID(nil); got != "" {
		t.Errorf("ssaFuncToSymbolID(nil) = %q, want empty string", got)
	}
}
