package admin

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/admin"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
)

type fakeAdminStore struct {
	authenticated identity.AuthenticatedUser
	overview      admin.AdminOverview
	overviewUser  string
	overviewSince time.Time

	createCalled         bool
	createPattern        string
	createReason         string
	createExpires        *time.Time
	allowlistEntries     []identity.RegistrationAllowlistEntry
	listQuery            string
	listIncludeDisabled  bool
	disabledAllowlistID  string
	allowlistEntrySerial int
}

func (fake *fakeAdminStore) AuthenticateSession(_ context.Context, _ string) (identity.AuthenticatedUser, error) {
	return fake.authenticated, nil
}

func (fake *fakeAdminStore) CreateRegistrationAllowlistEntryForUser(_ context.Context, userID string, pattern string, reason string, expiresAt *time.Time) (identity.RegistrationAllowlistEntry, error) {
	fake.createCalled = true
	fake.createPattern = pattern
	fake.createReason = reason
	fake.createExpires = expiresAt
	fake.allowlistEntrySerial++
	now := time.Date(2026, 5, 1, 1, 2, 3, 0, time.UTC)
	user := fake.authenticated.User
	entry := identity.RegistrationAllowlistEntry{
		ID:                   "ral_test_" + strconv.Itoa(fake.allowlistEntrySerial),
		Pattern:              pattern,
		Reason:               reason,
		CreatedByUserID:      userID,
		CreatedByEmail:       user.Email,
		CreatedByDisplayName: user.DisplayName,
		CreatedAt:            now,
		UpdatedAt:            now,
		ExpiresAt:            expiresAt,
	}
	fake.allowlistEntries = append([]identity.RegistrationAllowlistEntry{entry}, fake.allowlistEntries...)
	return entry, nil
}

func (fake *fakeAdminStore) DisableRegistrationAllowlistEntryForUser(_ context.Context, _ string, entryID string) (identity.RegistrationAllowlistEntry, error) {
	fake.disabledAllowlistID = entryID
	disabledAt := time.Date(2026, 5, 2, 3, 4, 5, 0, time.UTC)
	for i := range fake.allowlistEntries {
		if fake.allowlistEntries[i].ID != entryID {
			continue
		}
		fake.allowlistEntries[i].DisabledAt = &disabledAt
		fake.allowlistEntries[i].UpdatedAt = disabledAt
		return fake.allowlistEntries[i], nil
	}
	return identity.RegistrationAllowlistEntry{ID: entryID, DisabledAt: &disabledAt}, nil
}

func (fake *fakeAdminStore) ListAdminUsers(_ context.Context, _ string, _ string, _ time.Time, _ bool, _ int) ([]admin.AdminUserSummary, error) {
	return nil, nil
}

func (fake *fakeAdminStore) ListAdminWorkspaces(_ context.Context, _ string, _ string, _ time.Time, _ bool, _ int) ([]admin.AdminWorkspaceSummary, error) {
	return nil, nil
}

func (fake *fakeAdminStore) ListRegistrationAllowlistEntriesForUser(_ context.Context, _ string, query string, includeDisabled bool) ([]identity.RegistrationAllowlistEntry, error) {
	fake.listQuery = query
	fake.listIncludeDisabled = includeDisabled
	if includeDisabled {
		return fake.allowlistEntries, nil
	}
	active := make([]identity.RegistrationAllowlistEntry, 0, len(fake.allowlistEntries))
	for _, entry := range fake.allowlistEntries {
		if entry.DisabledAt == nil {
			active = append(active, entry)
		}
	}
	return active, nil
}

func (fake *fakeAdminStore) LoadAdminOverview(_ context.Context, userID string, since time.Time) (admin.AdminOverview, error) {
	fake.overviewUser = userID
	fake.overviewSince = since
	return fake.overview, nil
}

type fakeDatabasePoolController struct {
	snapshot  coredb.DBPoolSnapshot
	updateTo  coredb.DBPoolConfig
	updateErr error
	updated   bool
}

func (fake *fakeDatabasePoolController) Snapshot() coredb.DBPoolSnapshot {
	return fake.snapshot
}

