package server

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	agsconfig "github.com/pingcap/agent-git-service/config"
	agsserver "github.com/pingcap/agent-git-service/server"

	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/database"
	"github.com/tidbcloud/AgentTeam/internal/forwarder"
	"github.com/tidbcloud/AgentTeam/internal/gateway"
	"github.com/tidbcloud/AgentTeam/internal/router"
	repotokens "github.com/tidbcloud/AgentTeam/internal/service/repotokens"
	"github.com/tidbcloud/AgentTeam/internal/store/content"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	ratstore "github.com/tidbcloud/AgentTeam/internal/store/repotokens"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

type workspaceAGSWiringSessions struct{}

func (workspaceAGSWiringSessions) AuthenticateSession(context.Context, string) (identity.AuthenticatedUser, error) {
	return identity.AuthenticatedUser{
		User:    identity.User{ID: "usr_workspace_ags", Email: "alice@example.test", Name: "alice", DisplayName: "Alice"},
		Session: model.UserSession{ID: "sess_workspace_ags", UserID: "usr_workspace_ags"},
	}, nil
}

type workspaceAGSWiringMemberships struct{}

func (workspaceAGSWiringMemberships) GetWorkspaceForUser(context.Context, string, string) (workspace.Summary, error) {
	return workspace.Summary{WorkspaceID: "wsp_1", WorkspaceRole: "member"}, nil
}

type workspaceAGSWiringIdentityResolver struct{}

func (workspaceAGSWiringIdentityResolver) ResolveWorkspaceIdentity(_ context.Context, request gateway.WorkspaceIdentityRequest) (gateway.RouteIdentity, error) {
	return gateway.RouteIdentity{WorkspaceID: request.WorkspaceID}, nil
}

type workspaceAGSWiringPlacementResolver struct{}

func (workspaceAGSWiringPlacementResolver) ResolveWorkspaceRouteTarget(_ context.Context, workspaceID string) (workspace.WorkspaceRouteTarget, error) {
	return workspace.WorkspaceRouteTarget{WorkspaceID: workspaceID, WorkspaceGroupID: "group_1", PlacementState: workspace.PlacementStateActive}, nil
}

func TestProfileWorkspaceSessionReachesConfiguredEmbeddedAGSIdentity(t *testing.T) {
	requireCGO(t)
	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress:   ":0",
		PublicBaseURL: "https://agentteam.example.test",
		Auth: config.ResourceRuntimeAuthConfig{
			Enabled: true, Header: config.DefaultTrustedJWTHeader,
			TrustedJWTClaims: true, TrustedJWTClaimsProtectedListener: true,
		},
		Database: config.ResourceDatabaseConfig{DSN: meshxDatabaseDSN(t)},
		AGS: config.AGSConfig{
			DatabaseDSN: agsFileDatabaseDSN(t),
			GitRepoDir:  t.TempDir(),
			BaseURL:     "https://agentteam.example.test",
		},
	})
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) })
	require.NoError(t, srv.ConfigureWorkspaceAGSSeam("group_1", &seamWorkspaceChecker{}))

	handler := gatewayToProfileWorkspaceAGS(t, srv.Handler())
	sessionToken := base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{9}, 32))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/user/repos", nil)
	request.Header.Set("Authorization", "Bearer "+sessionToken)
	recorder := httptest.NewRecorder()

	handler.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	require.JSONEq(t, `[]`, recorder.Body.String())
}

