package kb

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/agsbridge"
	"github.com/tidbcloud/AgentTeam/internal/agsbridge/wiki"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	reposstore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

func TestAGSHTTPRepositoryStoreChecksRepositoryExistenceWithoutMutation(t *testing.T) {
	var requests []string
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		require.Equal(t, http.MethodGet, r.Method)
		if r.URL.Path == "/api/v3/repos/wsp_1/runbooks" {
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
				"id":        84,
				"name":      "runbooks",
				"full_name": "wsp_1/runbooks",
			}))
			return
		}
		w.WriteHeader(http.StatusNotFound)
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token"}

	exists, err := store.RepositoryExists(context.Background(), repos.RepositoryIdentity{Owner: "wsp_1", Name: "runbooks"})
	require.NoError(t, err)
	require.True(t, exists)
	exists, err = store.RepositoryExists(context.Background(), repos.RepositoryIdentity{Owner: "wsp_1", Name: "runbooks-1"})
	require.NoError(t, err)
	require.False(t, exists)
	require.Equal(t, []string{
		"GET /api/v3/repos/wsp_1/runbooks",
		"GET /api/v3/repos/wsp_1/runbooks-1",
	}, requests)
}

func TestAGSHTTPRepositoryStoreEnsuresRepositoryDirectly(t *testing.T) {
	var requests []string
	grantor := &fakeResourceRepositoryGrantor{}
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/wsp_1/runbooks":
			w.WriteHeader(http.StatusNotFound)
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/orgs/wsp_1":
			w.WriteHeader(http.StatusOK)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"login": "wsp_1"}))
		case r.Method == http.MethodPost && r.URL.Path == "/api/v3/orgs/wsp_1/repos":
			var body map[string]any
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, "runbooks", body["name"])
			require.Equal(t, "kb_source", body["kind"])
			require.Equal(t, "file_repository", body["source_type"])
			require.Equal(t, "private", body["visibility"])
			require.Equal(t, "main", body["default_branch"])
			w.WriteHeader(http.StatusCreated)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
				"id":             84,
				"name":           "runbooks",
				"full_name":      "wsp_1/runbooks",
				"default_branch": "main",
			}))
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.Path)
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token", RepositoryGrantor: grantor}

	repository, err := store.EnsureRepository(context.Background(), repos.AGSRepositoryEnsureRequest{
		WorkspaceID:   "wsp_1",
		ResourceType:  "kb_source",
		ResourceID:    "src_1",
		RepoRole:      "primary",
		Owner:         "wsp_1",
		Name:          "runbooks",
		Kind:          "kb_source",
		SourceType:    "file_repository",
		Visibility:    "private",
		DefaultBranch: "main",
		Features:      repos.AGSRepositoryFeatures{Issues: true, Wiki: true},
		AutoInit:      true,
		Actor:         repos.ResourceRepositoryActor{Provider: "agentteam", Subject: "user-1", Login: "lloyd", Name: "Lloyd", Email: "lloyd@example.test"},
	})

	require.NoError(t, err)
	require.Equal(t, "wsp_1/runbooks", repository.FullName)
	require.Equal(t, "84", repository.ID)
	require.Equal(t, []repos.ResourceRepositoryCollaboratorGrant{{
		Repository: repos.RepositoryIdentity{Owner: "wsp_1", Name: "runbooks", FullName: "wsp_1/runbooks", ID: "84", DefaultBranch: "main"},
		Actor:      repos.ResourceRepositoryActor{Provider: "agentteam", Subject: "user-1", Login: "lloyd", Name: "Lloyd", Email: "lloyd@example.test"},
		Permission: "write",
	}}, grantor.grants)
	require.Equal(t, []string{
		"GET /api/v3/repos/wsp_1/runbooks",
		"GET /api/v3/orgs/wsp_1",
		"POST /api/v3/orgs/wsp_1/repos",
	}, requests)
}

func TestAGSHTTPRepositoryStoreResolvesDefaultBranchSnapshot(t *testing.T) {
	const commitSHA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/api/v3/repos/pingcap/agentteam/git/refs/heads/release/v1", r.URL.Path)
		require.Equal(t, "Bearer internal-token", r.Header.Get("X-AgentTeam-Verified-JWT"))
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"object": map[string]string{"sha": commitSHA}}))
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthHeader: "X-AgentTeam-Verified-JWT", AuthorizationHeader: "Bearer internal-token"}

	snapshot, err := store.ResolveRepositorySnapshot(context.Background(), repos.RepositoryIdentity{Owner: "pingcap", Name: "agentteam", DefaultBranch: "release/v1"})

	require.NoError(t, err)
	require.Equal(t, commitSHA, snapshot)
}

