package workspace

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

	"github.com/stretchr/testify/require"
)

// TestReviewResolvedRootResolvesSymlinkAlias confirms two paths that resolve
// to the same physical worktree (one direct, one via symlink) produce the same
// resolved cache identity. This is the regression for the v2 #6 finding: the
// pre-rename reviewDisplayRoot retained the caller-supplied alias prefix,
// so two callers via different aliases poisoned the working state cache with
// two keys for the same underlying repo.
func TestReviewResolvedRootResolvesSymlinkAlias(t *testing.T) {
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not available")
	}

	repoDir := t.TempDir()
	setupReviewTestRepo(t, repoDir)

	// realpath of repoDir — setupReviewTestRepo does not introduce symlinks,
	// so filepath.EvalSymlinks(repoDir) should equal the cleaned abs path.
	expectedReal, err := filepath.EvalSymlinks(filepath.Clean(repoDir))
	require.NoError(t, err)

	aliasDir := filepath.Join(t.TempDir(), "alias")
	require.NoError(t, os.Symlink(repoDir, aliasDir))

	gotDirect := reviewResolvedRoot(repoDir)
	gotAlias := reviewResolvedRoot(aliasDir)

	require.Equal(t, expectedReal, gotDirect,
		"direct path must resolve to the realpath of the repo root")
	require.Equal(t, gotDirect, gotAlias,
		"symlink alias and direct path must produce the same resolved root so the cache key collides")
}

// TestReviewResolvedRootDistinguishesLinkedWorktrees confirms two worktrees
// that share a git common dir but live in different filesystem locations
// produce DIFFERENT resolved roots. The cache must not collide between linked
// worktrees because their working states are independent.
func TestReviewResolvedRootDistinguishesLinkedWorktrees(t *testing.T) {
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not available")
	}

	mainDir := t.TempDir()
	setupReviewTestRepo(t, mainDir)

	linkedDir := filepath.Join(t.TempDir(), "linked")
	runGit(t, mainDir, "worktree", "add", "-b", "linked-branch", linkedDir)

	mainResolved := reviewResolvedRoot(mainDir)
	linkedResolved := reviewResolvedRoot(linkedDir)

	require.NotEqual(t, mainResolved, linkedResolved,
		"linked worktrees must not collide on the working state cache key")
}

// TestLoadReviewRepoStateSetsResolvedRootIndependentlyFromDisplayRoot
// confirms loadReviewRepoState fills both root (display) and resolvedRoot
// (cache identity) fields, and that resolvedRoot is the realpath while
// display root may preserve caller alias information.
func TestLoadReviewRepoStateSetsResolvedRootIndependentlyFromDisplayRoot(t *testing.T) {
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not available")
	}

	repoDir := t.TempDir()
	setupReviewTestRepo(t, repoDir)
	repoReal, err := filepath.EvalSymlinks(filepath.Clean(repoDir))
	require.NoError(t, err)

	// agentDir is the repo root itself. resolvedRoot is the realpath (with
	// symlinks fully resolved); display root is filepath.Clean(Abs(...)) and
	// may preserve a symlink prefix that the OS introduces transparently
	// (e.g. macOS /var → /private/var). The two intentionally diverge here —
	// that is the whole reason resolvedRoot exists separately from root.
	repo, summary, err := loadReviewRepoState(repoDir, "", "", "", "")
	require.NoError(t, err)
	require.Equal(t, "", summary.Status)
	require.Equal(t, repoReal, repo.resolvedRoot,
		"resolvedRoot must be the realpath of the worktree filesystem root")

	// Now exercise the display-root-aliasing path: agentDir nested under repo
	// root. resolvedRoot must still resolve to the realpath; display root may
	// walk up to preserve the caller-supplied alias.
	nestedAgent := filepath.Join(repoDir, "subdir")
	require.NoError(t, os.MkdirAll(nestedAgent, 0o755))
	repo2, _, err := loadReviewRepoState(nestedAgent, "", "", "", "")
	require.NoError(t, err)
	require.Equal(t, repoReal, repo2.resolvedRoot,
		"resolvedRoot must resolve to the realpath regardless of caller alias")
}
