package server

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/mem9-ai/drive9/pkg/meta"
	"github.com/mem9-ai/drive9/pkg/tenant"
	"github.com/mem9-ai/drive9/pkg/tenant/token"
)

func TestTokensDispatcherAmbiguousCredentials(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Bearer + control-plane headers → 400
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(`{"ttl_seconds":60,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`))
	req.Header.Set("Authorization", "Bearer "+rt.token)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set(quotaPublicKeyHeader, "pub")
	req.Header.Set(quotaPrivateKeyHeader, "priv")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	body, _ := io.ReadAll(resp.Body)
	_ = resp.Body.Close()
	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("header ambiguous status=%d body=%s, want 400", resp.StatusCode, body)
	}
	if !strings.Contains(string(body), "ambiguous credentials") {
		t.Fatalf("body=%s, want ambiguous credentials", body)
	}

	// Bearer + public_key inside body → 400
	bodyPayload := `{"ttl_seconds":60,"scopes":[{"prefix":"/scratch","ops":["read"]}],"public_key":"pub","private_key":"priv"}`
	req2, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(bodyPayload))
	req2.Header.Set("Authorization", "Bearer "+rt.token)
	req2.Header.Set("Content-Type", "application/json")
	resp2, err := http.DefaultClient.Do(req2)
	if err != nil {
		t.Fatal(err)
	}
	body2, _ := io.ReadAll(resp2.Body)
	_ = resp2.Body.Close()
	if resp2.StatusCode != http.StatusBadRequest {
		t.Fatalf("body ambiguous status=%d body=%s, want 400", resp2.StatusCode, body2)
	}
	if !strings.Contains(string(body2), "ambiguous credentials") {
		t.Fatalf("body=%s, want ambiguous credentials", body2)
	}
}

