package service

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/pingcap/agent-git-service/internal/gitstore"
)

func TestGitHubImportClientPaginationAndAuthorization(t *testing.T) {
	var seenAuth []string
	var server *httptest.Server
	server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		seenAuth = append(seenAuth, r.Header.Get("Authorization"))
		switch {
		case r.URL.Path == "/repos/source/repo/labels" && r.URL.Query().Get("page") == "":
			if got := r.URL.Query().Get("per_page"); got != "100" {
				t.Fatalf("per_page = %q, want 100", got)
			}
			w.Header().Set("Link", `<`+server.URL+`/repos/source/repo/labels?page=2&per_page=100>; rel="next"`)
			_, _ = w.Write([]byte(`[{"id":1,"node_id":"L1","name":"bug","color":"d73a4a"}]`))
		case r.URL.Path == "/repos/source/repo/labels" && r.URL.Query().Get("page") == "2":
			if r.URL.Query().Get("page") != "2" {
				t.Fatalf("unexpected path/query: %s?%s", r.URL.Path, r.URL.RawQuery)
			}
			_, _ = w.Write([]byte(`[{"id":2,"node_id":"L2","name":"docs","color":"0075ca"}]`))
		default:
			t.Fatalf("unexpected request: %s?%s", r.URL.Path, r.URL.RawQuery)
		}
	}))
	defer server.Close()

	client := newGitHubImportClient(server.URL, "ghp_test")
	labels, err := client.labels(context.Background(), "source", "repo")
	if err != nil {
		t.Fatalf("labels: %v", err)
	}
	if len(labels) != 2 {
		t.Fatalf("labels length = %d, want 2", len(labels))
	}
	for _, header := range seenAuth {
		if header != "Bearer ghp_test" {
			t.Fatalf("authorization header = %q, want bearer token", header)
		}
	}
}

func TestGitHubImportClientPullRequestStacksPaginationAndCapability(t *testing.T) {
	var server *httptest.Server
	server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if got := r.Header.Get("X-GitHub-Api-Version"); got != githubStacksAPIVersion {
			t.Fatalf("Stack API version = %q, want %q", got, githubStacksAPIVersion)
		}
		switch r.URL.Query().Get("page") {
		case "":
			w.Header().Set("Link", `<`+server.URL+`/repos/source/repo/stacks?page=2&per_page=100>; rel="next"`)
			_, _ = w.Write([]byte(`[{"id":1,"number":1,"base":{"ref":"main"},"pull_requests":[{"number":10},{"number":11}]}]`))
		case "2":
			_, _ = w.Write([]byte(`[{"id":2,"number":2,"base":{"ref":"main"},"pull_requests":[{"number":12},{"number":13}]}]`))
		default:
			t.Fatalf("unexpected Stack page %q", r.URL.Query().Get("page"))
		}
	}))
	defer server.Close()

	stacks, err := newGitHubImportClient(server.URL, "token").pullRequestStacks(context.Background(), "source", "repo")
	if err != nil {
		t.Fatalf("pullRequestStacks: %v", err)
	}
	if len(stacks) != 2 || stacks[0].PullRequests[0].Number != 10 || stacks[1].PullRequests[1].Number != 13 {
		t.Fatalf("stacks = %#v", stacks)
	}

	disabled := httptest.NewServer(http.NotFoundHandler())
	defer disabled.Close()
	_, err = newGitHubImportClient(disabled.URL, "").pullRequestStacks(context.Background(), "source", "repo")
	if !errors.Is(err, errGitHubImportNotFound) {
		t.Fatalf("disabled Stack error = %v, want errGitHubImportNotFound", err)
	}
}