func TestAGSHTTPRepositoryStoreRejectsInvalidSnapshotSHA(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"object": map[string]string{"sha": "main"}}))
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer internal-token"}

	_, err := store.ResolveRepositorySnapshot(context.Background(), repos.RepositoryIdentity{Owner: "pingcap", Name: "agentteam", DefaultBranch: "main"})

	require.ErrorContains(t, err, "invalid response")
}

func TestAGSHTTPRepositoryStoreUpdatesRepositoryVisibility(t *testing.T) {
	var requests []string
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch {
		case r.Method == http.MethodPatch && r.URL.Path == "/api/v3/repos/wsp_1/runbooks":
			var body map[string]any
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, false, body["private"])
			w.WriteHeader(http.StatusOK)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
				"id":             84,
				"name":           "runbooks",
				"full_name":      "wsp_1/runbooks",
				"default_branch": "main",
			}))
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.Path)
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token"}

	repository, err := store.UpdateRepositoryVisibility(context.Background(), repos.RepositoryIdentity{Owner: "wsp_1", Name: "runbooks"}, "public")

	require.NoError(t, err)
	require.Equal(t, repos.RepositoryIdentity{Owner: "wsp_1", Name: "runbooks", FullName: "wsp_1/runbooks", ID: "84", DefaultBranch: "main"}, repository)
	require.Equal(t, []string{"PATCH /api/v3/repos/wsp_1/runbooks"}, requests)
}

func TestAGSHTTPRepositoryStoreRetriesRepositoryGrantAfterCreateConflict(t *testing.T) {
	var requests []string
	createAttempts := 0
	getRepoAttempts := 0
	grantor := &fakeResourceRepositoryGrantor{failures: []error{errors.New("grant unavailable")}}
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/wsp_1/runbooks":
			getRepoAttempts++
			if getRepoAttempts == 1 {
				w.WriteHeader(http.StatusNotFound)
				return
			}
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
				"id":             84,
				"name":           "runbooks",
				"full_name":      "wsp_1/runbooks",
				"default_branch": "main",
			}))
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/orgs/wsp_1":
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"login": "wsp_1"}))
		case r.Method == http.MethodPost && r.URL.Path == "/api/v3/orgs/wsp_1/repos":
			createAttempts++
			if createAttempts > 1 {
				w.WriteHeader(http.StatusConflict)
				require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "repository already exists"}))
				return
			}
			w.WriteHeader(http.StatusCreated)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
				"id":             84,
				"name":           "runbooks",
				"full_name":      "wsp_1/runbooks",
				"default_branch": "main",
			}))
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.Path)
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token", RepositoryGrantor: grantor}
	request := repos.AGSRepositoryEnsureRequest{
		WorkspaceID:   "wsp_1",
		ResourceType:  "kb_source",
		ResourceID:    "src_1",
		RepoRole:      "primary",
		Owner:         "wsp_1",
		Name:          "runbooks",
		Kind:          "kb_source",
		SourceType:    "file_repository",
		Visibility:    "private",
		DefaultBranch: "main",
		Features:      repos.AGSRepositoryFeatures{Issues: true, Wiki: true},
		AutoInit:      true,
		Actor:         repos.ResourceRepositoryActor{Provider: "agentteam", Subject: "user-1", Login: "lloyd", Name: "Lloyd", Email: "lloyd@example.test"},
	}

	_, err := store.EnsureRepository(context.Background(), request)
	require.ErrorContains(t, err, "grant unavailable")

	repository, err := store.EnsureRepository(context.Background(), request)
	require.NoError(t, err)
	require.Equal(t, "wsp_1/runbooks", repository.FullName)
	require.Equal(t, "84", repository.ID)
	require.Equal(t, []string{
		"GET /api/v3/repos/wsp_1/runbooks",
		"GET /api/v3/orgs/wsp_1",
		"POST /api/v3/orgs/wsp_1/repos",
		"GET /api/v3/repos/wsp_1/runbooks",
	}, requests)
	require.Len(t, grantor.grants, 2)
	require.Equal(t, grantor.grants[0], grantor.grants[1])
}

