package githttp

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

func TestPushCommitDetailsBatchDeduplicatesOIDs(t *testing.T) {
	if _, err := exec.LookPath("git"); err != nil {
		t.Skipf("git unavailable: %v", err)
	}
	repository := t.TempDir()
	runPushBatchGit(t, repository, "init", "-q")
	runPushBatchGit(t, repository, "config", "user.name", "Batch Test")
	runPushBatchGit(t, repository, "config", "user.email", "batch@example.invalid")
	if err := os.WriteFile(filepath.Join(repository, "README.md"), []byte("batch\n"), 0o600); err != nil {
		t.Fatalf("write README: %v", err)
	}
	runPushBatchGit(t, repository, "add", "README.md")
	runPushBatchGit(t, repository, "commit", "-qm", "batch commit")
	sha := runPushBatchGit(t, repository, "rev-parse", "HEAD")
	changes := []pushRefChange{
		{Ref: "refs/heads/one", After: sha, CommitSHAs: []string{sha}},
		{Ref: "refs/heads/two", After: sha, CommitSHAs: []string{sha}},
	}
	details, err := pushCommitDetailsBatch(t.Context(), repository, "owner/repo", "https://example.invalid", changes)
	if err != nil {
		t.Fatalf("pushCommitDetailsBatch: %v", err)
	}
	if len(details) != 1 || details[sha]["id"] != sha {
		t.Fatalf("details = %+v", details)
	}
	for _, change := range changes {
		commits, head := pushCommits(change, details)
		if len(commits) != 1 || head == nil {
			t.Fatalf("pushCommits(%s) = %+v, %+v", change.Ref, commits, head)
		}
	}
}

func TestPushCommitDetailsBatchRejectsOversizedNameStatus(t *testing.T) {
	const sha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	t.Setenv("PATH", writeOversizedPushLogGit(t, sha))
	_, err := pushCommitDetailsBatch(t.Context(), t.TempDir(), "owner/repo", "https://example.invalid", []pushRefChange{{
		Ref: "refs/heads/main", After: sha, CommitSHAs: []string{sha},
	}})
	if err == nil || !strings.Contains(err.Error(), "file limit") {
		t.Fatalf("pushCommitDetailsBatch oversized detail error = %v, want file limit", err)
	}
}

func TestBuildPushRefChangeDistinguishesNonAncestorFromGitFailure(t *testing.T) {
	gitDirectory := t.TempDir()
	gitPath := filepath.Join(gitDirectory, "git")
	t.Setenv("PATH", gitDirectory)
	writeGit := func(exitCode int) {
		t.Helper()
		if err := os.WriteFile(gitPath, []byte("#!/bin/sh\nexit "+fmt.Sprint(exitCode)+"\n"), 0o700); err != nil {
			t.Fatalf("write fake git: %v", err)
		}
	}
	const before = "1111111111111111111111111111111111111111"
	const after = "2222222222222222222222222222222222222222"
	writeGit(1)
	change, ok, err := buildPushRefChange(t.Context(), t.TempDir(), "refs/heads/main", before, after)
	if err != nil || !ok || !change.Forced {
		t.Fatalf("exit 1 classification = change:%+v ok:%v err:%v, want forced", change, ok, err)
	}
	writeGit(128)
	if _, _, err := buildPushRefChange(t.Context(), t.TempDir(), "refs/heads/main", before, after); err == nil {
		t.Fatal("git exit 128 was silently classified as forced")
	}
}

func runPushBatchGit(t *testing.T, directory string, arguments ...string) string {
	t.Helper()
	command := exec.Command("git", append([]string{"-C", directory}, arguments...)...)
	output, err := command.CombinedOutput()
	if err != nil {
		t.Fatalf("git %v: %v: %s", arguments, err, output)
	}
	return strings.TrimSpace(string(output))
}

func writeOversizedPushLogGit(t *testing.T, sha string) string {
	t.Helper()
	directory := t.TempDir()
	var output strings.Builder
	output.WriteByte(0x1e)
	fmt.Fprintf(&output, "%s\x00tree\x00author\x00author@example.invalid\x00committer\x00committer@example.invalid\x002026-08-15T00:00:00Z\x00oversized\n", sha)
	for index := 0; index <= maxPushCommitDetailFiles; index++ {
		fmt.Fprintf(&output, "A\tfile-%04d\n", index)
	}
	outputPath := filepath.Join(directory, "output")
	if err := os.WriteFile(outputPath, []byte(output.String()), 0o600); err != nil {
		t.Fatalf("write oversized git output: %v", err)
	}
	script := "#!/bin/sh\nexec /bin/cat " + fmt.Sprintf("%q", outputPath) + "\n"
	if err := os.WriteFile(filepath.Join(directory, "git"), []byte(script), 0o700); err != nil {
		t.Fatalf("write oversized fake git: %v", err)
	}
	return directory
}