func TestProfileAllSkillAndBranchReadsReachBoundRepository(t *testing.T) {
	requireCGO(t)
	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress:   ":0",
		PublicBaseURL: "https://agentteam.example.test",
		Auth: config.ResourceRuntimeAuthConfig{
			Enabled: true, Header: config.DefaultTrustedJWTHeader,
			TrustedJWTClaims: true, TrustedJWTClaimsProtectedListener: true,
		},
		Database: config.ResourceDatabaseConfig{DSN: meshxDatabaseDSN(t)},
		AGS: config.AGSConfig{
			DatabaseDSN: agsFileDatabaseDSN(t),
			GitRepoDir:  t.TempDir(),
			BaseURL:     "https://agentteam.example.test",
		},
	})
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) })

	trusted := "Bearer " + jwt(map[string]any{
		"sub": "usr_workspace_ags", "preferred_username": "alice", "name": "Alice", "email": "alice@example.test",
	})
	created := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPost, "/api/v3/user/repos", trusted, map[string]any{
		"name": "skills", "default_branch": "main", "auto_init": true, "private": true,
	})
	require.Equal(t, http.StatusCreated, created.Code, created.Body.String())
	var repo struct {
		ID json.Number `json:"id"`
	}
	require.NoError(t, json.Unmarshal(created.Body.Bytes(), &repo))
	for path, content := range map[string]string{
		"skills/skills/SKILL.md":      "# Skill\n",
		"skills/skills/reference.txt": "reference\n",
	} {
		seeded := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPut, "/api/v3/repos/alice/skills/contents/"+path, trusted, map[string]any{
			"message": "seed " + path, "content": base64.StdEncoding.EncodeToString([]byte(content)),
		})
		require.Equal(t, http.StatusCreated, seeded.Code, seeded.Body.String())
	}

	now := time.Now().UTC()
	_, err = repos.New(srv.meshxDB).Upsert(context.Background(), repos.ResourceRepository{
		ID: "rr_profile_all_skills", WorkspaceID: "wsp_1", ResourceType: repos.ResourceTypeSkillRepository,
		ResourceID: "sk_1", RepoRole: repos.RepoRolePrimary, OwnerKind: "workspace",
		RepoOwner: "alice", RepoName: "skills", RepoFullName: "alice/skills", AGSRepoID: repo.ID.String(),
		DefaultRef: "main", Visibility: "private", UserVisible: true, CreatedBy: "usr_workspace_ags", UpdatedBy: "usr_workspace_ags",
		CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	require.NoError(t, srv.ConfigureStandaloneWorkspaceAGSSeam(workspaceAGSWiringPlacementResolver{}))

	handler := profileAllAGSHandler(srv.Handler())
	sessionToken := base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{9}, 32))
	for _, path := range []string{
		"/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/skills",
		"/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/skills/readme?ref=main",
		"/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/skills/contents/skills/skills/SKILL.md?ref=main",
		"/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/skills/branches?page=1&per_page=1",
	} {
		response := serveWorkspaceQualifiedAGS(handler, http.MethodGet, path, "Bearer "+sessionToken)
		require.Equal(t, http.StatusOK, response.Code, "%s: %s", path, response.Body.String())
	}
}

