syntax = "proto3";

package tars.registry.v1;

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

// RegistryService provides APIs for managing registry service accounts.
// Used by valet deployments to register their service accounts with the registry server.
//
// Doc-internal but NOT runtime-internal: the (tars.docs.v1.internal) marker
// keeps this service out of the customer API reference, while the per-RPC
// auth annotations still require an ADMIN API key -- the credential the real
// callers present (the `admin sa register` CLI via newAPIKeyInterceptor, and
// valet's registration path). Doc visibility is decoupled from runtime auth:
// making the service internal must not change which credential it accepts, so
// the registry server's API-key auth interceptor keeps working unchanged.
service RegistryService {
  option (tars.docs.v1.internal) = true;

  // Register a service account with the registry.
  // This endpoint receives a service account (with private key stripped) from a valet deployment
  // and stores it for authentication purposes.
  rpc RegisterServiceAccount(RegisterServiceAccountRequest) returns (RegisterServiceAccountResponse) {
    option (google.api.http) = {
      post: "/registry/v1/service-accounts"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Revoke a service account.
  // Marks a service account as revoked so it can no longer authenticate.
  rpc RevokeServiceAccount(RevokeServiceAccountRequest) returns (RevokeServiceAccountResponse) {
    option (google.api.http) = {
      post: "/registry/v1/service-accounts/{id}:revoke"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Get a service account by ID.
  rpc GetServiceAccount(GetServiceAccountRequest) returns (GetServiceAccountResponse) {
    option (google.api.http) = {get: "/registry/v1/service-accounts/{id}"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Lookup a service account by natural key (customer_id, workspace_id, private_key_id).
  rpc LookupServiceAccount(LookupServiceAccountRequest) returns (LookupServiceAccountResponse) {
    option (google.api.http) = {get: "/registry/v1/service-accounts/{customer_id}/{workspace_id}/{private_key_id}"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // List service accounts for a deployment.
  rpc ListServiceAccounts(ListServiceAccountsRequest) returns (ListServiceAccountsResponse) {
    option (google.api.http) = {get: "/registry/v1/service-accounts"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Get public keys for a service account.
  // Used for JWT verification.
  rpc GetServiceAccountPublicKeys(GetServiceAccountPublicKeysRequest) returns (GetServiceAccountPublicKeysResponse) {
    option (google.api.http) = {get: "/registry/v1/service-accounts/{service_account_id}/public-keys"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Create a registry credential (username:password) for stock helm/docker login.
  // The plaintext password is returned ONCE in the response; only its HMAC-SHA256
  // digest (keyed with REGISTRY_API_KEY_SECRET) is persisted, matching the
  // admin_api_keys hashing scheme. A DB leak alone is insufficient to validate
  // passwords without also obtaining the server-side secret. Subsequent List
  // calls return metadata only.
  rpc CreateRegistryCredential(CreateRegistryCredentialRequest) returns (CreateRegistryCredentialResponse) {
    option (google.api.http) = {
      post: "/registry/v1/credentials"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // List registry credentials for a customer/workspace.
  // Returns metadata only — never the plaintext password or its hash.
  rpc ListRegistryCredentials(ListRegistryCredentialsRequest) returns (ListRegistryCredentialsResponse) {
    option (google.api.http) = {get: "/registry/v1/credentials"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Revoke a registry credential. Soft delete — row is kept for audit.
  rpc RevokeRegistryCredential(RevokeRegistryCredentialRequest) returns (RevokeRegistryCredentialResponse) {
    option (google.api.http) = {
      post: "/registry/v1/credentials/{id}:revoke"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // Ping endpoint to validate API key.
  // Returns success if the API key is valid.
  rpc Ping(PingRequest) returns (PingResponse) {
    option (google.api.http) = {get: "/registry/v1/ping"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }
}

// Request to register a service account.
message RegisterServiceAccountRequest {
  string customer_id = 1 [(buf.validate.field).string = {
    pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    min_len: 1
    max_len: 63
  }];

  string workspace_id = 2 [(buf.validate.field).string = {
    pattern: "^[a-zA-Z0-9-_.]+$"
    min_len: 1
    max_len: 253
  }];

  string client_email = 3 [(buf.validate.field).string.email = true];

  string private_key_id = 4 [(buf.validate.field).string = {
    min_len: 1
    max_len: 256
  }]; // kid reference

  string issuer = 5 [(buf.validate.field).string = {
    min_len: 1
    max_len: 253
  }];

  string audience = 6 [(buf.validate.field).string = {
    min_len: 1
    max_len: 253
  }];

  optional string jwks_uri = 7 [(buf.validate.field).string.uri = true];

  repeated string scopes = 8 [(buf.validate.field).repeated = {
    max_items: 10
    items: {
      string: {
        pattern: "^[a-z_]+$"
        max_len: 50
      }
    }
  }];

  google.protobuf.Timestamp expires_at = 9 [(buf.validate.field).required = true];

  // Public keys for JWT verification
  repeated PublicKey public_keys = 10 [(buf.validate.field).repeated.min_items = 1];

  // Optional: Checksum for integrity verification
  optional string checksum = 11;
  optional string checksum_algorithm = 12;
}

message RegisterServiceAccountResponse {
  string id = 1; // UUID of the created service account
  google.protobuf.Timestamp created_at = 2;
  string message = 3; // Success message
}

// Request to revoke a service account.
message RevokeServiceAccountRequest {
  string id = 1 [(buf.validate.field).string.uuid = true];
  optional string reason = 2 [(buf.validate.field).string.max_len = 500]; // Reason for revocation
}

message RevokeServiceAccountResponse {
  string id = 1;
  google.protobuf.Timestamp revoked_at = 2;
  string message = 3;
}

// Request to get a service account.
message GetServiceAccountRequest {
  string id = 1 [(buf.validate.field).string.uuid = true];
}

message GetServiceAccountResponse {
  ServiceAccountInfo service_account = 1;
}

// Request to lookup a service account by natural key.
message LookupServiceAccountRequest {
  string customer_id = 1 [(buf.validate.field).string = {
    pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    min_len: 1
    max_len: 63
  }];

  string workspace_id = 2 [(buf.validate.field).string = {
    pattern: "^[a-zA-Z0-9-_.]+$"
    min_len: 1
    max_len: 253
  }];

  string private_key_id = 3 [(buf.validate.field).string = {
    min_len: 1
    max_len: 256
  }];
}

message LookupServiceAccountResponse {
  ServiceAccountInfo service_account = 1;
}

// Request to list service accounts.
message ListServiceAccountsRequest {
  optional string customer_id = 1 [(buf.validate.field).string = {
    pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    max_len: 63
  }];

  optional string workspace_id = 2 [(buf.validate.field).string = {
    pattern: "^[a-zA-Z0-9-_.]+$"
    max_len: 253
  }];

  optional bool include_revoked = 3; // Include revoked service accounts in results

  int32 page_size = 4 [(buf.validate.field).int32 = {
    gte: 1
    lte: 100
  }]; // Default: 25

  optional string page_token = 5; // For pagination
}

message ListServiceAccountsResponse {
  repeated ServiceAccountInfo service_accounts = 1;
  optional string next_page_token = 2;
  int32 total_count = 3;
}

// Request to get public keys for a service account.
message GetServiceAccountPublicKeysRequest {
  string service_account_id = 1 [(buf.validate.field).string.uuid = true];
}

message GetServiceAccountPublicKeysResponse {
  repeated PublicKey public_keys = 1;
}

// Service account information (without private key).
message ServiceAccountInfo {
  string id = 1;
  string customer_id = 2;
  string workspace_id = 3;
  string client_email = 4;
  string private_key_id = 5; // kid reference only
  string issuer = 6;
  string audience = 7;
  optional string jwks_uri = 8;
  repeated string scopes = 9;
  google.protobuf.Timestamp created_at = 10;
  google.protobuf.Timestamp expires_at = 11;
  optional google.protobuf.Timestamp revoked_at = 12;
  optional string revoked_reason = 13;
  optional string checksum = 14;
  optional string checksum_algorithm = 15;
}

// Public key for JWT verification.
message PublicKey {
  string kid = 1 [(buf.validate.field).string = {
    min_len: 1
    max_len: 256
  }]; // Key ID

  string alg = 2 [(buf.validate.field).string = {
    in: [
      "ES256",
      "ES384",
      "ES512"
    ]
  }]; // Algorithm (default: ES256)

  string use = 3 [(buf.validate.field).string = {
    in: [
      "sig",
      "enc"
    ]
  }]; // Usage (default: sig)

  string key_pem = 4 [(buf.validate.field).string.min_len = 1]; // Public key in PEM format

  google.protobuf.Timestamp created_at = 5;
}

// Request to create a registry credential.
message CreateRegistryCredentialRequest {
  string customer_id = 1 [(buf.validate.field).string = {
    pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    min_len: 1
    max_len: 63
  }];

  string workspace_id = 2 [(buf.validate.field).string = {
    pattern: "^[a-zA-Z0-9-_.]+$"
    min_len: 1
    max_len: 253
  }];

  // Human-readable label for this credential (e.g. "GitHub Actions prod CI").
  // Required to force customers to label credentials and avoid an anonymous
  // credential graveyard.
  string description = 3 [(buf.validate.field).string = {
    min_len: 1
    max_len: 200
  }];

  // Optional explicit expiry. If unset, the credential never expires (until revoked).
  // CI customers typically do not want surprise mandatory rotations.
  optional google.protobuf.Timestamp expires_at = 4;

  // Optional scopes. Defaults to ["read"] when empty.
  repeated string scopes = 5;
}

// Response containing the created credential. The `password` field is the only
// time the plaintext is ever returned — clients MUST store it immediately.
message CreateRegistryCredentialResponse {
  string id = 1; // UUID of the created credential
  string username = 2; // Server-generated, e.g. "acme-corp-7af3b2"
  string password = 3; // PLAINTEXT — returned exactly once
  google.protobuf.Timestamp created_at = 4;
  string message = 5; // Success message + reminder to copy the password now
}

// Request to list registry credentials.
message ListRegistryCredentialsRequest {
  optional string customer_id = 1 [(buf.validate.field).string = {
    pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    max_len: 63
  }];

  optional string workspace_id = 2 [(buf.validate.field).string = {
    pattern: "^[a-zA-Z0-9-_.]+$"
    max_len: 253
  }];

  optional bool include_revoked = 3; // Include revoked credentials in results

  int32 page_size = 4 [(buf.validate.field).int32 = {
    gte: 1
    lte: 100
  }]; // Default: 25

  optional string page_token = 5; // For pagination
}

message ListRegistryCredentialsResponse {
  repeated RegistryCredentialInfo credentials = 1;
  optional string next_page_token = 2;
  int32 total_count = 3;
}

// Request to revoke a registry credential.
message RevokeRegistryCredentialRequest {
  string id = 1 [(buf.validate.field).string.uuid = true];
  optional string reason = 2 [(buf.validate.field).string.max_len = 500];
}

message RevokeRegistryCredentialResponse {
  string id = 1;
  google.protobuf.Timestamp revoked_at = 2;
  string message = 3;
}

// Registry credential metadata. Never includes the plaintext password or its
// HMAC-SHA256 digest — those are not returned by any API after the initial
// create call.
message RegistryCredentialInfo {
  string id = 1;
  string customer_id = 2;
  string workspace_id = 3;
  string username = 4;
  string description = 5;
  repeated string scopes = 6;
  google.protobuf.Timestamp created_at = 7;
  string created_by = 8;
  optional google.protobuf.Timestamp expires_at = 9;
  optional google.protobuf.Timestamp last_used_at = 10;
  optional google.protobuf.Timestamp revoked_at = 11;
  optional string revoked_reason = 12;
  optional string revoked_by = 13;
}

// Request to ping the registry service.
message PingRequest {}

// Response from ping endpoint.
message PingResponse {
  string message = 1; // Success message
  google.protobuf.Timestamp timestamp = 2; // Server timestamp
}
