package engine

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

	"bdev/internal/config"
)

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

	ctx := setupPullFixture(t)
	result, err := Pull(ctx, PullOpts{KeepLocalOnly: true})
	if err != nil {
		t.Fatalf("Pull: %v", err)
	}

	if !contains(result.Applied, "foo.txt") {
		t.Fatalf("expected foo.txt to be applied, got %#v", result.Applied)
	}
	if !contains(result.LocalOnly, "orphan.txt") {
		t.Fatalf("expected orphan.txt in local-only list, got %#v", result.LocalOnly)
	}
	if contains(result.Reverted, "orphan.txt") {
		t.Fatalf("orphan.txt should not be reverted when KeepLocalOnly=true")
	}

	fooContent := mustRead(t, filepath.Join(ctx.ChromiumDir, "foo.txt"))
	if strings.TrimSpace(fooContent) != "repo-version" {
		t.Fatalf("unexpected foo.txt content: %q", fooContent)
	}

	orphanContent := mustRead(t, filepath.Join(ctx.ChromiumDir, "orphan.txt"))
	if strings.TrimSpace(orphanContent) != "local-only-change" {
		t.Fatalf("orphan.txt should be preserved, got %q", orphanContent)
	}
}

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

	ctx := setupPullFixture(t)
	result, err := Pull(ctx, PullOpts{KeepLocalOnly: false})
	if err != nil {
		t.Fatalf("Pull: %v", err)
	}

	if !contains(result.Reverted, "orphan.txt") {
		t.Fatalf("expected orphan.txt to be reverted, got %#v", result.Reverted)
	}
	if contains(result.LocalOnly, "orphan.txt") {
		t.Fatalf("orphan.txt should not be local-only when KeepLocalOnly=false")
	}

	orphanContent := mustRead(t, filepath.Join(ctx.ChromiumDir, "orphan.txt"))
	if strings.TrimSpace(orphanContent) != "orphan-base" {
		t.Fatalf("orphan.txt should be reset to base, got %q", orphanContent)
	}
}

func setupPullFixture(t *testing.T) *config.Context {
	t.Helper()

	root := t.TempDir()
	chromiumDir := filepath.Join(root, "chromium")
	patchesRepo := filepath.Join(root, "patches")

	if err := os.MkdirAll(chromiumDir, 0o755); err != nil {
		t.Fatalf("mkdir chromium: %v", err)
	}
	if err := os.MkdirAll(patchesRepo, 0o755); err != nil {
		t.Fatalf("mkdir patches: %v", err)
	}

	initRepo(t, chromiumDir)
	writeFile(t, filepath.Join(chromiumDir, "foo.txt"), "base\n")
	writeFile(t, filepath.Join(chromiumDir, "orphan.txt"), "orphan-base\n")
	runGit(t, chromiumDir, "add", "foo.txt", "orphan.txt")
	runGit(t, chromiumDir, "commit", "-m", "base")
	baseCommit := strings.TrimSpace(runGit(t, chromiumDir, "rev-parse", "HEAD"))

	writeFile(t, filepath.Join(chromiumDir, "foo.txt"), "repo-version\n")
	patchDiff := runGit(t, chromiumDir, "diff", "--full-index", baseCommit, "--", "foo.txt")
	if strings.TrimSpace(patchDiff) == "" {
		t.Fatalf("expected patch diff for foo.txt")
	}
	runGit(t, chromiumDir, "checkout", baseCommit, "--", "foo.txt")

	writeFile(t, filepath.Join(chromiumDir, "orphan.txt"), "local-only-change\n")

	initRepo(t, patchesRepo)
	writeFile(t, filepath.Join(patchesRepo, "BASE_COMMIT"), baseCommit+"\n")
	writeFile(t, filepath.Join(patchesRepo, "chromium_patches", "foo.txt"), patchDiff)
	runGit(t, patchesRepo, "add", ".")
	runGit(t, patchesRepo, "commit", "-m", "seed patch repo")

	return &config.Context{
		Config:      &config.Config{Name: "test-checkout", PatchesRepo: patchesRepo},
		State:       &config.State{},
		ChromiumDir: chromiumDir,
		BrosDir:     filepath.Join(chromiumDir, ".bros"),
		PatchesRepo: patchesRepo,
		PatchesDir:  filepath.Join(patchesRepo, "chromium_patches"),
		BaseCommit:  baseCommit,
	}
}

func initRepo(t *testing.T, dir string) {
	t.Helper()
	runGit(t, dir, "init")
	runGit(t, dir, "config", "user.email", "bdev-test@example.com")
	runGit(t, dir, "config", "user.name", "bdev test")
}

func runGit(t *testing.T, dir string, args ...string) string {
	t.Helper()
	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %v failed: %v\n%s", args, err, string(out))
	}
	return string(out)
}

func contains(items []string, target string) bool {
	for _, item := range items {
		if item == target {
			return true
		}
	}
	return false
}

func writeFile(t *testing.T, path, content string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatalf("mkdir %s: %v", path, err)
	}
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatalf("write %s: %v", path, err)
	}
}

func mustRead(t *testing.T, path string) string {
	t.Helper()
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read %s: %v", path, err)
	}
	return string(data)
}