func TestConfiguredWorkspaceAGSSeamRejectsNativeAutomationWrites(t *testing.T) {
	requireCGO(t)
	var agsCalls atomic.Int32
	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress:   ":0",
		PublicBaseURL: "https://agentteam.example.test",
		Auth: config.ResourceRuntimeAuthConfig{
			Enabled: true, Header: config.DefaultTrustedJWTHeader,
			TrustedJWTClaims: true, TrustedJWTClaimsProtectedListener: true,
		},
		Database:     config.ResourceDatabaseConfig{DSN: meshxDatabaseDSN(t)},
		AgentTeamCore:     config.AgentTeamCoreConfig{DatabaseDSN: "file:" + t.TempDir() + "/agentteam-core?mode=memory&cache=shared"},
		ContentStore: config.ContentStoreConfig{Enabled: true, FilesystemRoot: t.TempDir()},
		AGS: config.AGSConfig{
			DatabaseDSN: agsFileDatabaseDSN(t), GitRepoDir: t.TempDir(), BaseURL: "https://agentteam.example.test",
		},
	}, Options{
		DisableBackgroundWorkers: true,
		NewAGS: func(agsconfig.Config, ...agsserver.Option) (agsServer, error) {
			return fakeAGS{handler: http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
				agsCalls.Add(1)
				writer.WriteHeader(http.StatusNoContent)
			})}, nil
		},
	})
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) })
	srv.ready.Store(true)

	repositories := repos.New(srv.meshxDB)
	now := time.Now().UTC()
	for _, repository := range []string{"acme/native", "acme/legacy"} {
		resourceID := repos.AutomationRepositoryResourceID(repository)
		owner, name, _ := strings.Cut(repository, "/")
		_, err = repositories.Upsert(t.Context(), repos.ResourceRepository{
			ID:          repos.ResourceRepositoryBindingID("wsp_1", repos.ResourceTypeAutomationRepository, resourceID, repos.RepoRolePrimary),
			WorkspaceID: "wsp_1", ResourceType: repos.ResourceTypeAutomationRepository, ResourceID: resourceID,
			RepoRole: repos.RepoRolePrimary, OwnerKind: "automation_repository",
			RepoOwner: owner, RepoName: name, RepoFullName: repository, CreatedBy: "usr_1", UpdatedBy: "usr_1",
			CreatedAt: now, UpdatedAt: now,
		})
		require.NoError(t, err)
	}
	_, err = content.New(srv.meshxDB).ActivateNativeAuthority(t.Context(), content.ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "acme/native", ExpectedGeneration: 0,
		Checkpoint: "automation-cutover", Actor: "usr_1", Now: now,
	})
	require.NoError(t, err)
	require.NoError(t, srv.ConfigureStandaloneWorkspaceAGSSeam(workspaceAGSWiringPlacementResolver{}))

	nativeWrite := httptest.NewRequest(http.MethodPut, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/native/contents/.agentteam/automation.yaml", nil)
	nativeRecorder := httptest.NewRecorder()
	srv.Handler().ServeHTTP(nativeRecorder, nativeWrite)
	require.Equal(t, http.StatusConflict, nativeRecorder.Code, nativeRecorder.Body.String())
	require.Zero(t, agsCalls.Load())

	legacyWrite := httptest.NewRequest(http.MethodPut, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/legacy/contents/.agentteam/automation.yaml", nil)
	legacyRecorder := httptest.NewRecorder()
	srv.Handler().ServeHTTP(legacyRecorder, legacyWrite)
	require.Equal(t, http.StatusNoContent, legacyRecorder.Code, legacyRecorder.Body.String())
	require.EqualValues(t, 1, agsCalls.Load())

	nativeRead := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/native", nil)
	nativeReadRecorder := httptest.NewRecorder()
	srv.Handler().ServeHTTP(nativeReadRecorder, nativeRead)
	require.Equal(t, http.StatusNoContent, nativeReadRecorder.Code, nativeReadRecorder.Body.String())
	require.EqualValues(t, 2, agsCalls.Load())
}

func TestConfigOffRestartRejectsNativeAutomationBeforeAnyAGSRoute(t *testing.T) {
	requireCGO(t)
	meshxDSN := "file:" + t.TempDir() + "/meshx.db?cache=shared&_busy_timeout=5000&_journal_mode=WAL&_txlock=immediate"
	bootstrapDB, err := database.Open(database.Config{DSN: meshxDSN})
	require.NoError(t, err)
	repositories := repos.New(bootstrapDB)
	seedWorkspaceAGSAutomationBinding(t, repositories, "wsp_1", "acme/native")
	_, err = content.New(bootstrapDB).ActivateNativeAuthority(t.Context(), content.ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "acme/native", ExpectedGeneration: 0,
		Checkpoint: "automation-cutover", Actor: "usr_1", Now: time.Now().UTC(),
	})
	require.NoError(t, err)
	require.NoError(t, database.Close(bootstrapDB))

	var agsCalls atomic.Int32
	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress:   ":0",
		PublicBaseURL: "https://agentteam.example.test",
		Auth: config.ResourceRuntimeAuthConfig{
			Enabled: true, Header: config.DefaultTrustedJWTHeader,
			TrustedJWTClaims: true, TrustedJWTClaimsProtectedListener: true,
		},
		Database:     config.ResourceDatabaseConfig{DSN: meshxDSN},
		AgentTeamCore:     config.AgentTeamCoreConfig{DatabaseDSN: "file:" + t.TempDir() + "/agentteam-core?mode=memory&cache=shared"},
		ContentStore: config.ContentStoreConfig{Enabled: false},
		AGS: config.AGSConfig{
			DatabaseDSN: agsFileDatabaseDSN(t), GitRepoDir: t.TempDir(), BaseURL: "https://agentteam.example.test",
		},
	}, Options{
		DisableBackgroundWorkers: true,
		NewAGS: func(agsconfig.Config, ...agsserver.Option) (agsServer, error) {
			return fakeAGS{handler: http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
				agsCalls.Add(1)
				writer.WriteHeader(http.StatusNoContent)
			})}, nil
		},
	})
	require.Nil(t, srv)
	require.ErrorContains(t, err, "cannot be disabled")
	require.Zero(t, agsCalls.Load(), "config-off restart must fail before any AGS route can be used")
}

