package server

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

	"github.com/prometheus/client_golang/prometheus/testutil"

	agsauth "github.com/pingcap/agent-git-service/auth"
	"github.com/pingcap/agent-git-service/config"
	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/metrics"
	"github.com/pingcap/agent-git-service/internal/service"
	"github.com/pingcap/agent-git-service/internal/tenant"
)

func TestMultitenantServerIsolatesBusinessStateAcrossSharedServiceGraph(t *testing.T) {
	const (
		tenantAToken = "tenant-a-admin-token"
		tenantBToken = "tenant-b-admin-token"
	)
	secretKey := func(fill byte) string {
		return base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{fill}, 32))
	}
	cfg := config.Config{
		Port:        "0",
		ListenMode:  "production",
		Environment: "development",
		Tenants: []config.TenantConfig{
			{
				ID:                  "tenant-a",
				BaseURL:             "https://a.example.test",
				DomainAliases:       []string{"https://api.a.example.test", "https://login.a.example.test"},
				DBdsn:               createEmptyBootstrapDSN(t, "tenant_isolation_a"),
				GitRepoDir:          t.TempDir(),
				SecretEncryptionKey: secretKey('a'),
				AdminLogin:          "same-admin",
				AdminToken:          tenantAToken,
				Browser: config.TenantBrowserConfig{
					ConsoleBaseURL:             "https://console-a.example.test",
					OAuthDeviceVerificationURL: "https://console-a.example.test/device",
					CORSAllowedOrigins:         []string{"https://app-a.example.test"},
				},
				ConnectedLogin: config.TenantConnectedLoginConfig{
					Enabled:      true,
					Origin:       "https://login-provider-a.example.test",
					APIOrigin:    "https://login-api-a.example.test",
					ClientID:     "tenant-a-connected-client",
					ClientSecret: "tenant-a-connected-secret",
				},
			},
			{
				ID:                  "tenant-b",
				BaseURL:             "https://b.example.test",
				DomainAliases:       []string{"https://login.b.example.test"},
				DBdsn:               createEmptyBootstrapDSN(t, "tenant_isolation_b"),
				GitRepoDir:          t.TempDir(),
				SecretEncryptionKey: secretKey('b'),
				AdminLogin:          "same-admin",
				AdminToken:          tenantBToken,
				Browser: config.TenantBrowserConfig{
					ConsoleBaseURL:             "https://console-b.example.test",
					OAuthDeviceVerificationURL: "https://console-b.example.test/device",
					CORSAllowedOrigins:         []string{"https://app-b.example.test"},
				},
				ConnectedLogin: config.TenantConnectedLoginConfig{
					Enabled:      true,
					Origin:       "https://login-provider-b.example.test",
					APIOrigin:    "https://login-api-b.example.test",
					ClientID:     "tenant-b-connected-client",
					ClientSecret: "tenant-b-connected-secret",
				},
			},
			{
				ID:                  "tenant-disabled",
				BaseURL:             "https://disabled.example.test",
				Disabled:            true,
				DBdsn:               "must-not-be-opened",
				GitRepoDir:          t.TempDir(),
				SecretEncryptionKey: secretKey('c'),
				AdminLogin:          "disabled-admin",
				AdminToken:          "tenant-disabled-admin-token",
			},
		},
	}

	srv, err := New(cfg, WithTenantAuthenticator("tenant-a", headerAuthenticator{
		header: "X-Embedded-User",
		identity: agsauth.Identity{
			Provider: "tenant-isolation-test",
			Subject:  "embedded-subject",
			Login:    "embedded-only-a",
			Name:     "Embedded Tenant A",
		},
	}))
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	t.Cleanup(func() {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		if err := srv.Shutdown(ctx); err != nil {
			t.Errorf("Shutdown: %v", err)
		}
	})

	resourcesA, err := srv.deps.Registry.ForTenant("tenant-a")
	if err != nil {
		t.Fatalf("tenant-a resources: %v", err)
	}
	resourcesB, err := srv.deps.Registry.ForTenant("tenant-b")
	if err != nil {
		t.Fatalf("tenant-b resources: %v", err)
	}
	sharedService := srv.deps.SvcDeps
	if sharedService == nil {
		t.Fatal("shared service is nil")
	}
	if srv.deps.Handlers == nil || srv.deps.Handlers.Svc != sharedService {
		t.Fatal("REST handlers do not use the shared service")
	}
	if srv.deps.GqlSrv == nil || srv.deps.GqlSrv.Svc != sharedService {
		t.Fatal("GraphQL handler does not use the shared service")
	}
	if srv.deps.GitHandler == nil || srv.deps.GitHandler.Svc != sharedService {
		t.Fatal("Git HTTP handler does not use the shared service")
	}
	if srv.deps.OauthHandler == nil || srv.deps.OauthHandler.Svc != sharedService {
		t.Fatal("OAuth handler does not use the shared service")
	}
	sqlA, err := resourcesA.DB.DB()
	if err != nil {
		t.Fatalf("tenant-a SQL handle: %v", err)
	}
	sqlB, err := resourcesB.DB.DB()
	if err != nil {
		t.Fatalf("tenant-b SQL handle: %v", err)
	}
	if sqlA == sqlB {
		t.Fatal("tenants share the same SQL connection pool")
	}
	if resourcesA.Git.CanonicalRoot() == resourcesB.Git.CanonicalRoot() {
		t.Fatal("tenants share the same Git root")
	}

	ctxA := tenant.WithInfo(context.Background(), resourcesA.Info)
	ctxB := tenant.WithInfo(context.Background(), resourcesB.Info)
	scopeA, err := sharedService.Scope(ctxA)
	if err != nil {
		t.Fatalf("resolve tenant-a service scope: %v", err)
	}
	scopeB, err := sharedService.Scope(ctxB)
	if err != nil {
		t.Fatalf("resolve tenant-b service scope: %v", err)
	}
	if scopeA.Resources != resourcesA || scopeB.Resources != resourcesB {
		t.Fatal("shared service did not resolve registry-owned tenant resources")
	}
	gitA, err := sharedService.GitForCtx(ctxA)
	if err != nil {
		t.Fatalf("resolve tenant-a Git capability: %v", err)
	}
	gitB, err := sharedService.GitForCtx(ctxB)
	if err != nil {
		t.Fatalf("resolve tenant-b Git capability: %v", err)
	}
	if gitA != resourcesA.Git || gitB != resourcesB.Git {
		t.Fatal("shared service did not resolve registry-owned Git capabilities")
	}
	if _, err := sharedService.GitForCtx(context.Background()); !errors.Is(err, tenant.ErrRequired) {
		t.Fatalf("GitForCtx without tenant error = %v, want %v", err, tenant.ErrRequired)
	}
	forgedInfo := resourcesA.Info.Clone()
	forgedInfo.CanonicalBaseURL = "https://forged-a.example.test"
	if _, err := sharedService.GitForCtx(tenant.WithInfo(context.Background(), forgedInfo)); !errors.Is(err, service.ErrTenantResourceMismatch) {
		t.Fatalf("GitForCtx with forged policy error = %v, want %v", err, service.ErrTenantResourceMismatch)
	}
	principalA, failure, err := srv.deps.SvcDeps.ValidateAndResolveTokenPrincipalDetailed(ctxA, tenantAToken)
	if err != nil || failure != service.TokenValidationFailureNone {
		t.Fatalf("resolve tenant-a principal: failure=%q err=%v", failure, err)
	}
	principalB, failure, err := srv.deps.SvcDeps.ValidateAndResolveTokenPrincipalDetailed(ctxB, tenantBToken)
	if err != nil || failure != service.TokenValidationFailureNone {
		t.Fatalf("resolve tenant-b principal: failure=%q err=%v", failure, err)
	}
	ctxA, err = service.ContextWithPrincipal(ctxA, principalA)
	if err != nil {
		t.Fatalf("bind tenant-a principal: %v", err)
	}
	ctxB, err = service.ContextWithPrincipal(ctxB, principalB)
	if err != nil {
		t.Fatalf("bind tenant-b principal: %v", err)
	}
	if principalA.User().Login != principalB.User().Login {
		t.Fatalf("expected the same login collision, got %q and %q", principalA.User().Login, principalB.User().Login)
	}
	if principalA.User().ID != principalB.User().ID {
		t.Fatalf("expected the same user ID collision, got %d and %d", principalA.User().ID, principalB.User().ID)
	}

	prometheusMetrics := metrics.DefaultPrometheus()
	if prometheusMetrics == nil {
		t.Fatal("Prometheus metrics are not initialized")
	}
	repoCreatedABefore := testutil.ToFloat64(prometheusMetrics.RepoCreatedTotal.WithLabelValues("tenant-a"))
	repoCreatedBBefore := testutil.ToFloat64(prometheusMetrics.RepoCreatedTotal.WithLabelValues("tenant-b"))

	repoA, err := srv.deps.SvcDeps.CreateRepo(ctxA, service.CreateRepoInput{
		OwnerLogin:  "same-admin",
		Name:        "same-repo",
		Description: "tenant-a repository",
		AutoInit:    true,
	})
	if err != nil {
		t.Fatalf("create tenant-a repo: %v", err)
	}
	repoB, err := srv.deps.SvcDeps.CreateRepo(ctxB, service.CreateRepoInput{
		OwnerLogin:  "same-admin",
		Name:        "same-repo",
		Description: "tenant-b repository",
		AutoInit:    true,
	})
	if err != nil {
		t.Fatalf("create tenant-b repo: %v", err)
	}
	if repoA.FullName != repoB.FullName {
		t.Fatalf("expected the same repository key collision, got %q and %q", repoA.FullName, repoB.FullName)
	}
	if repoA.ID != repoB.ID {
		t.Fatalf("expected the same repository ID collision, got %d and %d", repoA.ID, repoB.ID)
	}
	if got := testutil.ToFloat64(prometheusMetrics.RepoCreatedTotal.WithLabelValues("tenant-a")); got != repoCreatedABefore+1 {
		t.Fatalf("tenant-a repository metric = %v, want %v", got, repoCreatedABefore+1)
	}
	if got := testutil.ToFloat64(prometheusMetrics.RepoCreatedTotal.WithLabelValues("tenant-b")); got != repoCreatedBBefore+1 {
		t.Fatalf("tenant-b repository metric = %v, want %v", got, repoCreatedBBefore+1)
	}

	if _, err := gitA.WriteFile(ctxA, repoA.FullName, "main", "tenant.txt", "tenant-a content", []byte("only tenant a\n")); err != nil {
		t.Fatalf("write tenant-a Git content: %v", err)
	}
	if _, err := gitB.WriteFile(ctxB, repoB.FullName, "main", "tenant.txt", "tenant-b content", []byte("only tenant b\n")); err != nil {
		t.Fatalf("write tenant-b Git content: %v", err)
	}
	assertGitContent := func(ctx context.Context, gitStore interface {
		ReadFile(context.Context, string, string) ([]byte, error)
	}, want string) {
		t.Helper()
		body, err := gitStore.ReadFile(ctx, "same-admin/same-repo", "tenant.txt")
		if err != nil {
			t.Fatalf("read tenant Git content: %v", err)
		}
		if string(body) != want {
			t.Fatalf("tenant Git content = %q, want %q", body, want)
		}
	}
	assertGitContent(ctxA, gitA, "only tenant a\n")
	assertGitContent(ctxB, gitB, "only tenant b\n")

	headA, err := gitA.HeadSHA(ctxA, repoA.FullName, "main")
	if err != nil {
		t.Fatalf("tenant-a head SHA: %v", err)
	}
	headB, err := gitB.HeadSHA(ctxB, repoB.FullName, "main")
	if err != nil {
		t.Fatalf("tenant-b head SHA: %v", err)
	}
	if headA == headB {
		t.Fatalf("tenant Git histories unexpectedly share head %q", headA)
	}

	if _, err := srv.deps.SvcDeps.PutWikiPage(ctxA, repoA.FullName, "home", "tenant-a wiki", "tenant-a wiki", ""); err != nil {
		t.Fatalf("write tenant-a wiki: %v", err)
	}
	if _, err := srv.deps.SvcDeps.PutWikiPage(ctxB, repoB.FullName, "home", "tenant-b wiki", "tenant-b wiki", ""); err != nil {
		t.Fatalf("write tenant-b wiki: %v", err)
	}
	pageA, err := srv.deps.SvcDeps.GetWikiPage(ctxA, repoA.FullName, "home")
	if err != nil {
		t.Fatalf("read tenant-a wiki: %v", err)
	}
	pageB, err := srv.deps.SvcDeps.GetWikiPage(ctxB, repoB.FullName, "home")
	if err != nil {
		t.Fatalf("read tenant-b wiki: %v", err)
	}
	if pageA.Body != "tenant-a wiki" || pageB.Body != "tenant-b wiki" {
		t.Fatalf("wiki bodies crossed tenants: tenant-a=%q tenant-b=%q", pageA.Body, pageB.Body)
	}

	if resourcesA.SecretBox.PublicKeyID() == resourcesB.SecretBox.PublicKeyID() {
		t.Fatal("tenants share the same secret-encryption public key")
	}
	ciphertext, err := resourcesA.SecretBox.EncryptSecret("tenant-a secret")
	if err != nil {
		t.Fatalf("encrypt tenant-a secret: %v", err)
	}
	if _, err := resourcesB.SecretBox.DecryptSecret(ciphertext); err == nil {
		t.Fatal("tenant-b decrypted tenant-a ciphertext")
	}

	webhookReceived := make(chan struct{}, 1)
	webhookReceiver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		select {
		case webhookReceived <- struct{}{}:
		default:
		}
		w.WriteHeader(http.StatusNoContent)
	}))
	t.Cleanup(webhookReceiver.Close)
	webhookConfig, err := json.Marshal(map[string]string{
		"url":          webhookReceiver.URL,
		"content_type": "json",
	})
	if err != nil {
		t.Fatalf("marshal webhook config: %v", err)
	}
	hookA := db.Webhook{
		RepositoryID: repoA.ID,
		Name:         "web",
		Active:       true,
		EventsJSON:   `["push"]`,
		ConfigJSON:   string(webhookConfig),
	}
	if err := srv.deps.SvcDeps.CreateWebhook(ctxA, &hookA); err != nil {
		t.Fatalf("create tenant-a webhook: %v", err)
	}
	requestCtxA, cancelRequestA := context.WithCancel(ctxA)
	if err := srv.deps.SvcDeps.DispatchWebhookEvent(requestCtxA, repoA.ID, "push", "", map[string]any{
		"repository": repoA.FullName,
	}); err != nil {
		cancelRequestA()
		t.Fatalf("dispatch tenant-a webhook: %v", err)
	}
	cancelRequestA()
	select {
	case <-webhookReceived:
	case <-time.After(5 * time.Second):
		t.Fatal("tenant-a webhook did not complete after request cancellation")
	}
	var deliveriesA []db.HookDelivery
	deadline := time.Now().Add(5 * time.Second)
	for {
		if err := resourcesA.DB.Where("webhook_id = ?", hookA.ID).Find(&deliveriesA).Error; err != nil {
			t.Fatalf("load tenant-a webhook deliveries: %v", err)
		}
		if len(deliveriesA) == 1 && deliveriesA[0].Status != "pending" {
			break
		}
		if time.Now().After(deadline) {
			t.Fatalf("tenant-a webhook delivery did not finish: %#v", deliveriesA)
		}
		time.Sleep(20 * time.Millisecond)
	}
	if deliveriesA[0].Status != "ok" || deliveriesA[0].TenantID != "tenant-a" {
		t.Fatalf("tenant-a webhook delivery envelope = %#v", deliveriesA[0])
	}
	var deliveriesB int64
	if err := resourcesB.DB.Model(&db.HookDelivery{}).Count(&deliveriesB).Error; err != nil {
		t.Fatalf("count tenant-b webhook deliveries: %v", err)
	}
	if deliveriesB != 0 {
		t.Fatalf("tenant-a webhook created %d tenant-b delivery records", deliveriesB)
	}

	userIDA := principalA.User().ID
	code := db.AuthorizationCode{
		Code:        "same-authorization-code",
		UserID:      &userIDA,
		RedirectURI: "https://a.example.test/callback",
		ExpiresAt:   time.Now().UTC().Add(service.AuthorizationCodeTTL),
	}
	if err := srv.deps.SvcDeps.CreateAuthorizationCode(ctxA, &code); err != nil {
		t.Fatalf("create tenant-a authorization code: %v", err)
	}
	if _, err := srv.deps.SvcDeps.ExchangeAuthorizationCode(ctxB, code.Code, ""); !errors.Is(err, service.ErrNotFound) {
		t.Fatalf("tenant-b authorization-code exchange error = %v, want %v", err, service.ErrNotFound)
	}
	accessToken, err := srv.deps.SvcDeps.ExchangeAuthorizationCode(ctxA, code.Code, "")
	if err != nil {
		t.Fatalf("exchange tenant-a authorization code: %v", err)
	}
	var tenantATokens int64
	if err := resourcesA.DB.Model(&db.Token{}).Where("value = ?", accessToken).Count(&tenantATokens).Error; err != nil {
		t.Fatalf("count tenant-a issued token: %v", err)
	}
	var tenantBTokens int64
	if err := resourcesB.DB.Model(&db.Token{}).Where("value = ?", accessToken).Count(&tenantBTokens).Error; err != nil {
		t.Fatalf("count tenant-b issued token: %v", err)
	}
	if tenantATokens != 1 || tenantBTokens != 0 {
		t.Fatalf("issued token placement: tenant-a=%d tenant-b=%d", tenantATokens, tenantBTokens)
	}

	doRequest := func(method, host, path, tokenValue, origin string, body []byte) *httptest.ResponseRecorder {
		t.Helper()
		req := httptest.NewRequest(method, "https://"+host+path, bytes.NewReader(body))
		req.Host = host
		if tokenValue != "" {
			req.Header.Set("Authorization", "token "+tokenValue)
		}
		if origin != "" {
			req.Header.Set("Origin", origin)
		}
		if len(body) > 0 {
			req.Header.Set("Content-Type", "application/json")
		}
		rec := httptest.NewRecorder()
		srv.Handler().ServeHTTP(rec, req)
		return rec
	}

	assertRESTRepo := func(host, tokenValue, wantDescription, wantBaseURL string) {
		t.Helper()
		rec := doRequest(http.MethodGet, host, "/api/v3/repos/same-admin/same-repo", tokenValue, "", nil)
		if rec.Code != http.StatusOK {
			t.Fatalf("GET repo through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		var body map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
			t.Fatalf("decode repo through %s: %v", host, err)
		}
		if body["description"] != wantDescription {
			t.Fatalf("repo description through %s = %v, want %q", host, body["description"], wantDescription)
		}
		url, _ := body["url"].(string)
		if !strings.HasPrefix(url, wantBaseURL+"/api/v3/") {
			t.Fatalf("repo URL through %s = %q, want base %q", host, url, wantBaseURL)
		}
	}
	assertRESTRepo("a.example.test", tenantAToken, "tenant-a repository", "https://a.example.test")
	assertRESTRepo("api.a.example.test", tenantAToken, "tenant-a repository", "https://a.example.test")
	assertRESTRepo("b.example.test", tenantBToken, "tenant-b repository", "https://b.example.test")

	assertGitHTTPRepo := func(host, tokenValue, wantSHA, otherSHA string) {
		t.Helper()
		rec := doRequest(
			http.MethodGet,
			host,
			"/same-admin/same-repo.git/info/refs?service=git-upload-pack",
			tokenValue,
			"",
			nil,
		)
		if rec.Code != http.StatusOK {
			t.Fatalf("Git info/refs through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		if !bytes.Contains(rec.Body.Bytes(), []byte(wantSHA)) {
			t.Fatalf("Git info/refs through %s does not advertise tenant head %q", host, wantSHA)
		}
		if bytes.Contains(rec.Body.Bytes(), []byte(otherSHA)) {
			t.Fatalf("Git info/refs through %s advertises other tenant head %q", host, otherSHA)
		}
	}
	assertGitHTTPRepo("a.example.test", tenantAToken, headA, headB)
	assertGitHTTPRepo("b.example.test", tenantBToken, headB, headA)

	wrongTenantToken := doRequest(http.MethodGet, "b.example.test", "/api/v3/repos/same-admin/same-repo", tenantAToken, "", nil)
	if wrongTenantToken.Code != http.StatusUnauthorized {
		t.Fatalf("tenant-a token on tenant-b host = %d, want %d", wrongTenantToken.Code, http.StatusUnauthorized)
	}

	embeddedRequest := func(host string) *httptest.ResponseRecorder {
		t.Helper()
		req := httptest.NewRequest(http.MethodGet, "https://"+host+"/api/v3/user", nil)
		req.Host = host
		req.Header.Set("X-Embedded-User", "present")
		rec := httptest.NewRecorder()
		srv.Handler().ServeHTTP(rec, req)
		return rec
	}
	embeddedA := embeddedRequest("a.example.test")
	if embeddedA.Code != http.StatusOK {
		t.Fatalf("tenant-a embedded identity = %d: %s", embeddedA.Code, embeddedA.Body.String())
	}
	var embeddedABody map[string]any
	if err := json.Unmarshal(embeddedA.Body.Bytes(), &embeddedABody); err != nil {
		t.Fatalf("decode tenant-a embedded identity: %v", err)
	}
	if embeddedABody["login"] != "embedded-only-a" {
		t.Fatalf("tenant-a embedded login = %v", embeddedABody["login"])
	}
	embeddedB := embeddedRequest("b.example.test")
	if embeddedB.Code != http.StatusUnauthorized {
		t.Fatalf("tenant-a embedded identity on tenant-b host = %d, want %d", embeddedB.Code, http.StatusUnauthorized)
	}
	var embeddedUsersB int64
	if err := resourcesB.DB.Model(&db.User{}).Where("login = ?", "embedded-only-a").Count(&embeddedUsersB).Error; err != nil {
		t.Fatalf("count tenant-b embedded users: %v", err)
	}
	if embeddedUsersB != 0 {
		t.Fatalf("tenant-a embedded identity created %d tenant-b users", embeddedUsersB)
	}

	sshCreateBody, err := json.Marshal(map[string]string{
		"title": "tenant-a key",
		"key":   "ssh-ed25519 AAAATEST tenant-a",
	})
	if err != nil {
		t.Fatalf("marshal SSH key: %v", err)
	}
	createSSHKey := doRequest(http.MethodPost, "a.example.test", "/api/v3/user/keys", tenantAToken, "", sshCreateBody)
	if createSSHKey.Code != http.StatusCreated {
		t.Fatalf("create tenant-a SSH key = %d: %s", createSSHKey.Code, createSSHKey.Body.String())
	}
	listSSHKeys := func(host, tokenValue string) []map[string]any {
		t.Helper()
		rec := doRequest(http.MethodGet, host, "/api/v3/user/keys", tokenValue, "", nil)
		if rec.Code != http.StatusOK {
			t.Fatalf("list SSH keys through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		var rows []map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &rows); err != nil {
			t.Fatalf("decode SSH keys through %s: %v", host, err)
		}
		return rows
	}
	if keys := listSSHKeys("a.example.test", tenantAToken); len(keys) != 1 {
		t.Fatalf("tenant-a SSH keys = %#v, want one", keys)
	}
	if keys := listSSHKeys("b.example.test", tenantBToken); len(keys) != 0 {
		t.Fatalf("tenant-b saw tenant-a SSH keys: %#v", keys)
	}

	unknownHost := doRequest(http.MethodGet, "unknown.example.test", "/api/v3/repos/same-admin/same-repo", tenantAToken, "", nil)
	if unknownHost.Code != http.StatusMisdirectedRequest {
		t.Fatalf("unknown host = %d, want %d", unknownHost.Code, http.StatusMisdirectedRequest)
	}
	disabledHost := doRequest(http.MethodGet, "disabled.example.test", "/api/v3", "", "", nil)
	if disabledHost.Code != http.StatusMisdirectedRequest {
		t.Fatalf("disabled tenant host = %d, want %d", disabledHost.Code, http.StatusMisdirectedRequest)
	}

	allowedCORS := doRequest(http.MethodGet, "a.example.test", "/api/v3/repos/same-admin/same-repo", "", "https://app-a.example.test", nil)
	if got := allowedCORS.Header().Get("Access-Control-Allow-Origin"); got != "https://app-a.example.test" {
		t.Fatalf("tenant-a CORS allow origin = %q", got)
	}
	disallowedCORS := doRequest(http.MethodGet, "b.example.test", "/api/v3/repos/same-admin/same-repo", "", "https://app-a.example.test", nil)
	if got := disallowedCORS.Header().Get("Access-Control-Allow-Origin"); got != "" {
		t.Fatalf("tenant-b leaked tenant-a CORS policy: %q", got)
	}

	aliasLogin := doRequest(http.MethodGet, "login.a.example.test", "/auth/connected/login", "", "", nil)
	if aliasLogin.Code != http.StatusFound {
		t.Fatalf("connected login through tenant-a alias = %d: %s", aliasLogin.Code, aliasLogin.Body.String())
	}
	if got := aliasLogin.Header().Get("Location"); got != "https://a.example.test/auth/connected/login" {
		t.Fatalf("tenant-a alias connected-login redirect = %q", got)
	}
	if cookies := aliasLogin.Result().Cookies(); len(cookies) != 0 {
		t.Fatalf("tenant-a alias set connected-login cookies before canonical redirect: %#v", cookies)
	}
	aliasCallback := doRequest(
		http.MethodGet,
		"login.a.example.test",
		"/auth/connected/callback?code=code&state=state",
		"",
		"",
		nil,
	)
	if aliasCallback.Code != http.StatusMisdirectedRequest {
		t.Fatalf("connected callback through tenant-a alias = %d, want %d", aliasCallback.Code, http.StatusMisdirectedRequest)
	}

	requestConnectedLogin := func(host, wantClientID string) *http.Cookie {
		t.Helper()
		rec := doRequest(http.MethodGet, host, "/auth/connected/login", "", "", nil)
		if rec.Code != http.StatusFound {
			t.Fatalf("connected login through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		location, err := url.Parse(rec.Header().Get("Location"))
		if err != nil {
			t.Fatalf("parse connected login redirect through %s: %v", host, err)
		}
		if location.Query().Get("client_id") != wantClientID {
			t.Fatalf("connected login client through %s = %q", host, location.Query().Get("client_id"))
		}
		state := location.Query().Get("state")
		for _, cookie := range rec.Result().Cookies() {
			if cookie.Name == "connected_login_state" {
				if cookie.Domain != "" || cookie.Value != state {
					t.Fatalf("connected login cookie through %s = %#v, state %q", host, cookie, state)
				}
				return cookie
			}
		}
		t.Fatalf("connected login through %s did not set a state cookie", host)
		return nil
	}
	tenantAState := requestConnectedLogin("a.example.test", "tenant-a-connected-client")
	_ = requestConnectedLogin("b.example.test", "tenant-b-connected-client")
	crossTenantCallbackReq := httptest.NewRequest(
		http.MethodGet,
		"https://b.example.test/auth/connected/callback?code=code&state=different-state",
		nil,
	)
	crossTenantCallbackReq.Host = "b.example.test"
	crossTenantCallbackReq.AddCookie(tenantAState)
	crossTenantCallback := httptest.NewRecorder()
	srv.Handler().ServeHTTP(crossTenantCallback, crossTenantCallbackReq)
	if crossTenantCallback.Code != http.StatusUnprocessableEntity {
		t.Fatalf(
			"tenant-a connected state on tenant-b callback = %d, want %d: %s",
			crossTenantCallback.Code,
			http.StatusUnprocessableEntity,
			crossTenantCallback.Body.String(),
		)
	}

	assertGraphQLRepo := func(host, tokenValue, wantDescription string) {
		t.Helper()
		query, err := json.Marshal(map[string]any{
			"query": `query($owner: String!, $name: String!) {
				repository(owner: $owner, name: $name) { name description url }
			}`,
			"variables": map[string]string{
				"owner": "same-admin",
				"name":  "same-repo",
			},
		})
		if err != nil {
			t.Fatalf("marshal GraphQL request: %v", err)
		}
		rec := doRequest(http.MethodPost, host, "/api/graphql", tokenValue, "", query)
		if rec.Code != http.StatusOK {
			t.Fatalf("GraphQL repo through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		var response struct {
			Data struct {
				Repository struct {
					Description string `json:"description"`
					URL         string `json:"url"`
				} `json:"repository"`
			} `json:"data"`
			Errors []any `json:"errors"`
		}
		if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
			t.Fatalf("decode GraphQL through %s: %v", host, err)
		}
		if len(response.Errors) != 0 {
			t.Fatalf("GraphQL through %s returned errors: %s", host, rec.Body.String())
		}
		if response.Data.Repository.Description != wantDescription {
			t.Fatalf(
				"GraphQL description through %s = %q, want %q: %s",
				host,
				response.Data.Repository.Description,
				wantDescription,
				rec.Body.String(),
			)
		}
		if !strings.HasPrefix(response.Data.Repository.URL, "https://"+host+"/") {
			t.Fatalf("GraphQL URL through %s = %q", host, response.Data.Repository.URL)
		}
	}
	assertGraphQLRepo("a.example.test", tenantAToken, "tenant-a repository")
	assertGraphQLRepo("b.example.test", tenantBToken, "tenant-b repository")

	requestDeviceCode := func(host, wantVerificationURI string) string {
		t.Helper()
		rec := doRequest(http.MethodPost, host, "/login/device/code", "", "", nil)
		if rec.Code != http.StatusOK {
			t.Fatalf("request device code through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		var response struct {
			DeviceCode      string `json:"device_code"`
			VerificationURI string `json:"verification_uri"`
		}
		if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
			t.Fatalf("decode device code through %s: %v", host, err)
		}
		if response.DeviceCode == "" {
			t.Fatalf("device code through %s is empty", host)
		}
		if response.VerificationURI != wantVerificationURI {
			t.Fatalf("device verification URI through %s = %q, want %q", host, response.VerificationURI, wantVerificationURI)
		}
		return response.DeviceCode
	}
	exchangeDeviceCode := func(host, deviceCode string) string {
		t.Helper()
		payload, err := json.Marshal(map[string]string{"device_code": deviceCode})
		if err != nil {
			t.Fatalf("marshal device-code exchange: %v", err)
		}
		rec := doRequest(http.MethodPost, host, "/login/oauth/access_token", "", "", payload)
		if rec.Code != http.StatusBadRequest {
			t.Fatalf("exchange pending device code through %s = %d: %s", host, rec.Code, rec.Body.String())
		}
		var response struct {
			Error string `json:"error"`
		}
		if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
			t.Fatalf("decode device-code exchange through %s: %v", host, err)
		}
		return response.Error
	}

	deviceCodeA := requestDeviceCode("a.example.test", "https://console-a.example.test/device")
	_ = requestDeviceCode("b.example.test", "https://console-b.example.test/device")
	var deviceAuditsA []db.DeviceCodeAuditLog
	if err := resourcesA.DB.Where("device_code = ?", deviceCodeA).Find(&deviceAuditsA).Error; err != nil {
		t.Fatalf("load tenant-a device-code audit: %v", err)
	}
	if len(deviceAuditsA) != 1 || deviceAuditsA[0].TenantID != "tenant-a" {
		t.Fatalf("tenant-a device-code audit envelope = %#v", deviceAuditsA)
	}
	var deviceAuditsB int64
	if err := resourcesB.DB.Model(&db.DeviceCodeAuditLog{}).Where("device_code = ?", deviceCodeA).Count(&deviceAuditsB).Error; err != nil {
		t.Fatalf("count tenant-b device-code audit: %v", err)
	}
	if deviceAuditsB != 0 {
		t.Fatalf("tenant-a device code created %d tenant-b audit records", deviceAuditsB)
	}
	if got := exchangeDeviceCode("b.example.test", deviceCodeA); got != "bad_verification_code" {
		t.Fatalf("tenant-a device code on tenant-b = %q, want bad_verification_code", got)
	}
	if got := exchangeDeviceCode("a.example.test", deviceCodeA); got != "authorization_pending" {
		t.Fatalf("tenant-a device code on tenant-a = %q, want authorization_pending", got)
	}
}