// TestAGSHTTPRepositoryStoreCleansUpRepoWhenCreateResponseUndecodable verifies
// that when createRepo returns 2xx but the response body is not valid JSON,
// EnsureRepository returns a fallback identity (built from the known
// owner/name) alongside the decode error, so the caller can delete the
// orphan repo. The test asserts that DeleteRepository is called with the
// correct owner/name even though the AGS repo ID was never decoded.
func TestAGSHTTPRepositoryStoreCleansUpRepoWhenCreateResponseUndecodable(t *testing.T) {
	var requests []string
	var deleteRequests []string
	grantor := &fakeResourceRepositoryGrantor{}
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/wsp_1/runbooks":
			w.WriteHeader(http.StatusNotFound)
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/orgs/wsp_1":
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"login": "wsp_1"}))
		case r.Method == http.MethodPost && r.URL.Path == "/api/v3/orgs/wsp_1/repos":
			w.WriteHeader(http.StatusCreated)
			// Return invalid JSON body — the repo was created but the
			// response cannot be decoded.
			_, _ = w.Write([]byte("not-valid-json"))
		case r.Method == http.MethodDelete && strings.HasPrefix(r.URL.Path, "/api/v3/repos/wsp_1/runbooks"):
			deleteRequests = append(deleteRequests, r.Method+" "+r.URL.Path)
			w.WriteHeader(http.StatusNoContent)
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.Path)
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token", RepositoryGrantor: grantor}
	request := repos.AGSRepositoryEnsureRequest{
		WorkspaceID:   "wsp_1",
		ResourceType:  reposstore.ResourceTypeKBSource,
		ResourceID:    "src_1",
		RepoRole:      "primary",
		Owner:         "wsp_1",
		Name:          "runbooks",
		Kind:          "kb_source",
		SourceType:    "file_repository",
		Visibility:    "private",
		DefaultBranch: "main",
		Actor:         repos.ResourceRepositoryActor{Provider: "agentteam", Subject: "user-1", Login: "lloyd", Name: "Lloyd", Email: "lloyd@example.test"},
	}

	identity, err := store.EnsureRepository(context.Background(), request)
	require.Error(t, err)
	require.Contains(t, err.Error(), "decode created repo")

	// EnsureRepository must return a fallback identity with known owner/name
	// so the caller can clean up the orphan. ID will be empty since the
	// response was not decoded.
	require.Equal(t, "wsp_1", identity.Owner)
	require.Equal(t, "runbooks", identity.Name)
	require.Equal(t, "wsp_1/runbooks", identity.FullName)

	// The caller (simulated here) should be able to delete the orphan using
	// just owner/name.
	err = store.DeleteRepository(context.Background(), identity)
	require.NoError(t, err)
	require.Contains(t, deleteRequests, "DELETE /api/v3/repos/wsp_1/runbooks")
}

func TestAGSHTTPRepositoryStoreRejectsExistingRepositoryWhenRequested(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/wsp_1/runbooks":
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
				"id":             84,
				"name":           "runbooks",
				"full_name":      "wsp_1/runbooks",
				"default_branch": "main",
			}))
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.Path)
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token", RepositoryGrantor: &fakeResourceRepositoryGrantor{}}

	_, err := store.EnsureRepository(context.Background(), repos.AGSRepositoryEnsureRequest{
		WorkspaceID:       "wsp_1",
		ResourceType:      reposstore.ResourceTypeKBSource,
		ResourceID:        "src_1",
		RepoRole:          reposstore.RepoRolePrimary,
		Owner:             "wsp_1",
		Name:              "runbooks",
		RejectExisting:    true,
		NameConflictField: repos.RepositoryNameFieldName,
		SuggestedRepoName: "runbooks-2",
		DefaultBranch:     "main",
		Features:          repos.AGSRepositoryFeatures{Issues: true, Wiki: true},
	})

	require.ErrorIs(t, err, repos.ErrConflict)
	require.Equal(t, repos.RepositoryAlreadyExistsCode, repos.Code(err))
	details, ok := repos.Details(err).(repos.RepositoryNameConflictDetails)
	require.True(t, ok)
	require.Equal(t, "wsp_1/runbooks", details.RepoFullName)
	require.Equal(t, "runbooks-2", details.SuggestedRepoName)
	require.Equal(t, repos.RepositoryNameFieldName, details.Field)
}

