// ClientsService -- project-scoped public CRUD surface for managed
// API-key clients. Every request carries (customer_id, project_id) so
// the handler can filter by ownership; reads ignore rows belonging to
// other (customer, project) pairs and writes refuse to materialise rows
// with an unset project_id.
//
// Both customer_id and project_id are optional on the request body: when
// empty the handler substitutes the caller's session-default customer +
// project (today, the well-known "default"/"default" bootstrapped for
// every tenant). REST callers still receive them in the URL path; only
// direct connect-RPC + CLI callers benefit from the optionality.
//
// google.api.http annotations route Vanguard's REST surface under
// `/v1/customers/{customer_id}/projects/{project_id}/clients[/...]`.
// tars.cli.v1.command annotations surface the same RPCs as
// `ia clients ...` and `ia clients keys ...` subcommands.
syntax = "proto3";

package tars.clients.v1;

import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/timestamp.proto";
import "tars/auth/v1/auth_options.proto";
import "tars/cli/v1/cli.proto";
import "tars/docs/v1/docs.proto";

option go_package = "github.com/tetrateio/fraser/api/tars/clients/v1;clientsv1";

// ClientsService provides management-plane APIs for managing Clients and
// their API keys, project-scoped under (customer_id, project_id).
service ClientsService {
  // CreateClientWithKey creates a new client and a new API key attached
  // 1:1 (client_id == key_id at bootstrap).
  rpc CreateClientWithKey(CreateClientWithKeyRequest) returns (CreateClientWithKeyResponse) {
    option (tars.docs.v1.title) = "Create a client";
    option (tars.docs.v1.group) = "Clients";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/clients"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "create"
      ]
    };
  }

  // ListClients returns every client owned by the caller within the
  // given (customer_id, project_id) scope.
  rpc ListClients(ListClientsRequest) returns (ListClientsResponse) {
    option (tars.docs.v1.title) = "List clients";
    option (tars.docs.v1.group) = "Clients";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/clients"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "list"
      ]
    };
  }

  // GetClient returns a single client by id within the scope.
  rpc GetClient(GetClientRequest) returns (GetClientResponse) {
    option (tars.docs.v1.title) = "Get a client";
    option (tars.docs.v1.group) = "Clients";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "get"
      ]
    };
  }

  // UpdateClientTitle updates the editable client title.
  rpc UpdateClientTitle(UpdateClientTitleRequest) returns (UpdateClientTitleResponse) {
    option (tars.docs.v1.title) = "Update a client";
    option (tars.docs.v1.group) = "Clients";
    option (google.api.http) = {
      patch: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "update"
      ]
    };
  }

  // AddKeyToClient attaches an existing key to a client.
  rpc AddKeyToClient(AddKeyToClientRequest) returns (AddKeyToClientResponse) {
    option (tars.docs.v1.title) = "Attach a key to a client";
    option (tars.docs.v1.group) = "Keys";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}/keys"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "keys",
        "add"
      ]
    };
  }

  // RotateKeyInClient creates a new key attached to client_id and may
  // schedule the prior key's disable/destroy lifecycle.
  rpc RotateKeyInClient(RotateKeyInClientRequest) returns (RotateKeyInClientResponse) {
    option (tars.docs.v1.title) = "Rotate a client key";
    option (tars.docs.v1.group) = "Keys";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}/keys/rotate"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "keys",
        "rotate"
      ]
    };
  }

  // DisableKeyVersionInClient disables a key version immediately (reversible).
  rpc DisableKeyVersionInClient(DisableKeyVersionInClientRequest) returns (DisableKeyVersionInClientResponse) {
    option (tars.docs.v1.title) = "Disable a client key version";
    option (tars.docs.v1.group) = "Keys";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}/keys/{key_version}/disable"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "keys",
        "disable"
      ]
    };
  }

  // EnableKeyVersionInClient re-enables a disabled key version.
  rpc EnableKeyVersionInClient(EnableKeyVersionInClientRequest) returns (EnableKeyVersionInClientResponse) {
    option (tars.docs.v1.title) = "Enable a client key version";
    option (tars.docs.v1.group) = "Keys";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}/keys/{key_version}/enable"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "keys",
        "enable"
      ]
    };
  }

  // DestroyKeyVersionInClient permanently destroys a key version.
  rpc DestroyKeyVersionInClient(DestroyKeyVersionInClientRequest) returns (DestroyKeyVersionInClientResponse) {
    option (tars.docs.v1.title) = "Destroy a client key version";
    option (tars.docs.v1.group) = "Keys";
    option (google.api.http) = {delete: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}/keys/{key_version}"};
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "keys",
        "destroy"
      ]
    };
  }

  // EmergencyRevokeKeyVersion disables a key immediately and optionally
  // creates a replacement.
  rpc EmergencyRevokeKeyVersion(EmergencyRevokeKeyVersionRequest) returns (EmergencyRevokeKeyVersionResponse) {
    option (tars.docs.v1.title) = "Emergency-revoke a client key version";
    option (tars.docs.v1.group) = "Keys";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/clients/{client_id}/keys/{key_version}/emergency-revoke"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "clients",
        "keys",
        "emergency-revoke"
      ]
    };
  }
}

