package resolver

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/zzet/gortex/internal/graph"
)

// memberOf builds a WorkspaceMembership that derives a file's
// workspace-member id from the leading two path segments — the test
// stand-in for the indexer's manifest-driven membership index.
func memberOf(filePath string) string {
	parts := strings.Split(filePath, "/")
	if len(parts) >= 2 && parts[0] == "packages" {
		return parts[0] + "/" + parts[1]
	}
	return ""
}

// TestResolveImport_PrefersSameWorkspaceCandidate pins the
// same-workspace preference: a bare import name that matches a
// directory in two different package-manager workspace members
// resolves to the candidate sharing the importer's own member.
func TestResolveImport_PrefersSameWorkspaceCandidate(t *testing.T) {
	g := graph.New()
	// Importer lives in workspace member `packages/app`.
	g.AddNode(&graph.Node{
		ID: "packages/app/src/main.ts::main", Kind: graph.KindFunction, Name: "main",
		FilePath: "packages/app/src/main.ts", Language: "typescript",
	})
	// Two same-named `logger` packages in two different members.
	g.AddNode(&graph.Node{
		ID: "packages/app/logger/index.ts", Kind: graph.KindFile, Name: "index.ts",
		FilePath: "packages/app/logger/index.ts", Language: "typescript",
	})
	g.AddNode(&graph.Node{
		ID: "packages/admin/logger/index.ts", Kind: graph.KindFile, Name: "index.ts",
		FilePath: "packages/admin/logger/index.ts", Language: "typescript",
	})

	edge := &graph.Edge{
		From: "packages/app/src/main.ts::main", To: "unresolved::import::logger",
		Kind: graph.EdgeImports, FilePath: "packages/app/src/main.ts", Line: 1,
	}
	g.AddEdge(edge)

	r := New(g)
	r.SetWorkspaceMembership(memberOf)
	r.ResolveAll()

	assert.Equal(t, "packages/app/logger/index.ts", edge.To,
		"import should resolve to the logger in the importer's own workspace member")
}

// TestResolveImport_NoWorkspaceLookupKeepsBaseline confirms that
// without a workspace lookup the resolver's pre-existing first-hit
// behaviour is unchanged — no regression for non-workspace repos.
func TestResolveImport_NoWorkspaceLookupKeepsBaseline(t *testing.T) {
	g := graph.New()
	g.AddNode(&graph.Node{
		ID: "src/main.ts::main", Kind: graph.KindFunction, Name: "main",
		FilePath: "src/main.ts", Language: "typescript",
	})
	g.AddNode(&graph.Node{
		ID: "vendor/logger/index.ts", Kind: graph.KindFile, Name: "index.ts",
		FilePath: "vendor/logger/index.ts", Language: "typescript",
	})

	edge := &graph.Edge{
		From: "src/main.ts::main", To: "unresolved::import::logger",
		Kind: graph.EdgeImports, FilePath: "src/main.ts", Line: 1,
	}
	g.AddEdge(edge)

	// No SetWorkspaceMembership — the resolver must still resolve the
	// single directory match exactly as before the feature.
	New(g).ResolveAll()

	assert.Equal(t, "vendor/logger/index.ts", edge.To,
		"single directory match must resolve regardless of workspace lookup")
}

// TestResolveImport_WorkspaceLookupNoMatchKeepsFirstHit confirms that
// when the importer belongs to no workspace member the resolver falls
// back to its first-hit tie-break rather than failing the import.
func TestResolveImport_WorkspaceLookupNoMatchKeepsFirstHit(t *testing.T) {
	g := graph.New()
	// Importer is outside every `packages/*` member directory.
	g.AddNode(&graph.Node{
		ID: "tools/cli/main.ts::main", Kind: graph.KindFunction, Name: "main",
		FilePath: "tools/cli/main.ts", Language: "typescript",
	})
	g.AddNode(&graph.Node{
		ID: "packages/app/logger/index.ts", Kind: graph.KindFile, Name: "index.ts",
		FilePath: "packages/app/logger/index.ts", Language: "typescript",
	})

	edge := &graph.Edge{
		From: "tools/cli/main.ts::main", To: "unresolved::import::logger",
		Kind: graph.EdgeImports, FilePath: "tools/cli/main.ts", Line: 1,
	}
	g.AddEdge(edge)

	r := New(g)
	r.SetWorkspaceMembership(memberOf)
	r.ResolveAll()

	assert.Equal(t, "packages/app/logger/index.ts", edge.To,
		"importer outside any workspace member should still resolve via the existing tie-break")
}

// TestPickSameWorkspaceFile_AmbiguousLeavesTieBreak pins the unit
// contract: when several candidates share the importer's workspace the
// helper returns nil so the caller's existing tie-break decides.
func TestPickSameWorkspaceFile_AmbiguousLeavesTieBreak(t *testing.T) {
	caller := "packages/app/src/main.ts"
	candidates := []*graph.Node{
		{ID: "packages/app/a/logger.ts", Kind: graph.KindFile, FilePath: "packages/app/a/logger.ts"},
		{ID: "packages/app/b/logger.ts", Kind: graph.KindFile, FilePath: "packages/app/b/logger.ts"},
	}
	if got := pickSameWorkspaceFile(memberOf, caller, candidates); got != nil {
		t.Errorf("two candidates in the importer's workspace should be ambiguous, got %q", got.ID)
	}

	// A single same-workspace candidate among decoys resolves cleanly.
	candidates = []*graph.Node{
		{ID: "packages/admin/logger.ts", Kind: graph.KindFile, FilePath: "packages/admin/logger.ts"},
		{ID: "packages/app/logger.ts", Kind: graph.KindFile, FilePath: "packages/app/logger.ts"},
	}
	got := pickSameWorkspaceFile(memberOf, caller, candidates)
	if got == nil || got.ID != "packages/app/logger.ts" {
		t.Errorf("expected the packages/app candidate, got %v", got)
	}
}
