package spindle

import (
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

func gitInDir(t *testing.T, dir string, args ...string) string {
	t.Helper()
	out, err := exec.Command("git", append([]string{"-C", dir}, args...)...).CombinedOutput()
	if err != nil {
		t.Fatalf("git %v: %v\n%s", args, err, out)
	}
	return strings.TrimSpace(string(out))
}

func loadPipelineAt(t *testing.T, source string) (names []string) {
	t.Helper()
	sha := gitInDir(t, source, "rev-parse", "HEAD")
	checkout := filepath.Join(t.TempDir(), "checkout")
	raw, commit, err := (&Spindle{}).loadPipeline(context.Background(), source, checkout, sha)
	if err != nil {
		t.Fatal(err)
	}
	if commit != sha {
		t.Errorf("commit = %s, want %s", commit, sha)
	}
	for _, w := range raw {
		if strings.Contains(string(w.Contents), "stolen") {
			t.Errorf("%s holds a file from outside the repo", w.Name)
		}
		names = append(names, w.Name)
	}
	return names
}

func newSourceRepo(t *testing.T, format string) string {
	t.Helper()
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not available")
	}
	dir := filepath.Join(t.TempDir(), "source")
	gitInDir(t, filepath.Dir(dir), "init", "-q", "-b", "main", "--object-format="+format, dir)
	gitInDir(t, dir, "config", "user.email", "ci@nel.pet")
	gitInDir(t, dir, "config", "user.name", "ci")
	return dir
}

func commitEverything(t *testing.T, dir string) {
	t.Helper()
	gitInDir(t, dir, "add", "-A")
	gitInDir(t, dir, "commit", "-q", "-m", "wip")
}

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

func TestLoadPipelineReadsRegularFilesOnly(t *testing.T) {
	outside := t.TempDir()
	writeTestFile(t, filepath.Join(outside, "secret.yaml"), "stolen\n")

	src := newSourceRepo(t, "sha1")
	writeTestFile(t, filepath.Join(src, ".tangled/workflows/ci.yaml"), "when: []\n")
	writeTestFile(t, filepath.Join(src, ".tangled/workflows/nested/deep.yaml"), "when: []\n")
	if err := os.Symlink(filepath.Join(outside, "secret.yaml"), filepath.Join(src, ".tangled/workflows/link.yaml")); err != nil {
		t.Fatal(err)
	}
	commitEverything(t, src)

	if got := loadPipelineAt(t, src); len(got) != 1 || got[0] != "ci.yaml" {
		t.Errorf("workflows = %v, want only the regular file ci.yaml", got)
	}
}

func TestLoadPipelineIgnoresSymlinkedWorkflowDirs(t *testing.T) {
	outside := t.TempDir()
	writeTestFile(t, filepath.Join(outside, "workflows/evil.yaml"), "stolen\n")
	writeTestFile(t, filepath.Join(outside, "evil.yaml"), "stolen\n")

	t.Run("tangled dir is a symlink", func(t *testing.T) {
		src := newSourceRepo(t, "sha1")
		writeTestFile(t, filepath.Join(src, "README.md"), "hi\n")
		if err := os.Symlink(outside, filepath.Join(src, ".tangled")); err != nil {
			t.Fatal(err)
		}
		commitEverything(t, src)
		if got := loadPipelineAt(t, src); len(got) != 0 {
			t.Errorf("workflows = %v, want none", got)
		}
	})
	t.Run("workflows dir is a symlink", func(t *testing.T) {
		src := newSourceRepo(t, "sha1")
		if err := os.MkdirAll(filepath.Join(src, ".tangled"), 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.Symlink(outside, filepath.Join(src, ".tangled/workflows")); err != nil {
			t.Fatal(err)
		}
		commitEverything(t, src)
		if got := loadPipelineAt(t, src); len(got) != 0 {
			t.Errorf("workflows = %v, want none", got)
		}
	})
}