func TestTokensDispatcherNeitherCredential(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(`{"ttl_seconds":60,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	body, _ := io.ReadAll(resp.Body)
	_ = resp.Body.Close()
	if resp.StatusCode != http.StatusUnauthorized {
		t.Fatalf("status=%d body=%s, want 401", resp.StatusCode, body)
	}
	if !strings.Contains(string(body), "missing or malformed Authorization header") {
		t.Fatalf("body=%s", body)
	}
}

func TestTokensDispatcherBearerBodyRestored(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Bearer issue body without control fields must still decode after peek.
	body := `{"ttl_seconds":3600,"scopes":[{"prefix":"/scratch/restored/","ops":["read"]}]}`
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+rt.token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = resp.Body.Close() }()
	if resp.StatusCode != http.StatusCreated {
		b, _ := io.ReadAll(resp.Body)
		t.Fatalf("status=%d body=%s, want 201 (proves body restored after peek)", resp.StatusCode, b)
	}
}

func TestScopedTokenRefreshAllowedOtherPathsForbidden(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Issue a proper scoped token via owner.
	issueBody := `{"subject":"refresh-agent","ttl_seconds":3600,"scopes":[{"prefix":"/scratch","ops":["read","write"]}]}`
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(issueBody))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}

	// Scoped refresh allowed.
	refreshReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", strings.NewReader(`{}`))
	refreshReq.Header.Set("Authorization", "Bearer "+issued.Token)
	refreshReq.Header.Set("Content-Type", "application/json")
	refreshResp, err := http.DefaultClient.Do(refreshReq)
	if err != nil {
		t.Fatal(err)
	}
	refreshBody, _ := io.ReadAll(refreshResp.Body)
	_ = refreshResp.Body.Close()
	if refreshResp.StatusCode != http.StatusOK {
		t.Fatalf("refresh status=%d body=%s, want 200", refreshResp.StatusCode, refreshBody)
	}
	var refreshed refreshTokenResponse
	if err := json.Unmarshal(refreshBody, &refreshed); err != nil {
		t.Fatal(err)
	}
	if refreshed.Token == "" || refreshed.Token == issued.Token {
		t.Fatalf("expected rotated token, got %q", refreshed.Token)
	}
	if refreshed.TokenID != issued.TokenID {
		t.Fatalf("token_id=%s want %s", refreshed.TokenID, issued.TokenID)
	}
	if refreshed.ExpiresAt == nil {
		t.Fatal("expected expires_at on refreshed scoped token")
	}

	// Old token no longer authenticates (after cache miss / rotate).
	// New token works for fs.
	readReq, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/fs/scratch/x.txt", nil)
	readReq.Header.Set("Authorization", "Bearer "+refreshed.Token)
	readResp, err := http.DefaultClient.Do(readReq)
	if err != nil {
		t.Fatal(err)
	}
	_ = readResp.Body.Close()
	// 404 (not found) or 200 is fine — auth must succeed (not 401/403).
	if readResp.StatusCode == http.StatusUnauthorized || readResp.StatusCode == http.StatusForbidden {
		t.Fatalf("new token auth failed status=%d", readResp.StatusCode)
	}

	// Scoped refresh with query → 400 (not 403).
	qReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh?foo=1", strings.NewReader(`{}`))
	qReq.Header.Set("Authorization", "Bearer "+refreshed.Token)
	qReq.Header.Set("Content-Type", "application/json")
	qResp, err := http.DefaultClient.Do(qReq)
	if err != nil {
		t.Fatal(err)
	}
	_ = qResp.Body.Close()
	if qResp.StatusCode != http.StatusBadRequest {
		t.Fatalf("refresh with query status=%d, want 400", qResp.StatusCode)
	}

	// Other management paths remain 403 for scoped.
	paths := []struct {
		method string
		path   string
		body   string
	}{
		{http.MethodPost, "/v1/tokens", `{"ttl_seconds":60,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`},
		{http.MethodGet, "/v1/tokens", ``},
		{http.MethodPost, "/v1/tokens/generate", `{"tenant_id":"x"}`},
		{http.MethodPost, "/v1/tokens/revoke", `{"api_key":"drive9_x"}`},
		{http.MethodDelete, "/v1/tokens/" + issued.TokenID, ``},
		{http.MethodPost, "/v1/tokens/" + issued.TokenID + "/activate", `{}`},
		{http.MethodPost, "/v1/tokens/" + issued.TokenID + "/deactivate", `{}`},
	}
	for _, tc := range paths {
		var rdr io.Reader
		if tc.body != "" {
			rdr = strings.NewReader(tc.body)
		}
		req, _ := http.NewRequest(tc.method, ts.URL+tc.path, rdr)
		req.Header.Set("Authorization", "Bearer "+refreshed.Token)
		if tc.body != "" {
			req.Header.Set("Content-Type", "application/json")
		}
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		_ = resp.Body.Close()
		if resp.StatusCode != http.StatusForbidden {
			t.Fatalf("%s %s status=%d, want 403", tc.method, tc.path, resp.StatusCode)
		}
	}
}

func TestScopedTokenIssueWritesExpiresAt(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	body := `{"subject":"exp-write","ttl_seconds":7200,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+rt.token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = resp.Body.Close()
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("status=%d", resp.StatusCode)
	}
	if issued.ExpiresAt == nil {
		t.Fatal("response expires_at is nil")
	}

	// DB row must have expires_at non-NULL, second-aligned with JWT exp.
	row, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	if row.ExpiresAt == nil {
		t.Fatal("db expires_at is nil")
	}
	if row.ExpiresAt.Unix() != issued.ExpiresAt.Unix() {
		t.Fatalf("db expires_at=%v response=%v", row.ExpiresAt, issued.ExpiresAt)
	}
	// JWT exp claim must match the column at second precision.
	claims, err := token.ParseAndVerifyToken(rt.tokenSecret, issued.Token)
	if err != nil {
		t.Fatal(err)
	}
	if claims.ExpiresAt != row.ExpiresAt.Unix() {
		t.Fatalf("jwt exp=%d db expires_at unix=%d", claims.ExpiresAt, row.ExpiresAt.Unix())
	}
	// Truncation: no sub-second component stored beyond whole seconds.
	if row.ExpiresAt.Nanosecond() != 0 {
		t.Fatalf("expires_at has sub-second component: %v", row.ExpiresAt)
	}
}

func TestOwnerTokenListAndLegacyResponses(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Issue a scoped token so list has something beyond the owner key.
	issueBody := `{"subject":"list-me","ttl_seconds":3600,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(issueBody))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}

	listReq, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/tokens", nil)
	listReq.Header.Set("Authorization", "Bearer "+rt.token)
	listResp, err := http.DefaultClient.Do(listReq)
	if err != nil {
		t.Fatal(err)
	}
	listBody, _ := io.ReadAll(listResp.Body)
	_ = listResp.Body.Close()
	if listResp.StatusCode != http.StatusOK {
		t.Fatalf("list status=%d body=%s", listResp.StatusCode, listBody)
	}
	var listOut tokenListResponse
	if err := json.Unmarshal(listBody, &listOut); err != nil {
		t.Fatal(err)
	}
	if len(listOut.Tokens) < 2 {
		t.Fatalf("tokens=%d, want at least owner+scoped", len(listOut.Tokens))
	}
	for _, item := range listOut.Tokens {
		if item.TokenID == "" || item.TenantID != rt.tenantID {
			t.Fatalf("unexpected list item: %+v", item)
		}
	}
	// Must not leak secrets.
	if bytes.Contains(listBody, []byte("jwt_")) || bytes.Contains(listBody, []byte(issued.Token)) {
		t.Fatalf("list response leaked secret material: %s", listBody)
	}

	// Legacy DELETE response body.
	delReq, _ := http.NewRequest(http.MethodDelete, ts.URL+"/v1/tokens/"+issued.TokenID, nil)
	delReq.Header.Set("Authorization", "Bearer "+rt.token)
	delResp, err := http.DefaultClient.Do(delReq)
	if err != nil {
		t.Fatal(err)
	}
	delBody, _ := io.ReadAll(delResp.Body)
	_ = delResp.Body.Close()
	if delResp.StatusCode != http.StatusOK {
		t.Fatalf("delete status=%d body=%s", delResp.StatusCode, delBody)
	}
	var delOut map[string]string
	if err := json.Unmarshal(delBody, &delOut); err != nil {
		t.Fatal(err)
	}
	if delOut["status"] != "ok" || len(delOut) != 1 {
		t.Fatalf("legacy delete body=%s, want {\"status\":\"ok\"}", delBody)
	}
}

func TestOwnerRefreshNoExpiryAndTTL(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Owner with no expiry: default refresh rotates only.
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", strings.NewReader(`{}`))
	req.Header.Set("Authorization", "Bearer "+rt.token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	body, _ := io.ReadAll(resp.Body)
	_ = resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("refresh status=%d body=%s", resp.StatusCode, body)
	}
	var out refreshTokenResponse
	if err := json.Unmarshal(body, &out); err != nil {
		t.Fatal(err)
	}
	if out.Token == "" || out.Token == rt.token {
		t.Fatalf("expected rotated owner token")
	}
	if out.ExpiresAt != nil {
		t.Fatalf("no-expiry owner should keep null expires_at, got %v", out.ExpiresAt)
	}

	// Explicit ttl sets expiry.
	ttl := int64(3600)
	payload, _ := json.Marshal(refreshTokenRequest{TTLSeconds: &ttl})
	req2, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", bytes.NewReader(payload))
	req2.Header.Set("Authorization", "Bearer "+out.Token)
	req2.Header.Set("Content-Type", "application/json")
	resp2, err := http.DefaultClient.Do(req2)
	if err != nil {
		t.Fatal(err)
	}
	body2, _ := io.ReadAll(resp2.Body)
	_ = resp2.Body.Close()
	if resp2.StatusCode != http.StatusOK {
		t.Fatalf("ttl refresh status=%d body=%s", resp2.StatusCode, body2)
	}
	var out2 refreshTokenResponse
	if err := json.Unmarshal(body2, &out2); err != nil {
		t.Fatal(err)
	}
	if out2.ExpiresAt == nil {
		t.Fatal("expected expires_at after ttl refresh")
	}
	// Second default refresh must not grow the period unboundedly: issued_at updated.
	time.Sleep(10 * time.Millisecond)
	req3, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", strings.NewReader(`{}`))
	req3.Header.Set("Authorization", "Bearer "+out2.Token)
	req3.Header.Set("Content-Type", "application/json")
	resp3, err := http.DefaultClient.Do(req3)
	if err != nil {
		t.Fatal(err)
	}
	body3, _ := io.ReadAll(resp3.Body)
	_ = resp3.Body.Close()
	if resp3.StatusCode != http.StatusOK {
		t.Fatalf("second refresh status=%d body=%s", resp3.StatusCode, body3)
	}
	var out3 refreshTokenResponse
	if err := json.Unmarshal(body3, &out3); err != nil {
		t.Fatal(err)
	}
	// Period is ~3600s; new expiry should be roughly now+3600, not far beyond previous+3600.
	if out3.ExpiresAt == nil {
		t.Fatal("expected expires_at")
	}
	// Allow 5s clock skew.
	if out3.ExpiresAt.Sub(*out2.ExpiresAt) > 5*time.Second {
		t.Fatalf("period grew: prev=%v next=%v", out2.ExpiresAt, out3.ExpiresAt)
	}
}

func TestPeekTokenCredentialsHeaderFirst(t *testing.T) {
	// Header control credentials: body not required.
	req := httptest.NewRequest(http.MethodGet, "/v1/tokens", nil)
	req.Header.Set(quotaPublicKeyHeader, "pub")
	has, err := peekTokenCredentials(httptest.NewRecorder(), req)
	if err != nil {
		t.Fatal(err)
	}
	if !has {
		t.Fatal("expected header control cred")
	}

	// Body control credentials on POST.
	body := `{"public_key":"p","private_key":"s","tenant_id":"t1"}`
	req2 := httptest.NewRequest(http.MethodPost, "/v1/tokens/generate", strings.NewReader(body))
	req2.Header.Set("Content-Type", "application/json")
	has2, err := peekTokenCredentials(httptest.NewRecorder(), req2)
	if err != nil {
		t.Fatal(err)
	}
	if !has2 {
		t.Fatal("expected body control cred")
	}
	// Body must be restored.
	restored, _ := io.ReadAll(req2.Body)
	if string(restored) != body {
		t.Fatalf("body not restored: %q", restored)
	}

	// Malformed JSON is not control plane.
	req3 := httptest.NewRequest(http.MethodPost, "/v1/tokens", strings.NewReader(`not-json`))
	has3, err := peekTokenCredentials(httptest.NewRecorder(), req3)
	if err != nil {
		t.Fatal(err)
	}
	if has3 {
		t.Fatal("malformed JSON must not classify as control plane")
	}
}

func TestRefreshStaleTokenVersionConflict(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Issue scoped token, then refresh once successfully.
	issueBody := `{"subject":"stale-ver","ttl_seconds":7200,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(issueBody))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}
	oldToken := issued.Token

	refReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", strings.NewReader(`{}`))
	refReq.Header.Set("Authorization", "Bearer "+oldToken)
	refReq.Header.Set("Content-Type", "application/json")
	refResp, err := http.DefaultClient.Do(refReq)
	if err != nil {
		t.Fatal(err)
	}
	var refreshed refreshTokenResponse
	if err := json.NewDecoder(refResp.Body).Decode(&refreshed); err != nil {
		t.Fatal(err)
	}
	_ = refResp.Body.Close()
	if refResp.StatusCode != http.StatusOK {
		t.Fatalf("refresh status=%d", refResp.StatusCode)
	}
	newToken := refreshed.Token

	// Capture the DB version after the successful refresh.
	row, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	liveHash := row.JWTHash
	liveVersion := row.TokenVersion

	// Simulate residual-window stale auth: a caller still holding the pre-refresh
	// token_version must not clobber the live generation. Middleware is already
	// past for the old hash; assert the store optimistic-lock contract.
	staleNext := *row
	staleNext.JWTHash = "deadbeef"
	staleNext.JWTCiphertext = []byte("x")
	staleNext.TokenVersion = liveVersion + 1
	staleNext.ExpiresAt = row.ExpiresAt
	staleNext.ScopeKind = row.ScopeKind
	if err := rt.meta.RotateAPIKey(context.Background(), rt.tenantID, issued.TokenID, liveVersion-1, &staleNext); err == nil {
		t.Fatal("expected ErrNotFound for stale token_version")
	}

	// Live token still works; row unchanged.
	row2, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	if row2.JWTHash != liveHash || row2.TokenVersion != liveVersion {
		t.Fatalf("stale rotate mutated live key: hash/version changed")
	}
	if token.HashToken(newToken) != liveHash {
		t.Fatalf("live hash mismatch after stale attempt")
	}
	_ = oldToken
}

// TestRotateAPIKeySameVersionCandidateIsConflict proves the residual-window
// contract: a rotate candidate that reuses the live token_version must fail
// closed and leave hash/version untouched (even when the optimistic-lock
// oldVersion matches the row).
func TestRotateAPIKeySameVersionCandidateIsConflict(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	issueBody := `{"subject":"same-ver","ttl_seconds":3600,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(issueBody))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}

	row, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	liveHash := row.JWTHash
	liveVersion := row.TokenVersion

	// Force the bug class: new material with unchanged token_version.
	sameVerNext := *row
	sameVerNext.JWTHash = "same-version-collision"
	sameVerNext.JWTCiphertext = []byte("same-version-cipher")
	sameVerNext.TokenVersion = liveVersion
	sameVerNext.ExpiresAt = row.ExpiresAt
	sameVerNext.ScopeKind = row.ScopeKind
	if err := rt.meta.RotateAPIKey(context.Background(), rt.tenantID, issued.TokenID, liveVersion, &sameVerNext); !errors.Is(err, meta.ErrAPIKeyVersionUnchanged) {
		t.Fatalf("want ErrAPIKeyVersionUnchanged, got %v", err)
	}

	row2, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	if row2.JWTHash != liveHash || row2.TokenVersion != liveVersion {
		t.Fatalf("same-version rotate mutated live key: hash/version changed")
	}
	if token.HashToken(issued.Token) != liveHash {
		t.Fatalf("live plaintext no longer matches stored hash")
	}
}

func TestNextTokenVersionMonotonic(t *testing.T) {
	for _, old := range []int{0, 1, 42, math.MaxInt32 - 1} {
		n, err := nextTokenVersion(old)
		if err != nil {
			t.Fatalf("nextTokenVersion(%d): %v", old, err)
		}
		if n != old+1 {
			t.Fatalf("nextTokenVersion(%d)=%d, want %d", old, n, old+1)
		}
	}
	n, err := nextTokenVersion(math.MaxInt32)
	if err == nil || n != 0 {
		t.Fatalf("overflow: n=%d err=%v, want n=0 and error", n, err)
	}
}

func TestWriteControlPlaneTokenAuthzErrorMapping(t *testing.T) {
	type row struct {
		name         string
		err          error
		wantAntiEnum int
		wantExplicit int
	}
	cases := []row{
		{"backend_not_found", tenant.ErrQuotaBackendNotFound, http.StatusNotFound, http.StatusNotFound},
		// Explicit tenant: org mismatch is 403; plaintext revoke anti-enum: 404.
		{"permission_denied", tenant.ErrQuotaPermissionDenied, http.StatusNotFound, http.StatusForbidden},
		{"org_binding_missing", errTenantOrgBindingNotFound, http.StatusNotFound, http.StatusNotFound},
		{"shared_db_missing", errTenantSharedDBNotFound, http.StatusNotFound, http.StatusNotFound},
		{"provider_unsupported", errTokensProviderUnsupported, http.StatusConflict, http.StatusConflict},
		{"org_binding_lookup", fmt.Errorf("%w: boom", errTenantOrgBindingLookup), http.StatusBadGateway, http.StatusBadGateway},
		{"shared_db_lookup", fmt.Errorf("%w: boom", errTenantSharedDBLookup), http.StatusBadGateway, http.StatusBadGateway},
		{"membership_lookup", fmt.Errorf("%w: boom", errTenantPoolMembershipLookup), http.StatusBadGateway, http.StatusBadGateway},
	}
	for _, tc := range cases {
		t.Run("anti_enum/"+tc.name, func(t *testing.T) {
			rr := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPost, "/v1/tokens/revoke", nil)
			if !writeControlPlaneTokenAuthzError(rr, req, tc.err, "token not found or already revoked") {
				t.Fatal("expected response written")
			}
			if rr.Code != tc.wantAntiEnum {
				t.Fatalf("status=%d, want %d body=%s", rr.Code, tc.wantAntiEnum, rr.Body.String())
			}
		})
		t.Run("explicit/"+tc.name, func(t *testing.T) {
			rr := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPost, "/v1/tokens/generate", nil)
			if !writeControlPlaneTokenAuthzErrorExplicitTenant(rr, req, tc.err, "tenant not found") {
				t.Fatal("expected response written")
			}
			if rr.Code != tc.wantExplicit {
				t.Fatalf("status=%d, want %d body=%s", rr.Code, tc.wantExplicit, rr.Body.String())
			}
		})
	}
}

func TestWriteGenerateTenantNotActiveRecovery(t *testing.T) {
	cases := []struct {
		name   string
		live   *meta.Tenant
		getErr error
		want   int
		substr string
	}{
		{"flap_back_active", &meta.Tenant{Status: meta.TenantActive}, nil, http.StatusConflict, "retry"},
		{"suspended", &meta.Tenant{Status: meta.TenantSuspended}, nil, http.StatusForbidden, "suspended"},
		{"failed", &meta.Tenant{Status: meta.TenantFailed}, nil, http.StatusConflict, "not ready"},
		{"pending", &meta.Tenant{Status: meta.TenantPending}, nil, http.StatusConflict, "not ready"},
		{"provisioning", &meta.Tenant{Status: meta.TenantProvisioning}, nil, http.StatusConflict, "not ready"},
		{"deleting", &meta.Tenant{Status: meta.TenantDeleting}, nil, http.StatusNotFound, "not found"},
		{"deleted", &meta.Tenant{Status: meta.TenantDeleted}, nil, http.StatusNotFound, "not found"},
		{"gone", nil, meta.ErrNotFound, http.StatusNotFound, "not found"},
		{"metadb_fail", nil, fmt.Errorf("connection refused"), http.StatusBadGateway, ""},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			rr := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPost, "/v1/tokens/generate", nil)
			writeGenerateTenantNotActiveRecovery(rr, req, tc.live, tc.getErr)
			if rr.Code != tc.want {
				t.Fatalf("status=%d, want %d body=%s", rr.Code, tc.want, rr.Body.String())
			}
			if rr.Body.Len() == 0 {
				t.Fatal("expected non-empty response body (no empty 200)")
			}
			if tc.substr != "" && !strings.Contains(rr.Body.String(), tc.substr) {
				t.Fatalf("body %q missing %q", rr.Body.String(), tc.substr)
			}
		})
	}
}

func TestActivateExpiredKeyReturnsConflictNoMutation(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	// Owner-only path requires fs_scoped. Insert a disabled expired scoped key.
	past := time.Now().UTC().Add(-2 * time.Hour)
	keyID := token.NewID()
	now := time.Now().UTC()
	if err := rt.meta.InsertAPIKey(context.Background(), &meta.APIKey{
		ID: keyID, TenantID: rt.tenantID, KeyName: "expired-scoped",
		JWTCiphertext: []byte("c"), JWTHash: token.HashToken("raw-" + keyID),
		TokenVersion: 1, Status: meta.APIKeyDisabled, ScopeKind: meta.APIKeyScopeKindFS,
		IssuedAt: past, ExpiresAt: &past, CreatedAt: now, UpdatedAt: now,
	}); err != nil {
		t.Fatal(err)
	}
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/"+keyID+"/activate", strings.NewReader(`{}`))
	req.Header.Set("Authorization", "Bearer "+rt.token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = resp.Body.Close() }()
	if resp.StatusCode != http.StatusConflict {
		t.Fatalf("status=%d, want 409 body=%v", resp.StatusCode, resp.Body)
	}
	got, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, keyID)
	if err != nil {
		t.Fatal(err)
	}
	if got.Status != meta.APIKeyDisabled {
		t.Fatalf("status=%s, want still disabled", got.Status)
	}
}

func TestRefreshTokenVersionsMonotonic(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	issueBody := `{"subject":"mono","ttl_seconds":3600,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(issueBody))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}
	row0, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	v0 := row0.TokenVersion

	refresh := func(tok string) string {
		t.Helper()
		req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", strings.NewReader(`{}`))
		req.Header.Set("Authorization", "Bearer "+tok)
		req.Header.Set("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		defer func() { _ = resp.Body.Close() }()
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("refresh status=%d", resp.StatusCode)
		}
		var out refreshTokenResponse
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			t.Fatal(err)
		}
		return out.Token
	}
	tok1 := refresh(issued.Token)
	row1, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	if row1.TokenVersion != v0+1 {
		t.Fatalf("after first refresh ver=%d, want %d", row1.TokenVersion, v0+1)
	}
	_ = refresh(tok1)
	row2, err := rt.meta.GetAPIKey(context.Background(), rt.tenantID, issued.TokenID)
	if err != nil {
		t.Fatal(err)
	}
	if row2.TokenVersion != v0+2 {
		t.Fatalf("after second refresh ver=%d, want %d", row2.TokenVersion, v0+2)
	}
	// Historical oldVersion never matches again.
	probe := *row2
	probe.JWTHash = "x"
	probe.JWTCiphertext = []byte("x")
	probe.TokenVersion = v0 + 3
	if err := rt.meta.RotateAPIKey(context.Background(), rt.tenantID, issued.TokenID, v0, &probe); !errors.Is(err, meta.ErrNotFound) {
		t.Fatalf("historical oldVersion rotate: want ErrNotFound, got %v", err)
	}
}