func (fake *fakeDatabasePoolController) Update(cfg coredb.DBPoolConfig) (coredb.DBPoolSnapshot, error) {
	fake.updated = true
	fake.updateTo = cfg
	if fake.updateErr != nil {
		return coredb.DBPoolSnapshot{}, fake.updateErr
	}
	fake.snapshot.Config = cfg
	return fake.snapshot, nil
}

type fakeHostedCreditGrantClient struct {
	params HostedCreditGrantParams
	grant  HostedCreditGrant
	err    error
}

func (fake *fakeHostedCreditGrantClient) GrantHostedCredit(_ context.Context, params HostedCreditGrantParams) (HostedCreditGrant, error) {
	fake.params = params
	if fake.err != nil {
		return HostedCreditGrant{}, fake.err
	}
	return fake.grant, nil
}

func TestParseAdminWindow(t *testing.T) {
	t.Parallel()

	label, duration := parseAdminWindow("24h")
	require.Equal(t, adminWindow24h, label)
	require.Equal(t, 24*time.Hour, duration)

	label, duration = parseAdminWindow(" 30D ")
	require.Equal(t, adminWindow30d, label)
	require.Equal(t, 30*24*time.Hour, duration)

	label, duration = parseAdminWindow("bogus")
	require.Equal(t, adminWindow7d, label)
	require.Equal(t, 7*24*time.Hour, duration)
}

func TestParseAdminLimit(t *testing.T) {
	t.Parallel()

	require.Equal(t, 100, parseAdminLimit("", 100))
	require.Equal(t, 100, parseAdminLimit("not-a-number", 100))
	require.Equal(t, 250, parseAdminLimit("250", 100))
}

func TestAdminBoolQuery(t *testing.T) {
	t.Parallel()

	router := gin.New()
	router.GET("/admin", func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, gin.H{
			"one":   adminBoolQuery(ctx, "one"),
			"true":  adminBoolQuery(ctx, "true"),
			"false": adminBoolQuery(ctx, "false"),
			"empty": adminBoolQuery(ctx, "empty"),
		})
	})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/admin?one=1&true=TrUe&false=false", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `{"one":true,"true":true,"false":false,"empty":false}`, recorder.Body.String())
}

func TestHandlerOverviewUsesAuthenticatedAdminAndWindow(t *testing.T) {
	t.Parallel()

	fake := &fakeAdminStore{overview: admin.AdminOverview{TotalUsers: 3}}
	handler := &SystemAdminHandler{adminStore: fake, allowlistStore: fake}
	router := gin.New()
	router.GET("/api/v1/admin/overview", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuth(handler.Overview))

	before := time.Now().UTC().Add(-24*time.Hour - time.Minute)
	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/admin/overview?window=24h", nil)
	router.ServeHTTP(recorder, request)
	after := time.Now().UTC().Add(-24*time.Hour + time.Minute)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "usr_admin", fake.overviewUser)
	require.True(t, fake.overviewSince.After(before), fake.overviewSince.String())
	require.True(t, fake.overviewSince.Before(after), fake.overviewSince.String())
	require.JSONEq(t, `{"window":"24h","overview":{"windowHours":0,"totalWorkspaces":0,"newWorkspaces":0,"activeWorkspaces":0,"totalUsers":3,"verifiedUsers":0,"activeUsers":0,"recentLoginUsers":0,"totalAgents":0,"activeAgents":0,"totalMachines":0,"onlineMachines":0,"pendingWorkspaceInvites":0,"messageCount":0,"humanMessageCount":0,"agentMessageCount":0}}`, recorder.Body.String())
}

func TestHandlerRegistrationAllowlistCreateRejectsInvalidExpiresAt(t *testing.T) {
	t.Parallel()

	fake := &fakeAdminStore{}
	handler := &SystemAdminHandler{adminStore: fake, allowlistStore: fake}
	router := gin.New()
	router.POST("/api/v1/admin/registration-allowlist", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminRegistrationAllowlistCreateRequest](handler.CreateRegistrationAllowlist))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/registration-allowlist", bytes.NewBufferString(`{"pattern":"*@example.com","expiresAt":"soon"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Contains(t, recorder.Body.String(), "expiresAt must be RFC3339")
	require.False(t, fake.createCalled)
}

