package git_test

import (
	"context"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"testing"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/config"
	gitcontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/git"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/events"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/git"
)

type mockRunner struct {
	handler func(ctx context.Context, dir string, args []string) (string, string, error)
}

func (m *mockRunner) Run(ctx context.Context, dir string, args ...string) (string, string, error) {
	if m.handler == nil {
		return "", "", nil
	}
	return m.handler(ctx, dir, args)
}

func testConfig(workspace string) config.Config {
	return config.Config{
		Environment: "local",
		Git:         config.GitConfig{WorkspacePath: workspace},
		Bitbucket: config.BitbucketConfig{
			BaseURL:  "https://bit.admedia.com",
			Username: "user@example.com",
			Token:    "token",
		},
	}
}

func TestProviderFirstSync(t *testing.T) {
	workspace := t.TempDir()
	cfg := testConfig(workspace)
	workDir, err := git.WorkspaceDir(cfg, "ad/example-repo")
	if err != nil {
		t.Fatalf("WorkspaceDir: %v", err)
	}

	runner := &mockRunner{handler: func(_ context.Context, dir string, args []string) (string, string, error) {
		switch args[0] {
		case "clone":
			if err := os.MkdirAll(filepath.Join(workDir, ".git"), 0o755); err != nil {
				return "", "", err
			}
			return "", "", nil
		case "rev-parse":
			return "abc123def456\n", "", nil
		case "ls-files":
			return "main.go\nREADME.md\n", "", nil
		case "log":
			return commitLogFixture("aaa111", "", "Alice", "alice@example.com", "2026-06-01T10:00:00+00:00", "Alice", "alice@example.com", "2026-06-01T10:00:00+00:00", "init") +
				commitLogFixture("bbb222", "aaa111", "Bob", "bob@example.com", "2026-06-02T10:00:00+00:00", "Bob", "bob@example.com", "2026-06-02T10:00:00+00:00", "second"), "", nil
		default:
			t.Fatalf("unexpected git args: %v (dir=%q)", args, dir)
			return "", "", nil
		}
	}}

	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	provider := git.NewProvider(cfg, runner, logger)

	result, err := provider.SyncRepository(context.Background(), "ad/example-repo", "main")
	if err != nil {
		t.Fatalf("SyncRepository: %v", err)
	}
	if !result.IsFirstSync {
		t.Fatal("expected first sync")
	}
	if result.Snapshot.CommitSHA != "abc123def456" {
		t.Fatalf("commit sha: got %q", result.Snapshot.CommitSHA)
	}
	if len(result.FileChanges) != 2 {
		t.Fatalf("file changes: got %d, want 2", len(result.FileChanges))
	}
	if result.FileChanges[0].ChangeType != events.ChangeTypeAdded {
		t.Fatalf("expected added files on first sync")
	}
	if len(result.CommitChanges) != 2 {
		t.Fatalf("commit changes: got %d, want 2", len(result.CommitChanges))
	}
	if result.CommitChanges[0].CommitSHA != "aaa111" {
		t.Fatalf("first commit: got %q", result.CommitChanges[0].CommitSHA)
	}
	if result.CommitChanges[1].ParentSHA != "aaa111" {
		t.Fatalf("second commit parent: got %q", result.CommitChanges[1].ParentSHA)
	}
}

func TestProviderIncrementalSync(t *testing.T) {
	workspace := t.TempDir()
	cfg := testConfig(workspace)
	workDir, err := git.WorkspaceDir(cfg, "ad/example-repo")
	if err != nil {
		t.Fatalf("WorkspaceDir: %v", err)
	}
	if err := os.MkdirAll(filepath.Join(workDir, ".git"), 0o755); err != nil {
		t.Fatalf("mkdir .git: %v", err)
	}

	runner := &mockRunner{}
	headCalls := 0
	runner.handler = func(_ context.Context, dir string, args []string) (string, string, error) {
		switch args[0] {
		case "rev-parse":
			headCalls++
			if headCalls == 1 {
				return "oldsha1111111\n", "", nil
			}
			return "newsha2222222\n", "", nil
		case "fetch", "checkout":
			return "", "", nil
		case "diff":
			return "M\tmain.go\nA\tnew.go\n", "", nil
		case "log":
			if len(args) < 2 || args[1] != "--reverse" {
				t.Fatalf("expected --reverse in log args: %v", args)
			}
			return commitLogFixture("newsha2222222", "oldsha1111111", "Alice", "alice@example.com", "2026-06-01T10:00:00+00:00", "Alice", "alice@example.com", "2026-06-01T10:00:00+00:00", "incremental"), "", nil
		default:
			t.Fatalf("unexpected git args: %v (dir=%q)", args, dir)
			return "", "", nil
		}
	}

	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	provider := git.NewProvider(cfg, runner, logger)

	result, err := provider.SyncRepository(context.Background(), "ad/example-repo", "main")
	if err != nil {
		t.Fatalf("SyncRepository: %v", err)
	}
	if result.IsFirstSync {
		t.Fatal("expected incremental sync")
	}
	if len(result.FileChanges) != 2 {
		t.Fatalf("file changes: got %d, want 2", len(result.FileChanges))
	}
	if len(result.CommitChanges) != 1 {
		t.Fatalf("commit changes: got %d, want 1", len(result.CommitChanges))
	}
	if result.Snapshot.Status != gitcontract.SnapshotStatusReady {
		t.Fatalf("status: got %q", result.Snapshot.Status)
	}
}

func TestProviderInvalidRepoID(t *testing.T) {
	cfg := testConfig(t.TempDir())
	provider := git.NewProvider(cfg, &mockRunner{}, slog.New(slog.NewTextHandler(io.Discard, nil)))

	_, err := provider.SyncRepository(context.Background(), "../evil", "main")
	if err == nil {
		t.Fatal("expected error for invalid repo_id")
	}
}

func TestProviderMissingRef(t *testing.T) {
	cfg := testConfig(t.TempDir())
	provider := git.NewProvider(cfg, &mockRunner{}, slog.New(slog.NewTextHandler(io.Discard, nil)))

	_, err := provider.SyncRepository(context.Background(), "ad/example-repo", "")
	if err == nil {
		t.Fatal("expected error for missing ref")
	}
}