// EnterpriseContext is preserved as an optional, OPAQUE substructure on
// the create / rotate / revoke paths because the existing AdminServer
// path requires it. The project-scoping authoritatively lives in the
// top-level customer_id / project_id fields on every request.
message EnterpriseContext {
  // Customer/tenant slug; seeds the admin user-token request when set.
  string customer_id = 1;
  // Workspace slug within the customer; defaults to "default" when empty.
  string workspace_id = 2;
  // Organization unit the key belongs to; defaults to "default" when empty.
  string organization_unit = 3;
}

message ClientKey {
  // Key version id (UUID), the api_keys row primary key.
  string id = 1;
  // Human-friendly key name.
  string name = 2;
  // Public, non-secret key prefix shown in listings.
  string prefix = 3;
  // Hash of the secret key material; the raw key is never stored.
  string key_hash = 4;
  // System-generated lookup handle used to locate the key at auth time.
  string key_address = 5;
  // Last 4 characters of the issued key, for display/disambiguation.
  string key_suffix = 6;
  // Whether the key is currently enabled (not disabled/destroyed).
  bool is_active = 7;
  // Whether the key has been soft-deleted.
  bool is_deleted = 8;
  // Lifecycle state: enabled, disabled, or destroyed.
  KeyVersionState state = 9;
  // Scheduled time at which a rotated key will be auto-disabled; unset if none.
  optional google.protobuf.Timestamp rotation_disable_at = 10;
  // Scheduled time at which a rotated key will be auto-destroyed; unset if none.
  optional google.protobuf.Timestamp rotation_destroy_at = 11;
  // Time the key was permanently destroyed; unset while still alive.
  optional google.protobuf.Timestamp destroyed_at = 12;
}

enum KeyVersionState {
  // Default/unknown state.
  KEY_VERSION_STATE_UNSPECIFIED = 0;
  // Key is active and accepted for authentication.
  KEY_VERSION_STATE_ENABLED = 1;
  // Key is disabled (reversible) and rejected for authentication.
  KEY_VERSION_STATE_DISABLED = 2;
  // Key is permanently destroyed and can never be re-enabled.
  KEY_VERSION_STATE_DESTROYED = 3;
}

message Client {
  // Client id (UUID); equals the bootstrap key's id at creation time.
  string id = 1;
  // Owning user id (authn user PK) the client is scoped to.
  string user_id = 2;
  // Human-friendly client title; lazily derived from a key name when unset.
  string title = 4;
  // Owning customer slug the client is scoped to.
  string customer_id = 5;
  // Owning project slug the client is scoped to.
  string project_id = 6;
  // API key versions attached to this client.
  repeated ClientKey keys = 3;
}

message CreateClientWithKeyRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Human-friendly title for the client.
  string title = 3;
  // Backing key name; defaults to title when empty.
  string key_name = 4;
  // Field 5 (user_key_address) was removed: the key address is the
  // system-generated lookup handle minted by IssueAPIKey (crypto/rand);
  // a user must not choose it. Field number 5 is reserved to prevent reuse.
  reserved 5;
  reserved "user_key_address";
  // Optional enterprise tenancy context (legacy AdminServer path).
  EnterpriseContext enterprise_context = 6;
}

message CreateClientWithKeyResponse {
  // Bootstrap behavior: the first key establishes the client identity,
  // so initially client_id == key_id.
  string client_id = 1;
  // Id (UUID) of the newly minted key version.
  string key_id = 2;
  // The raw secret key, returned exactly once at creation; never stored.
  string api_key_once = 3;
  // Public, non-secret key prefix.
  string prefix = 4;
  // Hash of the secret key material.
  string key_hash = 5;
  // System-generated lookup handle used to locate the key at auth time.
  string key_address = 6;
  // Last 4 characters of the issued key, for display.
  string key_suffix = 7;
}

message AddKeyToClientRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) to attach the key to.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Id (UUID) of the existing key to attach.
  string key_id = 4 [(google.api.field_behavior) = REQUIRED];
}

message AddKeyToClientResponse {
  // True when the key was attached successfully.
  bool success = 1;
}

message RotateKeyInClientRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) whose key is being rotated.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Deprecated/ignored: key address is system-generated, not user-chosen.
  string user_key_address = 4;
  // Name for the new key version. Required.
  string key_name = 5;
  // Optional enterprise tenancy context (legacy AdminServer path).
  EnterpriseContext enterprise_context = 6;
  // Id (UUID) of the prior key to schedule disable/destroy for; empty skips scheduling.
  string old_key_id = 7;
  // Absolute time to auto-disable the old key; overridden by disable_after when set.
  google.protobuf.Timestamp old_key_disable_at = 8;
  // Absolute time to auto-destroy the old key; overridden by destroy_after when set.
  google.protobuf.Timestamp old_key_destroy_at = 9;
  // Convenience flags for the CLI: durations relative to now. When set
  // they override old_key_disable_at / old_key_destroy_at.
  string disable_after = 10;
  string destroy_after = 11;
}

message RotateKeyInClientResponse {
  // Client id (UUID) the new key is attached to (unchanged by rotation).
  string client_id = 1;
  // Id (UUID) of the newly minted key version.
  string key_id = 2;
  // The raw secret key, returned exactly once; never stored.
  string api_key_once = 3;
  // Public, non-secret key prefix.
  string prefix = 4;
  // Hash of the secret key material.
  string key_hash = 5;
  // System-generated lookup handle used to locate the key at auth time.
  string key_address = 6;
  // Last 4 characters of the issued key, for display.
  string key_suffix = 7;
  // True when a disable/destroy schedule was applied to the prior key.
  bool old_key_lifecycle_scheduled = 8;
}

message DisableKeyVersionInClientRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) that owns the key version.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Key version id (UUID). Named key_version to match IA's vocabulary;
  // fraser stores one row per version in api_keys.
  string key_version = 4 [(google.api.field_behavior) = REQUIRED];
}

message DisableKeyVersionInClientResponse {
  // True when the key version was disabled.
  bool success = 1;
}

message EnableKeyVersionInClientRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) that owns the key version.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Key version id (UUID) to re-enable.
  string key_version = 4 [(google.api.field_behavior) = REQUIRED];
}

message EnableKeyVersionInClientResponse {
  // True when the key version was re-enabled.
  bool success = 1;
}

message DestroyKeyVersionInClientRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) that owns the key version.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Key version id (UUID) to permanently destroy.
  string key_version = 4 [(google.api.field_behavior) = REQUIRED];
}

message DestroyKeyVersionInClientResponse {
  // True when the key version was destroyed.
  bool success = 1;
}

message EmergencyRevokeKeyVersionRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) that owns the key version being revoked.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Key version id (UUID) to revoke (disable immediately).
  string key_version = 4 [(google.api.field_behavior) = REQUIRED];
  // When true, also rotate in a replacement key after revoking.
  bool generate_replacement = 5;
  // Name for the replacement key; required when generate_replacement is true.
  string replacement_key_name = 6;
  // Deprecated/ignored: replacement key address is system-generated.
  string replacement_user_key_address = 7;
  // Optional enterprise tenancy context for the replacement key.
  EnterpriseContext replacement_enterprise_context = 8;
}

message EmergencyRevokeKeyVersionResponse {
  // True when the key version was revoked.
  bool success = 1;
  // The freshly rotated replacement key; set only when one was requested.
  optional RotateKeyInClientResponse replacement = 2;
}

message GetClientRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) to fetch.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
}

message GetClientResponse {
  // The requested client with its attached keys.
  Client client = 1;
}

message ListClientsRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
}

message ListClientsResponse {
  // All clients owned by the caller within the (customer, project) scope.
  repeated Client clients = 1;
}

message UpdateClientTitleRequest {
  // Owning customer slug. Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Owning project slug. Defaults to the caller's session project when empty.
  string project_id = 2;
  // Client id (UUID) whose title is being updated.
  string client_id = 3 [(google.api.field_behavior) = REQUIRED];
  // New human-friendly title for the client.
  string title = 4 [(google.api.field_behavior) = REQUIRED];
}

message UpdateClientTitleResponse {
  // The client after the title update.
  Client client = 1;
}
