package cas_test

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

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

	"github.com/gruntwork-io/terragrunt/internal/cas"
	"github.com/gruntwork-io/terragrunt/internal/git"
	"github.com/gruntwork-io/terragrunt/internal/venv"
	"github.com/gruntwork-io/terragrunt/internal/vexec"
	"github.com/gruntwork-io/terragrunt/test/helpers"
	"github.com/gruntwork-io/terragrunt/test/helpers/logger"
)

func TestIntegration_CloneAndReuse(t *testing.T) {
	t.Parallel()

	l := logger.CreateLogger()
	repoURL := startTestServer(t)

	v := venv.OSVenv()

	t.Run("clone same repo twice uses store", func(t *testing.T) {
		t.Parallel()
		tempDir := helpers.TmpDirWOSymlinks(t)
		storePath := filepath.Join(tempDir, "store")

		// First clone
		firstClonePath := filepath.Join(tempDir, "first")
		cas1, err := cas.New(cas.WithStorePath(storePath))
		require.NoError(t, err)
		require.NoError(t, cas1.Clone(t.Context(), l, v, repoURL, cas.WithDir(firstClonePath),
			cas.WithDepth(-1)))

		// Get info about first clone
		firstReadme := filepath.Join(firstClonePath, "README.md")
		firstStat, err := os.Stat(firstReadme)
		require.NoError(t, err)

		// Second clone
		secondClonePath := filepath.Join(tempDir, "second")
		cas2, err := cas.New(cas.WithStorePath(storePath))
		require.NoError(t, err)
		require.NoError(t, cas2.Clone(t.Context(), l, v, repoURL, cas.WithDir(secondClonePath),
			cas.WithDepth(-1)))

		// Get info about second clone
		secondReadme := filepath.Join(secondClonePath, "README.md")
		secondStat, err := os.Stat(secondReadme)
		require.NoError(t, err)

		// Verify both files exist
		assert.FileExists(t, firstReadme)
		assert.FileExists(t, secondReadme)

		// Verify they're hard links using os.SameFile instead of comparing entire Stat_t
		assert.True(t, os.SameFile(firstStat, secondStat))
	})

	t.Run("clone with nonexistent branch fails gracefully", func(t *testing.T) {
		t.Parallel()
		tempDir := helpers.TmpDirWOSymlinks(t)

		c, err := cas.New(cas.WithStorePath(filepath.Join(tempDir, "store")))
		require.NoError(t, err)

		err = c.Clone(t.Context(), l, v, repoURL, cas.WithDir(filepath.Join(tempDir, "repo")),
			cas.WithBranch("nonexistent-branch"),
			cas.WithDepth(-1))
		require.Error(t, err)

		var wrappedErr *git.WrappedError
		require.ErrorAs(t, err, &wrappedErr)
		assert.ErrorIs(t, wrappedErr.Err, git.ErrNoMatchingReference)
	})

	t.Run("clone with invalid repository fails gracefully", func(t *testing.T) {
		t.Parallel()
		tempDir := helpers.TmpDirWOSymlinks(t)

		c, err := cas.New(cas.WithStorePath(filepath.Join(tempDir, "store")))
		require.NoError(t, err)

		err = c.Clone(t.Context(), l, v, "http://127.0.0.1:1/nonexistent-repo.git",
			cas.WithDir(filepath.Join(tempDir, "repo")),
			cas.WithDepth(-1))
		require.Error(t, err)
	})
}

func TestIntegration_TreeStorage(t *testing.T) {
	t.Parallel()

	ctx := t.Context()
	l := logger.CreateLogger()
	repoURL := startTestServer(t)

	v := venv.OSVenv()

	t.Run("stores tree objects", func(t *testing.T) {
		t.Parallel()
		tempDir := helpers.TmpDirWOSymlinks(t)
		storePath := filepath.Join(tempDir, "store")

		// First clone to populate store
		c, err := cas.New(cas.WithStorePath(storePath))
		require.NoError(t, err)
		require.NoError(t, c.Clone(ctx, l, v, repoURL, cas.WithDir(filepath.Join(tempDir, "repo")),
			cas.WithDepth(-1)))

		// Get the commit hash for HEAD
		g, err := git.NewGitRunner(vexec.NewOSExec())
		require.NoError(t, err)

		results, err := g.LsRemote(ctx, repoURL, "HEAD")
		require.NoError(t, err)
		require.NotEmpty(t, results)
		commitHash := results[0].Hash

		// Verify the tree object is stored in the trees namespace
		treeStore := cas.NewStore(filepath.Join(storePath, "trees"))

		require.NoError(t, err)
		assert.False(t, treeStore.NeedsWrite(v, commitHash), "Tree object should be stored")

		// Verify we can read the tree content
		content := cas.NewContent(treeStore)
		treeData, err := content.Read(v, commitHash)
		require.NoError(t, err)

		// Parse the tree data to confirm it's valid
		tree, err := git.ParseTree(treeData, "")
		require.NoError(t, err)
		assert.NotEmpty(t, tree.Entries(), "Tree should have entries")
	})
}