func TestAGSHTTPRepositoryStoreRevokesRepositoryCollaborator(t *testing.T) {
	grantor := &fakeResourceRepositoryGrantor{}
	store := AGSHTTPRepositoryStore{AuthorizationHeader: "Bearer test-token", RepositoryGrantor: grantor}

	err := store.RevokeRepositoryCollaborator(context.Background(), repos.ResourceRepositoryCollaboratorRevoke{
		Repository: repos.RepositoryIdentity{FullName: "wsp_1/runbooks"},
		Actor:      repos.ResourceRepositoryActor{Provider: "agentteam", Subject: "user-1", Login: "lloyd", Name: "Lloyd"},
	})

	require.NoError(t, err)
	require.Equal(t, []repos.ResourceRepositoryCollaboratorRevoke{{
		Repository: repos.RepositoryIdentity{Owner: "wsp_1", Name: "runbooks", FullName: "wsp_1/runbooks"},
		Actor:      repos.ResourceRepositoryActor{Provider: "agentteam", Subject: "user-1", Login: "lloyd", Name: "Lloyd"},
	}}, grantor.revokes)
}

type fakeResourceRepositoryGrantor struct {
	grants   []repos.ResourceRepositoryCollaboratorGrant
	revokes  []repos.ResourceRepositoryCollaboratorRevoke
	failures []error
}

func (g *fakeResourceRepositoryGrantor) GrantRepositoryCollaborator(_ context.Context, grant repos.ResourceRepositoryCollaboratorGrant) (repos.ResourceRepositoryCollaboratorGrantResult, error) {
	g.grants = append(g.grants, grant)
	if len(g.failures) > 0 {
		err := g.failures[0]
		g.failures = g.failures[1:]
		return repos.ResourceRepositoryCollaboratorGrantResult{}, err
	}
	return repos.ResourceRepositoryCollaboratorGrantResult{Materialized: true}, nil
}

func (g *fakeResourceRepositoryGrantor) RevokeRepositoryCollaborator(_ context.Context, revoke repos.ResourceRepositoryCollaboratorRevoke) error {
	g.revokes = append(g.revokes, revoke)
	return nil
}

func TestAGSHTTPRepositoryStorePrunePagesPaginates(t *testing.T) {
	var listPages []string
	var deleted []string
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/wiki/pages"):
			require.Equal(t, codeWikiGeneratedLabel, r.URL.Query().Get("labels"))
			require.Empty(t, r.URL.Query().Get("path"))
			require.Equal(t, "100", r.URL.Query().Get("per_page"))
			page := r.URL.Query().Get("page")
			listPages = append(listPages, page)
			pageNumber, err := strconv.Atoi(page)
			require.NoError(t, err)
			rows := make([]map[string]string, 0, 100)
			switch pageNumber {
			case 1:
				rows = append(rows, map[string]string{"slug": "keep"})
				for i := 1; i < 100; i++ {
					rows = append(rows, map[string]string{"slug": fmt.Sprintf("old-%03d", i)})
				}
			case 2:
				rows = append(rows, map[string]string{"slug": "old-100"})
			default:
				require.Failf(t, "unexpected list page", "page=%d", pageNumber)
			}
			require.NoError(t, json.NewEncoder(w).Encode(rows))
		case r.Method == http.MethodDelete && strings.Contains(r.URL.Path, "/wiki/pages/"):
			deleted = append(deleted, r.URL.Path)
			w.WriteHeader(http.StatusNoContent)
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.String())
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler, AuthorizationHeader: "Bearer test-token"}

	err := store.PrunePages(context.Background(), wiki.KBWikiPrune{
		Repository: repos.RepositoryIdentity{FullName: "wsp_1/runbooks"},
		Labels:     []string{codeWikiGeneratedLabel},
		KeepSlugs:  []string{"keep"},
		Message:    "prune",
	})

	require.NoError(t, err)
	require.Equal(t, []string{"1", "2"}, listPages)
	require.Len(t, deleted, 100)
}

