package agentrouter

import (
	"context"

	"connectrpc.com/connect"

	aikeysv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/aikeys/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/aikeys/v1/aikeysv1connect"
	catalogv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/catalog/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/catalog/v1/catalogv1connect"
	clientsv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/clients/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/clients/v1/clientsv1connect"
	identityv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/identity/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/identity/v1/identityv1connect"
	inferencev1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/inference/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/inference/v1/inferencev1connect"
	mcpv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/mcp/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/mcp/v1/mcpv1connect"
	telemetryv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/telemetry/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/telemetry/v1/telemetryv1connect"
	tenancyv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/tenancy/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/tenancy/v1/tenancyv1connect"
)

// --- MeService -------------------------------------------------------

// MeClient wraps the MeService generated client.
type MeClient struct {
	c identityv1connect.MeServiceClient
}

// Login starts the OIDC PKCE flow.
func (m *MeClient) Login(ctx context.Context, req LoginRequest) (*identityv1.LoginResponse, error) {
	resp, err := m.c.Login(ctx, connect.NewRequest(&identityv1.LoginRequest{
		Issuer:              req.Issuer,
		RedirectUri:         req.RedirectURI,
		CodeChallenge:       req.CodeChallenge,
		CodeChallengeMethod: req.CodeChallengeMethod,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Logout invalidates the caller's session.
func (m *MeClient) Logout(ctx context.Context) error {
	_, err := m.c.Logout(ctx, connect.NewRequest(&identityv1.LogoutRequest{}))
	return wrap(err)
}

// Get returns the authenticated identity.
func (m *MeClient) Get(ctx context.Context) (*identityv1.GetMeResponse, error) {
	resp, err := m.c.GetMe(ctx, connect.NewRequest(&identityv1.GetMeRequest{}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// IssueKey issues a new long-lived API key. The plaintext secret is
// returned exactly once.
func (m *MeClient) IssueKey(ctx context.Context, req *identityv1.IssueMyKeyRequest) (*identityv1.IssueMyKeyResponse, error) {
	resp, err := m.c.IssueMyKey(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// ListKeys returns the caller's API keys (metadata only).
func (m *MeClient) ListKeys(ctx context.Context) (*identityv1.ListMyKeysResponse, error) {
	resp, err := m.c.ListMyKeys(ctx, connect.NewRequest(&identityv1.ListMyKeysRequest{}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// RevokeKey revokes a key by id.
func (m *MeClient) RevokeKey(ctx context.Context, keyID string) error {
	_, err := m.c.RevokeMyKey(ctx, connect.NewRequest(&identityv1.RevokeMyKeyRequest{KeyId: keyID}))
	return wrap(err)
}

// LoginRequest is the input to MeClient.Login.
type LoginRequest struct {
	Issuer              string
	RedirectURI         string
	CodeChallenge       string
	CodeChallengeMethod string
}

// --- IdentityService -------------------------------------------------

// IdentityClient wraps the IdentityService generated client.
type IdentityClient struct {
	c identityv1connect.IdentityServiceClient
}

// ResolveAPIKey resolves an API key plaintext to its owning Identity.
// Internal-only on the cluster (mTLS); included here so SDK consumers
// running against a local stack can exercise the surface.
func (i *IdentityClient) ResolveAPIKey(ctx context.Context, key string) (*identityv1.ResolveApiKeyResponse, error) {
	resp, err := i.c.ResolveApiKey(ctx, connect.NewRequest(&identityv1.ResolveApiKeyRequest{Key: key}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// --- TenancyService --------------------------------------------------

// CustomersClient wraps the TenancyService client (customer-level CRUD).
type CustomersClient struct {
	c tenancyv1connect.TenancyServiceClient
}

// Create creates a new customer.
func (cu *CustomersClient) Create(ctx context.Context, customerID, displayName, description string) (*tenancyv1.Customer, error) {
	resp, err := cu.c.CreateCustomer(ctx, connect.NewRequest(&tenancyv1.CreateCustomerRequest{
		CustomerId:  customerID,
		DisplayName: displayName,
		Description: description,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Get returns a customer by slug.
func (cu *CustomersClient) Get(ctx context.Context, customerID string) (*tenancyv1.Customer, error) {
	resp, err := cu.c.GetCustomer(ctx, connect.NewRequest(&tenancyv1.GetCustomerRequest{CustomerId: customerID}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// List returns all customers visible to the caller.
func (cu *CustomersClient) List(ctx context.Context) (*tenancyv1.ListCustomersResponse, error) {
	resp, err := cu.c.ListCustomers(ctx, connect.NewRequest(&tenancyv1.ListCustomersRequest{}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Update mutates display_name / description on a customer.
func (cu *CustomersClient) Update(ctx context.Context, req *tenancyv1.UpdateCustomerRequest) (*tenancyv1.Customer, error) {
	resp, err := cu.c.UpdateCustomer(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Archive soft-deletes a customer.
func (cu *CustomersClient) Archive(ctx context.Context, customerID string) error {
	_, err := cu.c.ArchiveCustomer(ctx, connect.NewRequest(&tenancyv1.ArchiveCustomerRequest{CustomerId: customerID}))
	return wrap(err)
}

// --- ProjectsService -------------------------------------------------

// ProjectsClient wraps the ProjectsService client.
type ProjectsClient struct {
	c tenancyv1connect.ProjectsServiceClient
}

// Create creates a project under a customer.
func (p *ProjectsClient) Create(ctx context.Context, customerID, projectID, displayName, description string) (*tenancyv1.Project, error) {
	resp, err := p.c.CreateProject(ctx, connect.NewRequest(&tenancyv1.CreateProjectRequest{
		CustomerId:  customerID,
		ProjectId:   projectID,
		DisplayName: displayName,
		Description: description,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Get returns a project by id.
func (p *ProjectsClient) Get(ctx context.Context, customerID, projectID string) (*tenancyv1.Project, error) {
	resp, err := p.c.GetProject(ctx, connect.NewRequest(&tenancyv1.GetProjectRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// List returns projects under a customer.
func (p *ProjectsClient) List(ctx context.Context, customerID string) (*tenancyv1.ListProjectsResponse, error) {
	resp, err := p.c.ListProjects(ctx, connect.NewRequest(&tenancyv1.ListProjectsRequest{CustomerId: customerID}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Update updates mutable project fields.
func (p *ProjectsClient) Update(ctx context.Context, req *tenancyv1.UpdateProjectRequest) (*tenancyv1.Project, error) {
	resp, err := p.c.UpdateProject(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Archive soft-deletes a project.
func (p *ProjectsClient) Archive(ctx context.Context, customerID, projectID string) error {
	_, err := p.c.ArchiveProject(ctx, connect.NewRequest(&tenancyv1.ArchiveProjectRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
	}))
	return wrap(err)
}

// AddMember grants an identity access to a project under a role.
func (p *ProjectsClient) AddMember(ctx context.Context, customerID, projectID, identityID, role string) (*tenancyv1.ProjectMember, error) {
	resp, err := p.c.AddProjectMember(ctx, connect.NewRequest(&tenancyv1.AddProjectMemberRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		IdentityId: identityID,
		Role:       role,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// ListMembers returns all members of a project and their roles.
func (p *ProjectsClient) ListMembers(ctx context.Context, customerID, projectID string) (*tenancyv1.ListProjectMembersResponse, error) {
	resp, err := p.c.ListProjectMembers(ctx, connect.NewRequest(&tenancyv1.ListProjectMembersRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// UpdateMember updates an existing member's role.
func (p *ProjectsClient) UpdateMember(ctx context.Context, customerID, projectID, identityID, role string) (*tenancyv1.ProjectMember, error) {
	resp, err := p.c.UpdateProjectMember(ctx, connect.NewRequest(&tenancyv1.UpdateProjectMemberRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		IdentityId: identityID,
		Role:       role,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// RemoveMember revokes a member's project access.
func (p *ProjectsClient) RemoveMember(ctx context.Context, customerID, projectID, identityID string) error {
	_, err := p.c.RemoveProjectMember(ctx, connect.NewRequest(&tenancyv1.RemoveProjectMemberRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		IdentityId: identityID,
	}))
	return wrap(err)
}

// --- CatalogService --------------------------------------------------

// CatalogClient wraps the CatalogService client.
type CatalogClient struct {
	c catalogv1connect.CatalogServiceClient
}

// ListProviders returns every catalog provider.
func (cl *CatalogClient) ListProviders(ctx context.Context) (*catalogv1.ListProvidersResponse, error) {
	resp, err := cl.c.ListProviders(ctx, connect.NewRequest(&catalogv1.ListProvidersRequest{}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// GetProvider returns one provider by id.
func (cl *CatalogClient) GetProvider(ctx context.Context, id string) (*catalogv1.Provider, error) {
	resp, err := cl.c.GetProvider(ctx, connect.NewRequest(&catalogv1.GetProviderRequest{Id: id}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetProvider(), nil
}

// ListModels returns catalog models, optionally filtered by provider.
func (cl *CatalogClient) ListModels(ctx context.Context, providerID string) (*catalogv1.ListModelsResponse, error) {
	resp, err := cl.c.ListModels(ctx, connect.NewRequest(&catalogv1.ListModelsRequest{ProviderId: providerID}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// GetModel returns one model by id.
func (cl *CatalogClient) GetModel(ctx context.Context, id string) (*catalogv1.Model, error) {
	resp, err := cl.c.GetModel(ctx, connect.NewRequest(&catalogv1.GetModelRequest{Id: id}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetModel(), nil
}

// UpsertProvider creates or replaces a provider.
func (cl *CatalogClient) UpsertProvider(ctx context.Context, req *catalogv1.UpsertProviderRequest) (*catalogv1.Provider, error) {
	resp, err := cl.c.UpsertProvider(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// UpsertModel creates or replaces a model.
func (cl *CatalogClient) UpsertModel(ctx context.Context, req *catalogv1.UpsertModelRequest) (*catalogv1.Model, error) {
	resp, err := cl.c.UpsertModel(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// SetProviderCredential stores or rotates the provider's credential.
func (cl *CatalogClient) SetProviderCredential(ctx context.Context, id, credential string) (*catalogv1.Provider, error) {
	resp, err := cl.c.SetProviderCredential(ctx, connect.NewRequest(&catalogv1.SetProviderCredentialRequest{
		Id:         id,
		Credential: credential,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// DeleteProvider removes a provider. Refuses while models still reference it.
func (cl *CatalogClient) DeleteProvider(ctx context.Context, id string) error {
	_, err := cl.c.DeleteProvider(ctx, connect.NewRequest(&catalogv1.DeleteProviderRequest{Id: id}))
	return wrap(err)
}

// DeleteModel removes a model entry.
func (cl *CatalogClient) DeleteModel(ctx context.Context, id string) error {
	_, err := cl.c.DeleteModel(ctx, connect.NewRequest(&catalogv1.DeleteModelRequest{Id: id}))
	return wrap(err)
}

// AssignModelToProject grants a project access to a catalog model.
func (cl *CatalogClient) AssignModelToProject(ctx context.Context, customerID, projectID, modelID string) (*catalogv1.AssignModelToProjectResponse, error) {
	resp, err := cl.c.AssignModelToProject(ctx, connect.NewRequest(&catalogv1.AssignModelToProjectRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ModelId:    modelID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// ListProjectModels returns every catalog model assigned to a project.
func (cl *CatalogClient) ListProjectModels(ctx context.Context, customerID, projectID string) (*catalogv1.ListProjectModelsResponse, error) {
	resp, err := cl.c.ListProjectModels(ctx, connect.NewRequest(&catalogv1.ListProjectModelsRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// UnassignModelFromProject removes a project's access to a catalog model.
func (cl *CatalogClient) UnassignModelFromProject(ctx context.Context, customerID, projectID, modelID string) (*catalogv1.UnassignModelFromProjectResponse, error) {
	resp, err := cl.c.UnassignModelFromProject(ctx, connect.NewRequest(&catalogv1.UnassignModelFromProjectRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ModelId:    modelID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// --- AIKeysService ---------------------------------------------------

// AIKeysClient wraps the AiKeysService client (BYOK keys).
type AIKeysClient struct {
	c aikeysv1connect.AiKeysServiceClient
}

// Register stores a provider API key under a project. userScope=true registers
// a per-user key private to the caller that overrides the project key for the
// caller's own inference traffic; userScope=false registers a shared
// project-level key (requires the caller to be the project owner).
func (a *AIKeysClient) Register(ctx context.Context, customerID, projectID string, provider aikeysv1.Provider, name, secret string, userScope bool) (*aikeysv1.AiKey, error) {
	resp, err := a.c.Register(ctx, connect.NewRequest(&aikeysv1.RegisterRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		Provider:   provider,
		Name:       name,
		Secret:     secret,
		UserScope:  userScope,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetKey(), nil
}

// Get returns one BYOK key record.
func (a *AIKeysClient) Get(ctx context.Context, customerID, projectID, id string) (*aikeysv1.AiKey, error) {
	resp, err := a.c.Get(ctx, connect.NewRequest(&aikeysv1.GetRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		Id:         id,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetKey(), nil
}

// List returns all BYOK keys for a project (metadata only).
func (a *AIKeysClient) List(ctx context.Context, customerID, projectID string) ([]*aikeysv1.AiKey, error) {
	resp, err := a.c.List(ctx, connect.NewRequest(&aikeysv1.ListRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetKeys(), nil
}

// Revoke marks a BYOK key revoked.
func (a *AIKeysClient) Revoke(ctx context.Context, customerID, projectID, id string) error {
	_, err := a.c.Revoke(ctx, connect.NewRequest(&aikeysv1.RevokeRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		Id:         id,
	}))
	return wrap(err)
}

// Fetch returns the plaintext provider secret. Internal-only on the
// cluster (service-account scope); included here so SDK consumers
// running against a local stack can exercise the surface.
func (a *AIKeysClient) Fetch(ctx context.Context, id string) (*aikeysv1.FetchResponse, error) {
	resp, err := a.c.Fetch(ctx, connect.NewRequest(&aikeysv1.FetchRequest{Id: id}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// --- InferenceIngressService -----------------------------------------

// InferenceClient wraps InferenceIngressService.
type InferenceClient struct {
	c inferencev1connect.InferenceIngressServiceClient
}

// PromptOption tunes a Prompt call.
type PromptOption func(*inferencev1.PromptRequest)

// WithModel selects a non-default model.
func WithModel(model string) PromptOption {
	return func(r *inferencev1.PromptRequest) { r.Model = model }
}

// WithKeyID pins the request to a specific client-key id (UUID).
func WithKeyID(id string) PromptOption {
	return func(r *inferencev1.PromptRequest) { r.KeyId = id }
}

// WithIdempotencyKey attaches an idempotency key for safe retry.
func WithIdempotencyKey(k string) PromptOption {
	return func(r *inferencev1.PromptRequest) { r.IdempotencyKey = k }
}

// PromptResult is the SDK-shaped response.
type PromptResult struct {
	Completion       string
	Model            string
	DataplaneID      string
	PromptTokens     int32
	CompletionTokens int32
}

// Prompt issues a prompt under (customerID, projectID).
func (i *InferenceClient) Prompt(ctx context.Context, customerID, projectID, prompt string, opts ...PromptOption) (*PromptResult, error) {
	req := &inferencev1.PromptRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		Prompt:     prompt,
	}
	for _, o := range opts {
		o(req)
	}
	resp, err := i.c.Prompt(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return &PromptResult{
		Completion:       resp.Msg.GetCompletion(),
		Model:            resp.Msg.GetModel(),
		DataplaneID:      resp.Msg.GetDataplaneId(),
		PromptTokens:     resp.Msg.GetPromptTokens(),
		CompletionTokens: resp.Msg.GetCompletionTokens(),
	}, nil
}

// --- ClientsService --------------------------------------------------

// ClientsClient wraps the ClientsService client.
type ClientsClient struct {
	c clientsv1connect.ClientsServiceClient
}

// Create creates a new client plus its bootstrap key. The api_key_once
// field of the response is returned exactly once.
func (cl *ClientsClient) Create(ctx context.Context, req *clientsv1.CreateClientWithKeyRequest) (*clientsv1.CreateClientWithKeyResponse, error) {
	resp, err := cl.c.CreateClientWithKey(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// List returns every client under (customerID, projectID).
func (cl *ClientsClient) List(ctx context.Context, customerID, projectID string) ([]*clientsv1.Client, error) {
	resp, err := cl.c.ListClients(ctx, connect.NewRequest(&clientsv1.ListClientsRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetClients(), nil
}

// Get returns a single client.
func (cl *ClientsClient) Get(ctx context.Context, customerID, projectID, clientID string) (*clientsv1.Client, error) {
	resp, err := cl.c.GetClient(ctx, connect.NewRequest(&clientsv1.GetClientRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ClientId:   clientID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetClient(), nil
}

// UpdateTitle updates the client's editable title.
func (cl *ClientsClient) UpdateTitle(ctx context.Context, customerID, projectID, clientID, title string) (*clientsv1.Client, error) {
	resp, err := cl.c.UpdateClientTitle(ctx, connect.NewRequest(&clientsv1.UpdateClientTitleRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ClientId:   clientID,
		Title:      title,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg.GetClient(), nil
}

// AddKey attaches an existing key to a client.
func (cl *ClientsClient) AddKey(ctx context.Context, customerID, projectID, clientID, keyID string) error {
	_, err := cl.c.AddKeyToClient(ctx, connect.NewRequest(&clientsv1.AddKeyToClientRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ClientId:   clientID,
		KeyId:      keyID,
	}))
	return wrap(err)
}

// RotateKey creates a new key attached to a client and may schedule the
// prior key's disable / destroy lifecycle.
func (cl *ClientsClient) RotateKey(ctx context.Context, req *clientsv1.RotateKeyInClientRequest) (*clientsv1.RotateKeyInClientResponse, error) {
	resp, err := cl.c.RotateKeyInClient(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// DisableKey disables a key version (reversible).
func (cl *ClientsClient) DisableKey(ctx context.Context, customerID, projectID, clientID, keyVersion string) error {
	_, err := cl.c.DisableKeyVersionInClient(ctx, connect.NewRequest(&clientsv1.DisableKeyVersionInClientRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ClientId:   clientID,
		KeyVersion: keyVersion,
	}))
	return wrap(err)
}

// EnableKey re-enables a disabled key version.
func (cl *ClientsClient) EnableKey(ctx context.Context, customerID, projectID, clientID, keyVersion string) error {
	_, err := cl.c.EnableKeyVersionInClient(ctx, connect.NewRequest(&clientsv1.EnableKeyVersionInClientRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ClientId:   clientID,
		KeyVersion: keyVersion,
	}))
	return wrap(err)
}

// DestroyKey permanently destroys a key version.
func (cl *ClientsClient) DestroyKey(ctx context.Context, customerID, projectID, clientID, keyVersion string) error {
	_, err := cl.c.DestroyKeyVersionInClient(ctx, connect.NewRequest(&clientsv1.DestroyKeyVersionInClientRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ClientId:   clientID,
		KeyVersion: keyVersion,
	}))
	return wrap(err)
}

// EmergencyRevoke disables a key immediately and optionally creates a
// replacement.
func (cl *ClientsClient) EmergencyRevoke(ctx context.Context, req *clientsv1.EmergencyRevokeKeyVersionRequest) (*clientsv1.EmergencyRevokeKeyVersionResponse, error) {
	resp, err := cl.c.EmergencyRevokeKeyVersion(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// --- TelemetryService ------------------------------------------------

// TelemetryClient wraps the TelemetryService client.
type TelemetryClient struct {
	c telemetryv1connect.TelemetryServiceClient
}

// Create creates a new telemetry export configuration.
func (t *TelemetryClient) Create(ctx context.Context, req *telemetryv1.CreateTelemetryConfigRequest) (*telemetryv1.TelemetryConfig, error) {
	resp, err := t.c.CreateTelemetryConfig(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// List returns every telemetry config under (customerID, projectID),
// optionally filtered by telemetryType and enabled tri-state.
func (t *TelemetryClient) List(ctx context.Context, customerID, projectID, telemetryType, enabled string) (*telemetryv1.ListTelemetryConfigsResponse, error) {
	resp, err := t.c.ListTelemetryConfigs(ctx, connect.NewRequest(&telemetryv1.ListTelemetryConfigsRequest{
		CustomerId:    customerID,
		ProjectId:     projectID,
		TelemetryType: telemetryType,
		Enabled:       enabled,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Get returns one telemetry config.
func (t *TelemetryClient) Get(ctx context.Context, customerID, projectID, configID string) (*telemetryv1.TelemetryConfig, error) {
	resp, err := t.c.GetTelemetryConfig(ctx, connect.NewRequest(&telemetryv1.GetTelemetryConfigRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ConfigId:   configID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Update mutates a telemetry config.
func (t *TelemetryClient) Update(ctx context.Context, req *telemetryv1.UpdateTelemetryConfigRequest) (*telemetryv1.TelemetryConfig, error) {
	resp, err := t.c.UpdateTelemetryConfig(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Delete removes a telemetry config.
func (t *TelemetryClient) Delete(ctx context.Context, customerID, projectID, configID string) error {
	_, err := t.c.DeleteTelemetryConfig(ctx, connect.NewRequest(&telemetryv1.DeleteTelemetryConfigRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		ConfigId:   configID,
	}))
	return wrap(err)
}

// --- MCP (Profiles + Catalog) ----------------------------------------

// MCPClient wraps the MCP surface (profiles + catalog).
type MCPClient struct {
	profiles mcpv1connect.MCPProfilesServiceClient
	catalog  mcpv1connect.MCPCatalogServiceClient
}

// Profiles returns a handle to MCPProfilesService.
func (m *MCPClient) Profiles() *MCPProfilesClient { return &MCPProfilesClient{c: m.profiles} }

// Catalog returns a handle to MCPCatalogService.
func (m *MCPClient) Catalog() *MCPCatalogClient { return &MCPCatalogClient{c: m.catalog} }

// MCPProfilesClient wraps MCPProfilesService.
type MCPProfilesClient struct {
	c mcpv1connect.MCPProfilesServiceClient
}

// Create registers a new MCP profile for a user.
func (mp *MCPProfilesClient) Create(ctx context.Context, req *mcpv1.CreateMCPProfileRequest) (*mcpv1.MCPProfile, error) {
	resp, err := mp.c.CreateMCPProfile(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Get returns one MCP profile.
func (mp *MCPProfilesClient) Get(ctx context.Context, customerID, projectID, userID, profileID string) (*mcpv1.MCPProfile, error) {
	resp, err := mp.c.GetMCPProfile(ctx, connect.NewRequest(&mcpv1.GetMCPProfileRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		UserId:     userID,
		ProfileId:  profileID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// List returns every MCP profile owned by a user under (customerID, projectID).
func (mp *MCPProfilesClient) List(ctx context.Context, customerID, projectID, userID string) (*mcpv1.ListMCPProfilesResponse, error) {
	resp, err := mp.c.ListMCPProfiles(ctx, connect.NewRequest(&mcpv1.ListMCPProfilesRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		UserId:     userID,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Update mutates fields on an MCP profile.
func (mp *MCPProfilesClient) Update(ctx context.Context, req *mcpv1.UpdateMCPProfileRequest) (*mcpv1.MCPProfile, error) {
	resp, err := mp.c.UpdateMCPProfile(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Delete soft-deletes an MCP profile.
func (mp *MCPProfilesClient) Delete(ctx context.Context, customerID, projectID, userID, profileID string) error {
	_, err := mp.c.DeleteMCPProfile(ctx, connect.NewRequest(&mcpv1.DeleteMCPProfileRequest{
		CustomerId: customerID,
		ProjectId:  projectID,
		UserId:     userID,
		ProfileId:  profileID,
	}))
	return wrap(err)
}

// MCPCatalogClient wraps MCPCatalogService.
type MCPCatalogClient struct {
	c mcpv1connect.MCPCatalogServiceClient
}

// Upsert creates or replaces a catalog entry.
func (mc *MCPCatalogClient) Upsert(ctx context.Context, req *mcpv1.UpsertCatalogServerRequest) (*mcpv1.MCPCatalogServer, error) {
	resp, err := mc.c.UpsertCatalogServer(ctx, connect.NewRequest(req))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Get returns one catalog entry by id.
func (mc *MCPCatalogClient) Get(ctx context.Context, id string) (*mcpv1.MCPCatalogServer, error) {
	resp, err := mc.c.GetCatalogServer(ctx, connect.NewRequest(&mcpv1.GetCatalogServerRequest{Id: id}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// List returns every catalog entry. includeDisabled controls whether
// is_enabled=false rows are included.
func (mc *MCPCatalogClient) List(ctx context.Context, includeDisabled bool) (*mcpv1.ListCatalogServersResponse, error) {
	resp, err := mc.c.ListCatalogServers(ctx, connect.NewRequest(&mcpv1.ListCatalogServersRequest{
		IncludeDisabled: includeDisabled,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// SetEnabled flips the operator visibility toggle.
func (mc *MCPCatalogClient) SetEnabled(ctx context.Context, id string, enabled bool) (*mcpv1.MCPCatalogServer, error) {
	resp, err := mc.c.SetCatalogServerEnabled(ctx, connect.NewRequest(&mcpv1.SetCatalogServerEnabledRequest{
		Id:      id,
		Enabled: enabled,
	}))
	if err != nil {
		return nil, wrap(err)
	}
	return resp.Msg, nil
}

// Delete removes a catalog entry by id.
func (mc *MCPCatalogClient) Delete(ctx context.Context, id string) error {
	_, err := mc.c.DeleteCatalogServer(ctx, connect.NewRequest(&mcpv1.DeleteCatalogServerRequest{Id: id}))
	return wrap(err)
}