func TestControlPlaneExplicitTenantCrossOrgForbidden(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	ctx := context.Background()
	if _, err := rt.meta.DB().ExecContext(ctx, `UPDATE tenants SET provider = ? WHERE id = ?`,
		tenant.ProviderTiDBCloudNative, rt.tenantID); err != nil {
		t.Fatal(err)
	}
	if err := rt.meta.UpsertTenantTiDBCloudOrgBinding(ctx, &meta.TenantTiDBCloudOrgBinding{
		TenantID: rt.tenantID, OrganizationID: "org-cp-1", ClusterID: "c1", BranchID: "main",
		PoolStatus: meta.TenantPoolBindingUsed, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
	}); err != nil {
		t.Fatal(err)
	}
	// Seed a token id for activate/delete paths.
	exp := time.Now().UTC().Add(time.Hour)
	seed := insertScopedTestKey(t, rt, exp)

	prov := &quotaTestProvisioner{
		iamIdentities: []*tenant.TiDBCloudAPIKeyIdentity{
			{OrganizationID: "org-other", Role: tenant.TiDBCloudRoleOrgOwner},
		},
	}
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret, Provisioner: prov})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	cpHeaders := func(req *http.Request) {
		req.Header.Set(quotaPublicKeyHeader, "pub")
		req.Header.Set(quotaPrivateKeyHeader, "priv")
	}
	assert403 := func(method, path, body string) {
		t.Helper()
		var rdr io.Reader
		if body != "" {
			rdr = strings.NewReader(body)
		}
		req, _ := http.NewRequest(method, ts.URL+path, rdr)
		cpHeaders(req)
		if body != "" {
			req.Header.Set("Content-Type", "application/json")
		}
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		defer func() { _ = resp.Body.Close() }()
		if resp.StatusCode != http.StatusForbidden {
			t.Fatalf("%s %s status=%d, want 403", method, path, resp.StatusCode)
		}
	}
	assert403(http.MethodPost, "/v1/tokens/generate?tenant_id="+rt.tenantID, "")
	assert403(http.MethodGet, "/v1/tokens?tenant_id="+rt.tenantID, "")
	assert403(http.MethodPost, "/v1/tokens/"+seed+"/activate", `{"tenant_id":"`+rt.tenantID+`"}`)
	assert403(http.MethodDelete, "/v1/tokens/"+seed+"?tenant_id="+rt.tenantID, `{}`)
}