func TestProfileWorkspaceRepositoryRawAndAvatarStayOnBoundOwner(t *testing.T) {
	requireCGO(t)
	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress:   ":0",
		PublicBaseURL: "https://agentteam.example.test",
		Auth: config.ResourceRuntimeAuthConfig{
			Enabled: true, Header: config.DefaultTrustedJWTHeader,
			TrustedJWTClaims: true, TrustedJWTClaimsProtectedListener: true,
		},
		Database: config.ResourceDatabaseConfig{DSN: meshxDatabaseDSN(t)},
		AGS: config.AGSConfig{
			DatabaseDSN: agsFileDatabaseDSN(t),
			GitRepoDir:  t.TempDir(),
			BaseURL:     "https://agentteam.example.test",
		},
	})
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) })

	trusted := "Bearer " + jwt(map[string]any{
		"sub": "usr_workspace_ags", "preferred_username": "alice", "name": "Alice", "email": "alice@example.test",
	})
	created := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPost, "/api/v3/user/repos", trusted, map[string]any{
		"name": "docs", "default_branch": "main", "auto_init": true, "private": true,
	})
	require.Equal(t, http.StatusCreated, created.Code, created.Body.String())
	var repo struct {
		ID       json.Number `json:"id"`
		FullName string      `json:"full_name"`
	}
	require.NoError(t, json.Unmarshal(created.Body.Bytes(), &repo))
	require.Equal(t, "alice/docs", repo.FullName)
	seedFile := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPut, "/api/v3/repos/alice/docs/contents/smoke.txt", trusted, map[string]any{
		"message": "seed raw content", "content": base64.StdEncoding.EncodeToString([]byte("workspace docs\n")),
	})
	require.Equal(t, http.StatusCreated, seedFile.Code, seedFile.Body.String())
	createdPublic := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPost, "/api/v3/user/repos", trusted, map[string]any{
		"name": "public-docs", "default_branch": "main", "auto_init": true,
	})
	require.Equal(t, http.StatusCreated, createdPublic.Code, createdPublic.Body.String())
	var publicRepo struct {
		ID json.Number `json:"id"`
	}
	require.NoError(t, json.Unmarshal(createdPublic.Body.Bytes(), &publicRepo))
	publicVisibility := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPatch, "/api/v3/repos/alice/public-docs", trusted, map[string]any{"private": false})
	require.Equal(t, http.StatusOK, publicVisibility.Code, publicVisibility.Body.String())
	publicFile := serveTrustedAGSJSON(t, srv.Handler(), http.MethodPut, "/api/v3/repos/alice/public-docs/contents/public.txt", trusted, map[string]any{
		"message": "seed public raw content", "content": base64.StdEncoding.EncodeToString([]byte("public workspace docs\n")),
	})
	require.Equal(t, http.StatusCreated, publicFile.Code, publicFile.Body.String())

	now := time.Now().UTC()
	_, err = repos.New(srv.meshxDB).Upsert(context.Background(), repos.ResourceRepository{
		ID: "rr_workspace_docs", WorkspaceID: "wsp_1", ResourceType: repos.ResourceTypeAutomationRepository,
		ResourceID: "repo_docs", RepoRole: repos.RepoRolePrimary, OwnerKind: "workspace",
		RepoOwner: "alice", RepoName: "docs", RepoFullName: "alice/docs", AGSRepoID: repo.ID.String(),
		DefaultRef: "main", Visibility: "private", UserVisible: true, CreatedBy: "usr_workspace_ags", UpdatedBy: "usr_workspace_ags",
		CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	_, err = repos.New(srv.meshxDB).Upsert(context.Background(), repos.ResourceRepository{
		ID: "rr_workspace_public_docs", WorkspaceID: "wsp_1", ResourceType: repos.ResourceTypeAutomationRepository,
		ResourceID: "repo_public_docs", RepoRole: repos.RepoRolePrimary, OwnerKind: "workspace",
		RepoOwner: "alice", RepoName: "public-docs", RepoFullName: "alice/public-docs", AGSRepoID: publicRepo.ID.String(),
		DefaultRef: "main", Visibility: "public", UserVisible: true, CreatedBy: "usr_workspace_ags", UpdatedBy: "usr_workspace_ags",
		CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	require.NoError(t, srv.ConfigureWorkspaceAGSSeam("group_1", &seamWorkspaceChecker{}))

	handler := gatewayToProfileWorkspaceAGS(t, srv.Handler())
	sessionToken := base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{9}, 32))
	repository := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/docs", "Bearer "+sessionToken)
	require.Equal(t, http.StatusOK, repository.Code, repository.Body.String())
	var repositoryBody struct {
		Owner struct {
			AvatarURL string `json:"avatar_url"`
		} `json:"owner"`
	}
	require.NoError(t, json.Unmarshal(repository.Body.Bytes(), &repositoryBody))
	require.Equal(t, "https://agentteam.example.test/api/v1/workspaces/wsp_1/ags/avatars/alice", repositoryBody.Owner.AvatarURL)

	raw := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/alice/docs/raw/main/smoke.txt", "Bearer "+sessionToken)
	require.Equal(t, http.StatusOK, raw.Code, raw.Body.String())
	require.Equal(t, "workspace docs\n", raw.Body.String())
	search := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/search/code?q="+url.QueryEscape("workspace repo:alice/docs"), "Bearer "+sessionToken)
	require.Equal(t, http.StatusOK, search.Code, search.Body.String())
	var searchBody struct {
		Items []struct {
			HTMLURL    string `json:"html_url"`
			URL        string `json:"url"`
			Repository struct {
				URL   string `json:"url"`
				Owner struct {
					AvatarURL string `json:"avatar_url"`
				} `json:"owner"`
			} `json:"repository"`
		} `json:"items"`
	}
	require.NoError(t, json.Unmarshal(search.Body.Bytes(), &searchBody))
	require.Len(t, searchBody.Items, 1)
	require.Equal(t, "https://agentteam.example.test/api/v1/workspaces/wsp_1/ags/alice/docs/blob/HEAD/smoke.txt", searchBody.Items[0].HTMLURL)
	require.Equal(t, "https://agentteam.example.test/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/docs/contents/smoke.txt", searchBody.Items[0].URL)
	require.Equal(t, "https://agentteam.example.test/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/docs", searchBody.Items[0].Repository.URL)
	require.Equal(t, "https://agentteam.example.test/api/v1/workspaces/wsp_1/ags/avatars/alice", searchBody.Items[0].Repository.Owner.AvatarURL)

	publicRepository := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/public-docs", "")
	require.Equal(t, http.StatusOK, publicRepository.Code, publicRepository.Body.String())
	publicRaw := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/alice/public-docs/raw/main/public.txt", "")
	require.Equal(t, http.StatusOK, publicRaw.Code, publicRaw.Body.String())
	require.Equal(t, "public workspace docs\n", publicRaw.Body.String())
	privateAnonymous := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/docs", "")
	require.Equal(t, http.StatusNotFound, privateAnonymous.Code, privateAnonymous.Body.String())
	privateRawAnonymous := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/alice/docs/raw/main/smoke.txt", "")
	require.Equal(t, http.StatusNotFound, privateRawAnonymous.Code, privateRawAnonymous.Body.String())
	anonymousWrite := serveWorkspaceQualifiedAGS(handler, http.MethodPut, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/public-docs/contents/anonymous.txt", "")
	require.Equal(t, http.StatusUnauthorized, anonymousWrite.Code, anonymousWrite.Body.String())
	malformedAuthorization := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/alice/public-docs", "Bearer unsupported")
	require.Equal(t, http.StatusBadRequest, malformedAuthorization.Code, malformedAuthorization.Body.String())

	avatar := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/avatars/alice", "")
	require.Equal(t, http.StatusFound, avatar.Code, avatar.Body.String())
	require.NotEmpty(t, avatar.Header().Get("Location"))

	lease, err := repotokens.NewService(ratstore.New(srv.meshxDB), nil).CreateSyncLease(t.Context(), repotokens.CreateSyncLeaseRequest{
		WorkspaceID: "wsp_1", ActorPrincipalID: "desktop_sync", ProviderHost: "agentteam.example.test",
		RepositoryScope: repotokens.RepositoryScope{Mode: "repositories", Repositories: []string{"alice/docs"}},
		Capabilities:    []string{"repo:clone"},
	})
	require.NoError(t, err)
	git := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_1/ags/alice/docs.git/info/refs?service=git-upload-pack", "Bearer "+lease.Plaintext)
	require.Equal(t, http.StatusOK, git.Code, git.Body.String())
	require.Contains(t, git.Body.String(), "# service=git-upload-pack")

	wrongOwner := serveWorkspaceQualifiedAGS(handler, http.MethodGet, "/api/v1/workspaces/wsp_2/ags/api/v3/repos/alice/docs", "Bearer "+lease.Plaintext)
	require.Equal(t, http.StatusNotFound, wrongOwner.Code, wrongOwner.Body.String())
}

func profileWorkspaceAGSHandler(meshx http.Handler) http.Handler {
	return router.NewRouterForProfile(router.ProfileWorkspace, router.Dependencies{
		API: router.APIDependencies{
			MeshXWorkspaceAGS: meshx,
			APIForwarder: forwarder.Dependencies{
				AuthStore:          workspaceAGSWiringSessions{},
				WorkspaceStore:     workspaceAGSWiringMemberships{},
				ManagedWorkspaces:  owner.NewStaticSet([]string{"wsp_1"}),
				TrustedJWTAudience: "meshx",
				Now:                func() time.Time { return time.Unix(1700000000, 0) },
			},
		},
	})
}

func profileAllAGSHandler(meshx http.Handler) http.Handler {
	return router.NewRouterForProfile(router.ProfileAll, router.Dependencies{
		API: router.APIDependencies{
			MeshXWorkspaceAGS: meshx,
			APIForwarder: forwarder.Dependencies{
				AuthStore:          workspaceAGSWiringSessions{},
				WorkspaceStore:     workspaceAGSWiringMemberships{},
				ManagedWorkspaces:  owner.NewStaticSet([]string{"wsp_1"}),
				TrustedJWTAudience: "meshx",
				Now:                func() time.Time { return time.Unix(1700000000, 0) },
			},
		},
	})
}

func gatewayToProfileWorkspaceAGS(t *testing.T, meshx http.Handler) http.Handler {
	t.Helper()
	workspaceServer := httptest.NewServer(profileWorkspaceAGSHandler(meshx))
	t.Cleanup(workspaceServer.Close)
	handler, err := gateway.NewHandler(workspaceAGSWiringPlacementResolver{}, gateway.Config{
		CentralUpstream:    "http://127.0.0.1:1",
		WorkspaceUpstreams: map[string]string{"group_1": workspaceServer.URL},
		IdentityResolver:   workspaceAGSWiringIdentityResolver{},
	})
	require.NoError(t, err)
	return handler
}

func serveTrustedAGSJSON(t *testing.T, handler http.Handler, method, path, authorization string, body any) *httptest.ResponseRecorder {
	t.Helper()
	payload, err := json.Marshal(body)
	require.NoError(t, err)
	request := httptest.NewRequest(method, path, bytes.NewReader(payload))
	request.Header.Set("Authorization", authorization)
	request.Header.Set(config.DefaultTrustedJWTHeader, authorization)
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()
	handler.ServeHTTP(recorder, request)
	return recorder
}

func serveWorkspaceQualifiedAGS(handler http.Handler, method, path, authorization string) *httptest.ResponseRecorder {
	request := httptest.NewRequest(method, path, nil)
	if authorization != "" {
		request.Header.Set("Authorization", authorization)
	}
	recorder := httptest.NewRecorder()
	handler.ServeHTTP(recorder, request)
	return recorder
}
