package git

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestBuildDiffArgs(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name string
		opts DiffOptions
		want []string
	}{
		{
			name: "working tree vs HEAD",
			opts: DiffOptions{},
			want: []string{"diff", "--no-color"},
		},
		{
			name: "staged",
			opts: DiffOptions{Staged: true},
			want: []string{"diff", "--no-color", "--cached"},
		},
		{
			name: "single ref",
			opts: DiffOptions{Ref: "HEAD~1"},
			want: []string{"diff", "--no-color", "HEAD~1"},
		},
		{
			name: "ref range",
			opts: DiffOptions{Ref: "main", Against: "feature"},
			want: []string{"diff", "--no-color", "main..feature"},
		},
		{
			name: "context lines + paths",
			opts: DiffOptions{ContextLines: 5, Paths: []string{"a.txt", "dir/b.go"}},
			want: []string{"diff", "--no-color", "-U5", "--", "a.txt", "dir/b.go"},
		},
		{
			name: "staged with ref range and paths",
			opts: DiffOptions{Staged: true, Ref: "main", Against: "dev", Paths: []string{"x"}},
			want: []string{"diff", "--no-color", "--cached", "main..dev", "--", "x"},
		},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			require.Equal(t, tc.want, buildDiffArgs(tc.opts))
		})
	}
}

func TestDiff_WorkingTreeAndStaged(t *testing.T) {
	t.Parallel()
	gitAvailable(t)
	bare := makeBareRepo(t)
	r := newTestRunner(t)
	dst := filepath.Join(t.TempDir(), "wc")
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	require.NoError(t, r.Clone(ctx, dst, bare, "main", nil))

	// clean tree => empty diff
	res, err := r.Diff(ctx, dst, DiffOptions{})
	require.NoError(t, err)
	require.Empty(t, res.Diff)
	require.False(t, res.Truncated)

	// modify a tracked file => unified diff against working tree
	require.NoError(t, os.WriteFile(filepath.Join(dst, "README.md"), []byte("hello\nworld\n"), 0o644))
	res, err = r.Diff(ctx, dst, DiffOptions{})
	require.NoError(t, err)
	require.Contains(t, res.Diff, "diff --git")
	require.Contains(t, res.Diff, "+world")

	// stage it; working-tree diff now empty, --cached shows the change
	mustGit(t, dst, "add", "README.md")
	res, err = r.Diff(ctx, dst, DiffOptions{})
	require.NoError(t, err)
	require.Empty(t, res.Diff)

	res, err = r.Diff(ctx, dst, DiffOptions{Staged: true})
	require.NoError(t, err)
	require.Contains(t, res.Diff, "+world")
}

func TestDiff_PathsFilter(t *testing.T) {
	t.Parallel()
	gitAvailable(t)
	bare := makeBareRepo(t)
	r := newTestRunner(t)
	dst := filepath.Join(t.TempDir(), "wc")
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	require.NoError(t, r.Clone(ctx, dst, bare, "main", nil))

	require.NoError(t, os.WriteFile(filepath.Join(dst, "README.md"), []byte("changed\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(dst, "other.txt"), []byte("new\n"), 0o644))
	mustGit(t, dst, "add", "other.txt")

	// restrict to README only => other.txt not in output
	res, err := r.Diff(ctx, dst, DiffOptions{Paths: []string{"README.md"}})
	require.NoError(t, err)
	require.Contains(t, res.Diff, "README.md")
	require.False(t, strings.Contains(res.Diff, "other.txt"))
}

func TestDiff_ErrorPropagation(t *testing.T) {
	t.Parallel()
	gitAvailable(t)
	r := newTestRunner(t)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	// not a git repo => git diff fails, error propagates
	_, err := r.Diff(ctx, t.TempDir(), DiffOptions{})
	require.Error(t, err)
}