func TestControlPlaneRevokeByAPIKeyCrossOrgNotFound(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	ctx := context.Background()
	// Issue scoped token first while tenant is still the default provider (db9-compatible).
	srvOwner := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	tsOwner := httptest.NewServer(srvOwner)
	issueReq, _ := http.NewRequest(http.MethodPost, tsOwner.URL+"/v1/tokens",
		strings.NewReader(`{"subject":"x","ttl_seconds":3600,"scopes":[{"prefix":"/s","ops":["read"]}]}`))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	tsOwner.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}

	// Then switch tenant to native + org binding for CP authz (cross-org path).
	if _, err := rt.meta.DB().ExecContext(ctx, `UPDATE tenants SET provider = ? WHERE id = ?`,
		tenant.ProviderTiDBCloudNative, rt.tenantID); err != nil {
		t.Fatal(err)
	}
	if err := rt.meta.UpsertTenantTiDBCloudOrgBinding(ctx, &meta.TenantTiDBCloudOrgBinding{
		TenantID: rt.tenantID, OrganizationID: "org-cp-1", ClusterID: "c1", BranchID: "main",
		PoolStatus: meta.TenantPoolBindingUsed, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
	}); err != nil {
		t.Fatal(err)
	}

	prov := &quotaTestProvisioner{
		iamIdentities: []*tenant.TiDBCloudAPIKeyIdentity{
			{OrganizationID: "org-other", Role: tenant.TiDBCloudRoleOrgOwner},
		},
	}
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret, Provisioner: prov})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	body := `{"api_key":` + jsonString(issued.Token) + `}`
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/revoke", strings.NewReader(body))
	req.Header.Set(quotaPublicKeyHeader, "pub")
	req.Header.Set(quotaPrivateKeyHeader, "priv")
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = resp.Body.Close() }()
	if resp.StatusCode != http.StatusNotFound {
		t.Fatalf("cross-org revoke status=%d, want 404", resp.StatusCode)
	}
}