func TestHandlerRegistrationAllowlistCreateParsesDTO(t *testing.T) {
	t.Parallel()

	fake := &fakeAdminStore{}
	handler := &SystemAdminHandler{adminStore: fake, allowlistStore: fake}
	router := gin.New()
	router.POST("/api/v1/admin/registration-allowlist", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminRegistrationAllowlistCreateRequest](handler.CreateRegistrationAllowlist))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/registration-allowlist", bytes.NewBufferString(`{"pattern":"*@example.com","reason":"launch","expiresAt":"2026-05-01T01:02:03+08:00"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.True(t, fake.createCalled)
	require.Equal(t, "*@example.com", fake.createPattern)
	require.Equal(t, "launch", fake.createReason)
	require.NotNil(t, fake.createExpires)
	require.Equal(t, "2026-04-30T17:02:03Z", fake.createExpires.Format(time.RFC3339))
}

func TestRegistrationAllowlistRoutesCreateListDisable(t *testing.T) {
	t.Parallel()

	fake := &fakeAdminStore{
		authenticated: identity.AuthenticatedUser{
			User: identity.User{
				ID:          "usr_admin",
				Email:       "admin@example.com",
				DisplayName: "Admin",
				IsAdmin:     true,
			},
		},
	}
	router := gin.New()
	RegisterRoutes(router, Dependencies{Store: fake})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/registration-allowlist", bytes.NewBufferString(`{"pattern":"@example.com","reason":"ops rollout"}`))
	request.Header.Set("Authorization", "Bearer session-token")
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	var created dto.AdminRegistrationAllowlistEntry
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &created))
	require.Equal(t, "ral_test_1", created.ID)
	require.Equal(t, "@example.com", created.Pattern)
	require.Equal(t, "ops rollout", created.Reason)
	require.Equal(t, "usr_admin", created.CreatedByUserID)

	recorder = httptest.NewRecorder()
	request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/registration-allowlist?query=example.com", nil)
	request.Header.Set("Authorization", "Bearer session-token")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	var activeEntries []dto.AdminRegistrationAllowlistEntry
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &activeEntries))
	require.Len(t, activeEntries, 1)
	require.Equal(t, "example.com", fake.listQuery)
	require.False(t, fake.listIncludeDisabled)

	recorder = httptest.NewRecorder()
	request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/registration-allowlist/"+created.ID+"/disable", bytes.NewBufferString(`{}`))
	request.Header.Set("Authorization", "Bearer session-token")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	require.Equal(t, created.ID, fake.disabledAllowlistID)
	var disabled dto.AdminRegistrationAllowlistEntry
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &disabled))
	require.NotNil(t, disabled.DisabledAt)

	recorder = httptest.NewRecorder()
	request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/registration-allowlist?includeDisabled=true", nil)
	request.Header.Set("Authorization", "Bearer session-token")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	var disabledEntries []dto.AdminRegistrationAllowlistEntry
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &disabledEntries))
	require.Len(t, disabledEntries, 1)
	require.NotNil(t, disabledEntries[0].DisabledAt)
	require.True(t, fake.listIncludeDisabled)
}

