// McpOAuthClientsService -- project-scoped CRUD for the OAuth clients that
// front MCP servers.
//
// An OAuth client holds the credentials TARS uses to drive an OAuth
// authorization-code flow against an upstream identity provider on behalf
// of an MCP server: the client_id, the client_secret, the authorization /
// token endpoints, the requested scopes, and the redirect URIs. The
// client_secret is WRITE-ONLY: it is wrapped by the fraser secret service
// on the way in (an "sm://<name>" reference is persisted in
// client_secret_ref) and is NEVER returned on any response, exactly like
// the BYOK provider key in tars.aikeys.v1.
//
// Tenancy: every record is scoped to (customer_id, project_id). Writes
// (Create/Update/Delete) require the "project_owner" scope (project rw);
// reads (Get/List) carry no scope and are gated by the handler's project
// membership check (account tier), mirroring MCPProfilesService reads.
//
// Data model adapts the dashboard's mcp_server_oauth_clients table
// (src/db/schemas/mcpOauthClients.ts) into fraser's tenancy model.
syntax = "proto3";

package tars.mcp.v1;

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

option go_package = "github.com/tetrateio/fraser/api/tars/mcp/v1;mcpv1";

// McpOAuthClient is the metadata projection of an mcp_server_oauth_clients
// row. The client_secret is never present on this message -- only the
// write requests carry it, and only as input.
message McpOAuthClient {
  // Server-assigned uuid row id. Output-only.
  string id = 1 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Owning customer scope. Defaults to the caller's session customer when
  // empty on the request; immutable once set.
  string customer_id = 2 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Owning project scope. Defaults to the caller's session project when
  // empty on the request; immutable once set.
  string project_id = 3 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Stable provider key, e.g. "github", "google". Unique per
  // (customer, project). Required on create.
  string provider = 4 [(google.api.field_behavior) = REQUIRED];

  // Human-friendly display name for the provider, e.g. "GitHub".
  string provider_name = 5 [(google.api.field_behavior) = REQUIRED];

  // OAuth client id issued by the upstream provider.
  string client_id = 6 [(google.api.field_behavior) = REQUIRED];

  // OAuth authorization endpoint URL.
  string authorization_url = 7 [(google.api.field_behavior) = REQUIRED];

  // OAuth token endpoint URL.
  string token_url = 8 [(google.api.field_behavior) = REQUIRED];

  // OAuth scopes requested during the authorization flow.
  repeated string scopes = 9;

  // Allowed redirect URIs for the authorization-code flow.
  repeated string redirect_uris = 10;

  // Free-form provider configuration (discovery_url, token_auth_method,
  // ...). Mirrors the dashboard's jsonb metadata column.
  map<string, string> metadata = 11;

  // MCP server ids this client serves. Empty means "all servers in the
  // project".
  repeated string server_ids = 12;

  // Lifecycle status: "active" | "inactive" | "revoked".
  string status = 13;

  // Row creation time. Output-only.
  google.protobuf.Timestamp created_at = 50 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Last mutation time. Output-only.
  google.protobuf.Timestamp updated_at = 51 [(google.api.field_behavior) = OUTPUT_ONLY];
}

// McpOAuthClientsService is the (customer, project)-scoped CRUD surface
// for mcp_server_oauth_clients.
service McpOAuthClientsService {
  // CreateMcpOAuthClient registers a new OAuth client under a project.
  // The client_secret is wrapped by the secret service and discarded;
  // only an sm:// reference is persisted.
  rpc CreateMcpOAuthClient(CreateMcpOAuthClientRequest) returns (McpOAuthClient) {
    option (tars.docs.v1.title) = "Create an MCP OAuth client";
    option (tars.docs.v1.group) = "OAuth Clients";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/mcp-oauth-clients"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["project_owner"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "mcp",
        "oauth-clients",
        "create"
      ]
    };
  }

  // GetMcpOAuthClient returns a single OAuth client by id. Never returns
  // the client_secret. Account tier: any project member may read.
  rpc GetMcpOAuthClient(GetMcpOAuthClientRequest) returns (McpOAuthClient) {
    option (tars.docs.v1.title) = "Get an MCP OAuth client";
    option (tars.docs.v1.group) = "OAuth Clients";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/mcp-oauth-clients/{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: [
        "mcp",
        "oauth-clients",
        "get"
      ]
      primary_flag: "id"
    };
  }

  // ListMcpOAuthClients lists every non-deleted OAuth client in the
  // (customer, project) scope. Account tier: any project member may read.
  rpc ListMcpOAuthClients(ListMcpOAuthClientsRequest) returns (ListMcpOAuthClientsResponse) {
    option (tars.docs.v1.title) = "List MCP OAuth clients";
    option (tars.docs.v1.group) = "OAuth Clients";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/mcp-oauth-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: [
        "mcp",
        "oauth-clients",
        "list"
      ]
    };
  }

  // UpdateMcpOAuthClient applies a partial patch. Omitted scalar strings
  // ("") leave the existing value untouched. A non-empty client_secret
  // rotates the wrapped secret in place.
  rpc UpdateMcpOAuthClient(UpdateMcpOAuthClientRequest) returns (McpOAuthClient) {
    option (tars.docs.v1.title) = "Update an MCP OAuth client";
    option (tars.docs.v1.group) = "OAuth Clients";
    option (google.api.http) = {
      patch: "/v1/customers/{customer_id}/projects/{project_id}/mcp-oauth-clients/{id}"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["project_owner"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "mcp",
        "oauth-clients",
        "update"
      ]
    };
  }

  // DeleteMcpOAuthClient soft-deletes a client (deleted_at set).
  rpc DeleteMcpOAuthClient(DeleteMcpOAuthClientRequest) returns (google.protobuf.Empty) {
    option (tars.docs.v1.title) = "Delete an MCP OAuth client";
    option (tars.docs.v1.group) = "OAuth Clients";
    option (google.api.http) = {delete: "/v1/customers/{customer_id}/projects/{project_id}/mcp-oauth-clients/{id}"};
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["project_owner"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "mcp",
        "oauth-clients",
        "delete"
      ]
      primary_flag: "id"
    };
  }
}