func jsonString(s string) string {
	b, _ := json.Marshal(s)
	return string(b)
}

func insertScopedTestKey(t *testing.T, rt *authTestRuntime, exp time.Time) string {
	t.Helper()
	keyID := token.NewID()
	now := time.Now().UTC()
	if err := rt.meta.InsertAPIKey(context.Background(), &meta.APIKey{
		ID: keyID, TenantID: rt.tenantID, KeyName: "seed",
		JWTCiphertext: []byte("c"), JWTHash: token.HashToken("raw-" + keyID),
		TokenVersion: 1, Status: meta.APIKeyDisabled, ScopeKind: meta.APIKeyScopeKindFS,
		IssuedAt: now, ExpiresAt: &exp, CreatedAt: now, UpdatedAt: now,
	}); err != nil {
		t.Fatal(err)
	}
	return keyID
}

func TestRefreshLongTTLDefaultPeriodNoLockout(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Issue scoped with >365d TTL (still allowed by issue path).
	longTTL := int64(400 * 24 * 3600)
	issuePayload, _ := json.Marshal(map[string]any{
		"subject":     "long-ttl",
		"ttl_seconds": longTTL,
		"scopes":      []map[string]any{{"prefix": "/scratch", "ops": []string{"read"}}},
	})
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", bytes.NewReader(issuePayload))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}

	// Default refresh must succeed without clamping into no-shortening 400.
	refReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", strings.NewReader(`{}`))
	refReq.Header.Set("Authorization", "Bearer "+issued.Token)
	refReq.Header.Set("Content-Type", "application/json")
	refResp, err := http.DefaultClient.Do(refReq)
	if err != nil {
		t.Fatal(err)
	}
	body, _ := io.ReadAll(refResp.Body)
	_ = refResp.Body.Close()
	if refResp.StatusCode != http.StatusOK {
		t.Fatalf("long-ttl default refresh status=%d body=%s", refResp.StatusCode, body)
	}

	// Explicit oversize TTL is rejected without Duration overflow bypass.
	huge := int64(1 << 62)
	payload, _ := json.Marshal(refreshTokenRequest{TTLSeconds: &huge})
	ref2, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", bytes.NewReader(payload))
	ref2.Header.Set("Authorization", "Bearer "+rt.token) // owner path also checks ceiling
	ref2.Header.Set("Content-Type", "application/json")
	// First need a fresh owner token if owner was rotated — use rt.token (owner no-expiry).
	resp2, err := http.DefaultClient.Do(ref2)
	if err != nil {
		t.Fatal(err)
	}
	body2, _ := io.ReadAll(resp2.Body)
	_ = resp2.Body.Close()
	if resp2.StatusCode != http.StatusBadRequest {
		t.Fatalf("huge ttl status=%d body=%s, want 400", resp2.StatusCode, body2)
	}
}