func TestAGSHTTPRepositoryStoreReconcilesThroughExtensionRoute(t *testing.T) {
	var requestPath string
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestPath = r.Method + " " + r.URL.Path
		require.Equal(t, "Bearer internal-token", r.Header.Get("X-AgentTeam-Verified-JWT"))
		require.True(t, agsbridge.SiteAdminFromContext(r.Context()))
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
			"repository_id":      84,
			"indexed_commit_sha": "abc123",
			"page_count":         7,
			"reconciled":         true,
		}))
	})
	store := AGSHTTPRepositoryStore{
		Handler:             handler,
		AuthHeader:          "X-AgentTeam-Verified-JWT",
		AuthorizationHeader: "Bearer internal-token",
	}

	result, err := store.Reconcile(context.Background(), repos.RepositoryIdentity{FullName: "pingcap/agentteam"})

	require.NoError(t, err)
	require.Equal(t, "POST /api/ext/v1/repos/pingcap/agentteam/wiki/reconcile", requestPath)
	require.Equal(t, "84", result.RepositoryID)
	require.Equal(t, "abc123", result.IndexedCommitSHA)
	require.Equal(t, 7, result.PageCount)
	require.True(t, result.Reconciled)
}

func TestAGSHTTPRepositoryStoreUsesConfiguredAuthHeaderAndSiteAdminContext(t *testing.T) {
	var sawRequest bool
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		sawRequest = true
		require.Equal(t, "Bearer internal-token", r.Header.Get("X-AgentTeam-Verified-JWT"))
		require.Empty(t, r.Header.Get("Authorization"))
		require.True(t, agsbridge.SiteAdminFromContext(r.Context()))
		require.NoError(t, json.NewEncoder(w).Encode([]map[string]string{}))
	})
	store := AGSHTTPRepositoryStore{
		Handler:             handler,
		AuthHeader:          "X-AgentTeam-Verified-JWT",
		AuthorizationHeader: "Bearer internal-token",
	}

	err := store.PrunePages(context.Background(), wiki.KBWikiPrune{
		Repository: repos.RepositoryIdentity{FullName: "pingcap/agentteam"},
		Labels:     []string{codeWikiGeneratedLabel},
		KeepSlugs:  []string{"overview"},
		Message:    "prune",
	})

	require.NoError(t, err)
	require.True(t, sawRequest)
}

func TestAGSHTTPRepositoryStorePutPageEnsuresLabelsBeforeBinding(t *testing.T) {
	var requests []string
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch {
		case r.Method == http.MethodPut && strings.HasSuffix(r.URL.Path, "/wiki/pages/overview"):
			w.WriteHeader(http.StatusOK)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"slug": "overview"}))
		case r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/labels"):
			require.Equal(t, "100", r.URL.Query().Get("per_page"))
			require.NoError(t, json.NewEncoder(w).Encode([]map[string]string{}))
		case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/labels"):
			var body map[string]string
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, codeWikiGeneratedLabel, body["name"])
			require.Equal(t, "0969da", body["color"])
			w.WriteHeader(http.StatusCreated)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"name": codeWikiGeneratedLabel}))
		case r.Method == http.MethodPut && strings.HasSuffix(r.URL.Path, "/wiki/pages/overview/labels"):
			var body map[string][]string
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, []string{codeWikiGeneratedLabel}, body["labels"])
			require.NoError(t, json.NewEncoder(w).Encode([]map[string]string{{"name": codeWikiGeneratedLabel}}))
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.String())
		}
	})
	store := AGSHTTPRepositoryStore{Handler: handler}

	err := store.PutPage(context.Background(), wiki.KBWikiPageWrite{
		Repository: repos.RepositoryIdentity{FullName: "pingcap/agentteam"},
		Page: wiki.GeneratedWikiPage{
			Slug:   "overview",
			Body:   "# Overview",
			Labels: []string{codeWikiGeneratedLabel},
		},
	})

	require.NoError(t, err)
	require.Equal(t, []string{
		"PUT /api/v3/repos/pingcap/agentteam/wiki/pages/overview",
		"GET /api/v3/repos/pingcap/agentteam/labels",
		"POST /api/v3/repos/pingcap/agentteam/labels",
		"PUT /api/v3/repos/pingcap/agentteam/wiki/pages/overview/labels",
	}, requests)
}

func TestEscapedWikiSlugKeepsNestedSlugAsSinglePathSegment(t *testing.T) {
	require.Equal(t, "service%2Frest", escapedWikiSlug("service/rest"))
}
