package gitstore_test

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

	"github.com/pingcap/agent-git-service/internal/gitstore"
)

func TestListBranches_HappyPath(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "gitstore-listbranch-*")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	store, err := gitstore.New(tmpDir)
	if err != nil {
		t.Fatal(err)
	}
	ctx := context.Background()
	repoName := "user/list-branches"

	if err := initializeBareRepositoryFixture(store, ctx, repoName, "main", true); err != nil {
		t.Fatalf("Init failed: %v", err)
	}
	if _, err := store.WriteFile(ctx, repoName, "main", "file.txt", "init", []byte("hello\n")); err != nil {
		t.Fatalf("WriteFile failed: %v", err)
	}
	if err := createBranchFixture(t, store, ctx, repoName, "feature", "main"); err != nil {
		t.Fatalf("CreateBranch failed: %v", err)
	}

	branches, err := store.ListBranches(ctx, repoName)
	if err != nil {
		t.Fatalf("ListBranches failed: %v", err)
	}
	if len(branches) != 2 {
		t.Fatalf("expected 2 branches, got %d", len(branches))
	}

	names := map[string]bool{}
	for _, b := range branches {
		names[b.Name] = true
		if b.SHA == "" {
			t.Errorf("branch %q has empty SHA", b.Name)
		}
	}
	if !names["main"] || !names["feature"] {
		t.Errorf("expected branches main and feature, got %v", names)
	}
}

func TestListBranches_IteratorError(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("permission-based test not reliable on Windows")
	}
	if os.Getuid() == 0 {
		t.Skip("cannot restrict file permissions as root")
	}

	tmpDir, err := os.MkdirTemp("", "gitstore-listbranch-iterr-*")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	store, err := gitstore.New(tmpDir)
	if err != nil {
		t.Fatal(err)
	}
	ctx := context.Background()
	repoName := "user/iter-error"

	if err := initializeBareRepositoryFixture(store, ctx, repoName, "main", true); err != nil {
		t.Fatalf("Init failed: %v", err)
	}
	if _, err := store.WriteFile(ctx, repoName, "main", "file.txt", "init", []byte("hi\n")); err != nil {
		t.Fatalf("WriteFile failed: %v", err)
	}

	// Corrupt a loose ref so the branch iterator fails during iteration.
	// Place an unreadable file in refs/heads/ — go-git tries to read it and
	// returns a permission-denied error from ForEach/Next.
	repoDir, err := store.GetRepoPath(ctx, repoName)
	if err != nil {
		t.Fatalf("GetRepoPath failed: %v", err)
	}
	badRef := filepath.Join(repoDir, "refs", "heads", "corrupt")
	if err := os.WriteFile(badRef, []byte("dummy\n"), 0o644); err != nil {
		t.Fatalf("failed to create bad ref: %v", err)
	}
	if err := os.Chmod(badRef, 0o000); err != nil {
		t.Fatalf("failed to chmod bad ref: %v", err)
	}
	// Ensure cleanup can remove the file.
	t.Cleanup(func() { os.Chmod(badRef, 0o644) })

	_, err = store.ListBranches(ctx, repoName)
	if err == nil {
		t.Fatal("expected error from ListBranches when iterator encounters unreadable ref, got nil")
	}
	// The error surfaces during reference enumeration. The key assertion is
	// that ListBranches returns an error rather than silently yielding a
	// partial branch list (which was the pre-fix behavior for ForEach errors).
	if got := err.Error(); !contains(got, "permission denied") {
		t.Fatalf("expected permission-denied error, got: %v", err)
	}
}

func TestListBranches_NonexistentRepo(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "gitstore-listbranch-noexist-*")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	store, err := gitstore.New(tmpDir)
	if err != nil {
		t.Fatal(err)
	}

	_, err = store.ListBranches(context.Background(), "no/such-repo")
	if err == nil {
		t.Fatal("expected error for nonexistent repo, got nil")
	}
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && searchSubstring(s, substr)
}

func searchSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