func TestControlPlaneGenerateHeaderOnly(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	ctx := context.Background()

	// Switch tenant to native + bind to org for control-plane authorization.
	if _, err := rt.meta.DB().ExecContext(ctx, `UPDATE tenants SET provider = ? WHERE id = ?`,
		tenant.ProviderTiDBCloudNative, rt.tenantID); err != nil {
		t.Fatal(err)
	}
	if err := rt.meta.UpsertTenantTiDBCloudOrgBinding(ctx, &meta.TenantTiDBCloudOrgBinding{
		TenantID:       rt.tenantID,
		OrganizationID: "org-cp-1",
		ClusterID:      "cluster-1",
		BranchID:       "main",
		PoolStatus:     meta.TenantPoolBindingUsed,
		CreatedAt:      time.Now().UTC(),
		UpdatedAt:      time.Now().UTC(),
	}); err != nil {
		t.Fatal(err)
	}

	prov := &quotaTestProvisioner{
		iamIdentities: []*tenant.TiDBCloudAPIKeyIdentity{
			{OrganizationID: "org-cp-1", Role: tenant.TiDBCloudRoleOrgOwner},
		},
	}
	srv := NewWithConfig(Config{
		Meta:        rt.meta,
		Pool:        rt.pool,
		TokenSecret: rt.tokenSecret,
		Provisioner: prov,
	})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	// Header-only credentials + tenant_id query, empty body → 201.
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/generate?tenant_id="+rt.tenantID, nil)
	req.Header.Set(quotaPublicKeyHeader, "pub")
	req.Header.Set(quotaPrivateKeyHeader, "priv")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	body, _ := io.ReadAll(resp.Body)
	_ = resp.Body.Close()
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("generate status=%d body=%s, want 201", resp.StatusCode, body)
	}
	var out map[string]any
	if err := json.Unmarshal(body, &out); err != nil {
		t.Fatal(err)
	}
	if out["token"] == nil || out["token_id"] == nil || out["scope_kind"] != "owner" {
		t.Fatalf("unexpected generate response: %s", body)
	}

	// Wrong method on generate → 405.
	req2, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/tokens/generate?tenant_id="+rt.tenantID, nil)
	req2.Header.Set(quotaPublicKeyHeader, "pub")
	req2.Header.Set(quotaPrivateKeyHeader, "priv")
	resp2, err := http.DefaultClient.Do(req2)
	if err != nil {
		t.Fatal(err)
	}
	_ = resp2.Body.Close()
	if resp2.StatusCode != http.StatusMethodNotAllowed {
		t.Fatalf("wrong method status=%d, want 405", resp2.StatusCode)
	}

	// Huge ttl rejected without Duration overflow.
	hugeBody := `{"tenant_id":"` + rt.tenantID + `","ttl_seconds":` + "4611686018427387904" + `}`
	req3, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/generate", strings.NewReader(hugeBody))
	req3.Header.Set(quotaPublicKeyHeader, "pub")
	req3.Header.Set(quotaPrivateKeyHeader, "priv")
	req3.Header.Set("Content-Type", "application/json")
	resp3, err := http.DefaultClient.Do(req3)
	if err != nil {
		t.Fatal(err)
	}
	body3, _ := io.ReadAll(resp3.Body)
	_ = resp3.Body.Close()
	if resp3.StatusCode != http.StatusBadRequest {
		t.Fatalf("huge ttl generate status=%d body=%s, want 400", resp3.StatusCode, body3)
	}
}