// ── McpOAuthClientsService request/response messages ──────────────────

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

  // Required: stable provider key, e.g. "github".
  string provider = 3 [(google.api.field_behavior) = REQUIRED];
  // Required: human-friendly provider display name.
  string provider_name = 4 [(google.api.field_behavior) = REQUIRED];
  // Required: OAuth client id from the provider.
  string client_id = 5 [(google.api.field_behavior) = REQUIRED];

  // Required: OAuth client secret. WRITE-ONLY -- wrapped by the secret
  // service and never returned on any response.
  string client_secret = 6 [
    (google.api.field_behavior) = REQUIRED,
    (tars.cli.v1.field) = {flag: "client-secret"}
  ];

  // Required: OAuth authorization endpoint URL.
  string authorization_url = 7 [(google.api.field_behavior) = REQUIRED];
  // Required: OAuth token endpoint URL.
  string token_url = 8 [(google.api.field_behavior) = REQUIRED];

  // Optional: requested OAuth scopes.
  repeated string scopes = 9;
  // Optional: allowed redirect URIs.
  repeated string redirect_uris = 10;
  // Optional: free-form provider configuration.
  map<string, string> metadata = 11;
  // Optional: MCP server ids this client serves.
  repeated string server_ids = 12;
  // Optional: initial status ("active" | "inactive"). Defaults to
  // "active" when empty.
  string status = 13;
}

message GetMcpOAuthClientRequest {
  // Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Defaults to the caller's session project when empty.
  string project_id = 2;
  // Required: uuid of the client to fetch (McpOAuthClient.id).
  string id = 3 [(google.api.field_behavior) = REQUIRED];
}

message ListMcpOAuthClientsRequest {
  // Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Defaults to the caller's session project when empty.
  string project_id = 2;
  // Cursor-pagination inputs (page size + continuation token).
  tars.common.v1.PageRequest page = 3;
}

message ListMcpOAuthClientsResponse {
  // OAuth clients in the requested scope (metadata only, no secrets).
  repeated McpOAuthClient clients = 1;
  // Cursor-pagination outputs (next-page token).
  tars.common.v1.PageResponse page = 2;
}

message UpdateMcpOAuthClientRequest {
  // Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Defaults to the caller's session project when empty.
  string project_id = 2;
  // Required: uuid of the client to update (McpOAuthClient.id).
  string id = 3 [(google.api.field_behavior) = REQUIRED];

  // Mutable scalar fields. Empty string leaves the existing value
  // untouched.
  string provider_name = 4;
  string client_id = 5;

  // Optional: a non-empty value rotates the wrapped client secret in
  // place. Empty leaves the stored secret untouched. WRITE-ONLY.
  string client_secret = 6 [(tars.cli.v1.field) = {flag: "client-secret"}];

  string authorization_url = 7;
  string token_url = 8;
  string status = 9;

  // Repeated / map fields: a non-nil value REPLACES the stored value;
  // a nil/omitted value leaves it untouched (see the replace_* flags).
  repeated string scopes = 10;
  repeated string redirect_uris = 11;
  map<string, string> metadata = 12;
  repeated string server_ids = 13;

  // Because proto3 cannot distinguish an omitted repeated/map field from
  // an explicit "set to empty", these booleans opt in to clearing the
  // matching collection. When false, an empty scopes/redirect_uris/
  // metadata/server_ids is treated as "leave untouched".
  bool replace_scopes = 20;
  bool replace_redirect_uris = 21;
  bool replace_metadata = 22;
  bool replace_server_ids = 23;
}

message DeleteMcpOAuthClientRequest {
  // Defaults to the caller's session customer when empty.
  string customer_id = 1;
  // Defaults to the caller's session project when empty.
  string project_id = 2;
  // Required: uuid of the client to soft-delete (McpOAuthClient.id).
  string id = 3 [(google.api.field_behavior) = REQUIRED];
}
