package gitx

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

	"go.bigb.es/sourcehut-compare/core"
)

// newFidelityRepo builds a two-commit bare repo whose base..head diff exercises
// (a) a rename WITH an edit, (b) a binary file change, and (c) a plain text
// edit. It returns reposRoot plus the base and head SHAs.
func newFidelityRepo(t *testing.T) (reposRoot, base, head string) {
	t.Helper()
	reposRoot = t.TempDir()
	work := t.TempDir()

	d1 := "2024-02-01T00:00:00Z"
	d2 := "2024-02-02T00:00:00Z"

	gitTest(t, work, d1, "init", "-b", "main")

	// Base commit.
	writeFile(t, work, "a.txt", []byte("line1\nline2\nline3\nline4\n"))
	writeFile(t, work, "old.txt", []byte("one\ntwo\nthree\nfour\nfive\nsix\n"))
	writeFile(t, work, "img.bin", []byte{0x00, 0x01, 0x02, 0x03, 0x00, 0xff})
	gitTest(t, work, d1, "add", "-A")
	gitTest(t, work, d1, "commit", "-m", "base")

	// Head commit: plain edit, rename+edit, binary change.
	writeFile(t, work, "a.txt", []byte("line1\nline2-edited\nline3\nline4\n"))
	gitTest(t, work, d2, "mv", "old.txt", "new.txt")
	writeFile(t, work, "new.txt", []byte("one\ntwo\nthree-edited\nfour\nfive\nsix\n"))
	writeFile(t, work, "img.bin", []byte{0xff, 0xfe, 0x00, 0x11, 0x22, 0x33})
	gitTest(t, work, d2, "add", "-A")
	gitTest(t, work, d2, "commit", "-m", "head: rename+edit, binary change, plain edit")

	if err := os.MkdirAll(filepath.Join(reposRoot, "~"+fxOwner), 0o755); err != nil {
		t.Fatal(err)
	}
	bare := filepath.Join(reposRoot, "~"+fxOwner, "fidelity")
	gitTest(t, work, d2, "clone", "--bare", work, bare)

	base = fxRevIn(t, reposRoot, "fidelity", "main~1")
	head = fxRevIn(t, reposRoot, "fidelity", "main")
	return reposRoot, base, head
}

// fxRevIn is fxRev for an arbitrary repo name under fxOwner.
func fxRevIn(t *testing.T, reposRoot, name, rev string) string {
	t.Helper()
	bare := filepath.Join(reposRoot, "~"+fxOwner, name)
	cmd := exec.Command("git", "-C", bare, "rev-parse", rev)
	cmd.Env = append(os.Environ(),
		"GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null", "LC_ALL=C")
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("fxRevIn %q: %v\n%s", rev, err, out)
	}
	return strings.TrimSpace(string(out))
}

// TestPatchFidelity is the compatibility gate for the browser-side
// parsePatchFiles(): go-git's generated patch must carry standard git headers
// for renames, binary files, and text hunks.
func TestPatchFidelity(t *testing.T) {
	reposRoot, base, head := newFidelityRepo(t)
	repo, err := Open(reposRoot, fxOwner, "fidelity")
	if err != nil {
		t.Fatalf("Open: %v", err)
	}

	spec := core.CompareSpec{Base: base, Head: head}
	patch, err := repo.Diff(ctx(), spec)
	if err != nil {
		t.Fatalf("Diff: %v", err)
	}
	text := patch.Text
	t.Logf("generated patch:\n%s", text)

	mustContain := []string{
		// plain edit
		"diff --git a/a.txt b/a.txt",
		"@@",
		"-line2",
		"+line2-edited",
		// rename + edit
		"diff --git a/old.txt b/new.txt",
		"rename from old.txt",
		"rename to new.txt",
		// binary change
		"img.bin",
		"Binary files",
	}
	for _, want := range mustContain {
		if !strings.Contains(text, want) {
			t.Errorf("patch missing %q\n---\n%s", want, text)
		}
	}

	// The rename must be classified as a single R change, not add+delete.
	files, err := repo.DiffStat(ctx(), spec)
	if err != nil {
		t.Fatalf("DiffStat: %v", err)
	}
	byPath := map[string]FileChange{}
	for _, f := range files {
		byPath[f.Path] = f
	}
	if r, ok := byPath["new.txt"]; !ok || r.Status != "R" || r.OldPath != "old.txt" {
		t.Errorf("rename entry = %+v (ok=%v), want R from old.txt", byPath["new.txt"], ok)
	}
	if r := byPath["new.txt"]; r.Additions == 0 && r.Deletions == 0 {
		t.Errorf("rename+edit should report line changes, got %+v", r)
	}
	if b, ok := byPath["img.bin"]; !ok || !b.Binary {
		t.Errorf("img.bin = %+v (ok=%v), want binary", byPath["img.bin"], ok)
	}
	if a, ok := byPath["a.txt"]; !ok || a.Status != "M" {
		t.Errorf("a.txt = %+v (ok=%v), want M", byPath["a.txt"], ok)
	}
}