func TestRefreshTTLOverflowRejectedOnScoped(t *testing.T) {
	rt, cleanup := newAuthRuntime(t)
	defer cleanup()
	srv := NewWithConfig(Config{Meta: rt.meta, Pool: rt.pool, TokenSecret: rt.tokenSecret})
	ts := httptest.NewServer(srv)
	defer ts.Close()

	issueBody := `{"subject":"ttl-overflow","ttl_seconds":3600,"scopes":[{"prefix":"/scratch","ops":["read"]}]}`
	issueReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens", strings.NewReader(issueBody))
	issueReq.Header.Set("Authorization", "Bearer "+rt.token)
	issueReq.Header.Set("Content-Type", "application/json")
	issueResp, err := http.DefaultClient.Do(issueReq)
	if err != nil {
		t.Fatal(err)
	}
	var issued scopedTokenResponse
	if err := json.NewDecoder(issueResp.Body).Decode(&issued); err != nil {
		t.Fatal(err)
	}
	_ = issueResp.Body.Close()
	if issueResp.StatusCode != http.StatusCreated {
		t.Fatalf("issue status=%d", issueResp.StatusCode)
	}

	huge := int64(1 << 62)
	payload, _ := json.Marshal(refreshTokenRequest{TTLSeconds: &huge})
	refReq, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/tokens/refresh", bytes.NewReader(payload))
	refReq.Header.Set("Authorization", "Bearer "+issued.Token)
	refReq.Header.Set("Content-Type", "application/json")
	refResp, err := http.DefaultClient.Do(refReq)
	if err != nil {
		t.Fatal(err)
	}
	body, _ := io.ReadAll(refResp.Body)
	_ = refResp.Body.Close()
	if refResp.StatusCode != http.StatusBadRequest {
		t.Fatalf("status=%d body=%s, want 400 for overflow ttl", refResp.StatusCode, body)
	}
}
