package parser_test

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

	contractevents "bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/contracts/events"
	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/service/parser"
)

func TestResolveCommitDiffIgnoresNonCodePaths(t *testing.T) {
	event := contractevents.NewCommitsChangedEvent("evt_1", "ad/example", "snap_1", "target_sha")
	event.ParentSHA = "base_sha"
	event.ChangedFiles = []string{".gitignore", "package-lock.json", "README.md"}

	_, err := parser.ResolveCommitDiff("/tmp/workspace", event)
	if !errors.Is(err, parser.ErrNoCodeFilesChanged) {
		t.Fatalf("expected ErrNoCodeFilesChanged, got %v", err)
	}
}

func TestResolveNonCodeCommitDiffFromChangedFiles(t *testing.T) {
	event := contractevents.NewCommitsChangedEvent("evt_1", "ad/example", "snap_1", "target_sha")
	event.ParentSHA = "base_sha"
	event.ChangedFiles = []string{".gitignore", "docs/readme.md", "main.go"}

	diff, err := parser.ResolveNonCodeCommitDiff("/tmp/workspace", event)
	if err != nil {
		t.Fatalf("ResolveNonCodeCommitDiff: %v", err)
	}
	if len(diff.ChangedFiles) != 2 {
		t.Fatalf("expected 2 non-code files, got %v", diff.ChangedFiles)
	}
}

func TestResolveCommitDiffFromChangedFiles(t *testing.T) {
	event := contractevents.NewCommitsChangedEvent("evt_1", "ad/example", "snap_1", "target_sha")
	event.ParentSHA = "base_sha"
	event.ChangedFiles = []string{"internal/main.go", "README.md", "pkg/util.ts", "lib/app.dart", "scripts/init.lua"}

	diff, err := parser.ResolveCommitDiff("/tmp/workspace", event)
	if err != nil {
		t.Fatalf("ResolveCommitDiff: %v", err)
	}

	if diff.BaseSHA != "base_sha" || diff.TargetSHA != "target_sha" {
		t.Fatalf("shas: base=%q target=%q", diff.BaseSHA, diff.TargetSHA)
	}
	if len(diff.ChangedFiles) != 4 {
		t.Fatalf("expected 4 code files, got %v", diff.ChangedFiles)
	}
}

func TestResolveCommitDiffRootCommitFromChangedFiles(t *testing.T) {
	event := contractevents.NewCommitsChangedEvent("evt_1", "ad/example", "snap_1", "target_sha")
	event.ChangedFiles = []string{"main.go", "README.md"}

	diff, err := parser.ResolveCommitDiff("/tmp/workspace", event)
	if err != nil {
		t.Fatalf("ResolveCommitDiff: %v", err)
	}
	if diff.BaseSHA != parser.GitEmptyTreeSHA {
		t.Fatalf("expected empty tree base sha for root commit, got %q", diff.BaseSHA)
	}
	if len(diff.ChangedFiles) != 1 || diff.ChangedFiles[0] != "main.go" {
		t.Fatalf("changed files: %v", diff.ChangedFiles)
	}
}

func TestResolveCommitDiffGitFallback(t *testing.T) {
	workspace := setupWorkspace(t, map[string]string{
		"main.go": "package main\nfunc main() {}\n",
	})

	repoDir := workspace + "/ad__example"
	initGitRepo(t, repoDir, []string{"main.go"}, "package main\nfunc main() {}\n", "package main\nfunc main() { println(\"hi\") }\n")

	event := contractevents.NewCommitsChangedEvent("evt_1", "ad/example", "snap_1", "HEAD")
	event.ParentSHA = "HEAD~1"

	diff, err := parser.ResolveCommitDiff(workspace, event)
	if err != nil {
		t.Fatalf("ResolveCommitDiff: %v", err)
	}
	if len(diff.ChangedFiles) != 1 || diff.ChangedFiles[0] != "main.go" {
		t.Fatalf("changed files: %v", diff.ChangedFiles)
	}
}

func initGitRepo(t *testing.T, repoDir string, files []string, firstContent, secondContent string) {
	t.Helper()

	runGit(t, repoDir, "init")
	runGit(t, repoDir, "config", "user.email", "test@example.com")
	runGit(t, repoDir, "config", "user.name", "Test User")
	for _, file := range files {
		if err := osWriteFile(filepath.Join(repoDir, file), []byte(firstContent), 0o644); err != nil {
			t.Fatalf("write %s: %v", file, err)
		}
		runGit(t, repoDir, "add", file)
	}
	runGit(t, repoDir, "commit", "-m", "initial")
	for _, file := range files {
		if err := osWriteFile(filepath.Join(repoDir, file), []byte(secondContent), 0o644); err != nil {
			t.Fatalf("write %s: %v", file, err)
		}
		runGit(t, repoDir, "add", file)
	}
	runGit(t, repoDir, "commit", "-m", "update")
}

func runGit(t *testing.T, dir string, args ...string) {
	t.Helper()

	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	if out, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("git %v: %v\n%s", args, err, out)
	}
}

func osWriteFile(path string, data []byte, perm os.FileMode) error {
	return os.WriteFile(path, data, perm)
}