func TestResolveGitHubImportTokenRuntimeEnvAndFile(t *testing.T) {
	token, err := resolveGitHubImportToken(GitHubImportRuntimeOptions{SourceToken: " runtime-token "}, "env:IGNORED")
	if err != nil {
		t.Fatalf("runtime token: %v", err)
	}
	if token != "runtime-token" {
		t.Fatalf("runtime token = %q", token)
	}

	t.Setenv("AGS_IMPORT_TEST_TOKEN", " env-token ")
	token, err = resolveGitHubImportToken(GitHubImportRuntimeOptions{}, "env:AGS_IMPORT_TEST_TOKEN")
	if err != nil {
		t.Fatalf("env token: %v", err)
	}
	if token != "env-token" {
		t.Fatalf("env token = %q", token)
	}

	path := t.TempDir() + "/token"
	if err := os.WriteFile(path, []byte(" file-token\n"), 0o600); err != nil {
		t.Fatalf("write token file: %v", err)
	}
	token, err = resolveGitHubImportToken(GitHubImportRuntimeOptions{}, "file:"+path)
	if err != nil {
		t.Fatalf("file token: %v", err)
	}
	if token != "file-token" {
		t.Fatalf("file token = %q", token)
	}
}

func TestGitHubImportDoesNotPersistOneShotTokenInJobOptions(t *testing.T) {
	options := GitHubImportOptions{ImportIssues: true}
	encoded, err := marshalJSON(options)
	if err != nil {
		t.Fatalf("marshal options: %v", err)
	}
	if strings.Contains(encoded, "source_token") || strings.Contains(encoded, "ghp_") {
		t.Fatalf("options JSON should not contain raw token fields: %s", encoded)
	}
}

func TestGitRemoteURLWithTokenAndSanitizedOutput(t *testing.T) {
	remote := gitRemoteURLWithToken("https://github.com/source/repo.git", "secret-token")
	if !strings.Contains(remote, "x-access-token:secret-token@github.com") {
		t.Fatalf("remote URL did not embed token as expected: %s", remote)
	}
	sanitized := sanitizeTokenOutput("fatal: https://x-access-token:secret-token@github.com/source/repo.git", "secret-token")
	if strings.Contains(sanitized, "secret-token") {
		t.Fatalf("sanitized output leaked token: %s", sanitized)
	}
}

func TestMirrorBareRemoteSetsSourceDefaultBranch(t *testing.T) {
	ctx := context.Background()
	tmp := t.TempDir()
	source := filepath.Join(tmp, "source.git")
	worktree := filepath.Join(tmp, "source-worktree")
	targetRoot := filepath.Join(tmp, "target")

	runImportGit(t, tmp, "init", "--bare", "--initial-branch=master", source)
	runImportGit(t, tmp, "init", "--initial-branch=master", worktree)
	runImportGit(t, worktree, "config", "user.name", "Importer Test")
	runImportGit(t, worktree, "config", "user.email", "importer-test@example.com")
	if err := os.WriteFile(filepath.Join(worktree, "Home.md"), []byte("# Wiki\n"), 0o600); err != nil {
		t.Fatalf("write source wiki page: %v", err)
	}
	runImportGit(t, worktree, "add", "Home.md")
	runImportGit(t, worktree, "commit", "-m", "seed wiki")
	runImportGit(t, worktree, "remote", "add", "origin", source)
	runImportGit(t, worktree, "push", "origin", "master")

	store, err := gitstore.New(targetRoot)
	if err != nil {
		t.Fatalf("new git store: %v", err)
	}
	runner := &githubImportRunner{git: store}
	if err := runner.mirrorBareRemote(ctx, "target/wiki.wiki", source); err != nil {
		t.Fatalf("mirror bare remote: %v", err)
	}

	targetPath, err := store.GetRepoPath(ctx, "target/wiki.wiki")
	if err != nil {
		t.Fatalf("target repo path: %v", err)
	}
	if head := runImportGit(t, targetPath, "symbolic-ref", "HEAD"); head != "refs/heads/master" {
		t.Fatalf("target HEAD = %q, want refs/heads/master", head)
	}
	if sourceHead, targetHead := runImportGit(t, source, "rev-parse", "master"), runImportGit(t, targetPath, "rev-parse", "HEAD"); sourceHead != targetHead {
		t.Fatalf("target HEAD = %s, want source HEAD %s", targetHead, sourceHead)
	}
}

func runImportGit(t *testing.T, dir string, args ...string) string {
	t.Helper()
	cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
	}
	return strings.TrimSpace(string(out))
}