func TestHandlerGrantHostedCreditForwardsOperatorGrantToMaaS(t *testing.T) {
	t.Parallel()

	expiresAt := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
	createdAt := time.Date(2026, 6, 10, 8, 0, 0, 0, time.UTC)
	client := &fakeHostedCreditGrantClient{
		grant: HostedCreditGrant{
			GrantID:           "hcg_1",
			OperationID:       "hgo_1",
			Created:           true,
			OrganizationID:    "org_1",
			CreditUnit:        "agentteam_credit",
			CreditAmount:      3000,
			CreditAmountAtoms: 3_000_000_000,
			GrantSourceType:   "operator_gift",
			GrantReasonRef:    "support-ticket-1",
			Refundable:        false,
			ExpiresAt:         &expiresAt,
			OperatorIdentity:  "usr_admin",
			SourceRef:         "ticket-1",
			CreatedAt:         createdAt,
			UpdatedAt:         createdAt,
		},
	}
	handler := &SystemAdminHandler{creditGrants: client}
	router := gin.New()
	router.POST("/api/v1/admin/orgs/:orgId/hosted-credit-grants", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminHostedCreditGrantCreateRequest](handler.GrantHostedCredit))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/orgs/org_1/hosted-credit-grants", bytes.NewBufferString(`{
		"creditAmount":3000,
		"grantReasonRef":"support-ticket-1",
		"expiresAt":"2026-07-01T08:00:00+08:00",
		"sourceRef":"ticket-1"
	}`))
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Idempotency-Key", "grant-idem")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusCreated, recorder.Code, recorder.Body.String())
	require.Equal(t, "org_1", client.params.OrganizationID)
	require.Equal(t, "grant-idem", client.params.IdempotencyKey)
	require.Equal(t, "agentteam_credit", client.params.CreditUnit)
	require.Equal(t, int64(3000), client.params.CreditAmount)
	require.Zero(t, client.params.CreditAmountAtoms)
	require.Equal(t, "operator_gift", client.params.GrantSourceType)
	require.Equal(t, "support-ticket-1", client.params.GrantReasonRef)
	require.False(t, client.params.Refundable)
	require.NotNil(t, client.params.ExpiresAt)
	require.Equal(t, expiresAt, *client.params.ExpiresAt)
	require.Equal(t, "usr_admin", client.params.OperatorIdentity)
	require.Equal(t, "ticket-1", client.params.SourceRef)
	require.JSONEq(t, `{
		"grantId":"hcg_1",
		"operationId":"hgo_1",
		"created":true,
		"organizationId":"org_1",
		"creditUnit":"agentteam_credit",
		"creditAmount":3000,
		"creditAmountAtoms":3000000000,
		"grantSourceType":"operator_gift",
		"grantReasonRef":"support-ticket-1",
		"refundable":false,
		"expiresAt":"2026-07-01T00:00:00Z",
		"operatorIdentity":"usr_admin",
		"sourceRef":"ticket-1",
		"createdAt":"2026-06-10T08:00:00Z",
		"updatedAt":"2026-06-10T08:00:00Z"
	}`, recorder.Body.String())
}

func TestHandlerGrantHostedCreditReturnsOKForIdempotentReplay(t *testing.T) {
	t.Parallel()

	client := &fakeHostedCreditGrantClient{
		grant: HostedCreditGrant{
			GrantID:           "hcg_1",
			OperationID:       "hgo_1",
			Created:           false,
			OrganizationID:    "org_1",
			CreditUnit:        "agentteam_credit",
			CreditAmountAtoms: 250_000_000,
			GrantSourceType:   "operator_gift",
			GrantReasonRef:    "support-ticket-1",
		},
	}
	handler := &SystemAdminHandler{creditGrants: client}
	router := gin.New()
	router.POST("/api/v1/admin/orgs/:orgId/hosted-credit-grants", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminHostedCreditGrantCreateRequest](handler.GrantHostedCredit))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/orgs/org_1/hosted-credit-grants", bytes.NewBufferString(`{
		"creditAmountAtoms":250000000,
		"grantReasonRef":"support-ticket-1"
	}`))
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Idempotency-Key", "grant-idem")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	require.Zero(t, client.params.CreditAmount)
	require.Equal(t, int64(250_000_000), client.params.CreditAmountAtoms)
}

func TestHandlerGrantHostedCreditRejectsInvalidInput(t *testing.T) {
	t.Parallel()

	handler := &SystemAdminHandler{creditGrants: &fakeHostedCreditGrantClient{}}
	router := gin.New()
	router.POST("/api/v1/admin/orgs/:orgId/hosted-credit-grants", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminHostedCreditGrantCreateRequest](handler.GrantHostedCredit))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/orgs/org_1/hosted-credit-grants", bytes.NewBufferString(`{
		"creditAmount":3000,
		"creditAmountAtoms":3000000000,
		"grantReasonRef":"support-ticket-1"
	}`))
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Idempotency-Key", "grant-idem")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Contains(t, recorder.Body.String(), "provide exactly one of creditAmount or creditAmountAtoms")
}

func TestHandlerLoadDatabasePoolReturnsSnapshot(t *testing.T) {
	t.Parallel()

	dbPool := &fakeDatabasePoolController{
		snapshot: coredb.DBPoolSnapshot{
			Config: coredb.DBPoolConfig{
				MaxOpenConns:    40,
				MaxIdleConns:    20,
				ConnMaxLifetime: 5 * time.Minute,
				ConnMaxIdleTime: 2 * time.Minute,
			},
			Stats: coredb.DBPoolStats{
				MaxOpenConnections: 40,
				OpenConnections:    7,
				InUse:              3,
				Idle:               4,
				WaitCount:          12,
				WaitDuration:       time.Second,
			},
		},
	}
	handler := &SystemAdminHandler{dbPool: dbPool}
	router := gin.New()
	router.GET("/api/v1/admin/runtime/db-pool", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuth(handler.LoadDatabasePool))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/admin/runtime/db-pool", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `{
		"config":{"maxOpenConns":40,"maxIdleConns":20,"connMaxLifetime":"5m0s","connMaxIdleTime":"2m0s"},
		"stats":{"maxOpenConnections":40,"openConnections":7,"inUse":3,"idle":4,"waitCount":12,"waitDuration":"1s","maxIdleClosed":0,"maxIdleTimeClosed":0,"maxLifetimeClosed":0}
	}`, recorder.Body.String())
}

func TestHandlerPatchDatabasePoolOverlaysAndUpdatesSnapshot(t *testing.T) {
	t.Parallel()

	dbPool := &fakeDatabasePoolController{
		snapshot: coredb.DBPoolSnapshot{
			Config: coredb.DBPoolConfig{
				MaxOpenConns:    40,
				MaxIdleConns:    20,
				ConnMaxLifetime: 5 * time.Minute,
				ConnMaxIdleTime: 2 * time.Minute,
			},
		},
	}
	handler := &SystemAdminHandler{dbPool: dbPool}
	router := gin.New()
	router.PATCH("/api/v1/admin/runtime/db-pool", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminDBPoolPatchRequest](handler.PatchDatabasePool))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/admin/runtime/db-pool", bytes.NewBufferString(`{"maxOpenConns":16,"connMaxIdleTime":"45s"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.True(t, dbPool.updated)
	require.Equal(t, 16, dbPool.updateTo.MaxOpenConns)
	require.Equal(t, 20, dbPool.updateTo.MaxIdleConns)
	require.Equal(t, 5*time.Minute, dbPool.updateTo.ConnMaxLifetime)
	require.Equal(t, 45*time.Second, dbPool.updateTo.ConnMaxIdleTime)
	require.JSONEq(t, `{
		"config":{"maxOpenConns":16,"maxIdleConns":20,"connMaxLifetime":"5m0s","connMaxIdleTime":"45s"},
		"stats":{"maxOpenConnections":0,"openConnections":0,"inUse":0,"idle":0,"waitCount":0,"waitDuration":"0s","maxIdleClosed":0,"maxIdleTimeClosed":0,"maxLifetimeClosed":0}
	}`, recorder.Body.String())
}

func TestHandlerPatchDatabasePoolRejectsInvalidDuration(t *testing.T) {
	t.Parallel()

	handler := &SystemAdminHandler{dbPool: &fakeDatabasePoolController{snapshot: coredb.DBPoolSnapshot{}}}
	router := gin.New()
	router.PATCH("/api/v1/admin/runtime/db-pool", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_admin", IsAdmin: true}})
	}, api.HandleAuthJSON[dto.AdminDBPoolPatchRequest](handler.PatchDatabasePool))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/admin/runtime/db-pool", bytes.NewBufferString(`{"connMaxLifetime":"soon"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Contains(t, recorder.Body.String(), "connMaxLifetime must be a valid Go duration")
}
