syntax = "proto3";

package bff.cluster_management.v1beta1;

import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/duration.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "tidb_cloud_open_api/dedicated/v1beta1/resources.proto";
import "tidb_cloud_open_api/dedicated/v1beta1/services.proto";
import "tidb_cloud_open_api/serverless/v1beta1/resources.proto";

option go_package = "bff/cluster_management/v1beta1";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
  swagger: "2.0"
  host: ""
  base_path: "/v1beta1"
  info: {
    version: "v1beta1"
    title: "TiDB Cloud Console API"
    description: "TiDB Cloud Console API."
  }
  schemes: HTTPS
};

service ClusterService {
  rpc ListClusters(ListClustersRequest) returns (ListClustersResponse) {
    option (google.api.http) = {get: "/clusters"};
  }

  rpc GetCluster(GetClusterRequest) returns (Cluster) {
    option (google.api.http) = {get: "/clusters/{cluster_id}"};
  }

  rpc AdvancedModifyDedicatedCluster(AdvancedModifyDedicatedClusterRequest) returns (Cluster) {
    option (google.api.http) = {
      post: "/clusters/{cluster.cluster_id}:dedicatedAdvancedModify"
      body: "cluster"
    };
  }

  rpc CheckModificationRisks(CheckModificationRisksRequest) returns (CheckModificationRisksResponse) {
    option (google.api.http) = {
      post: "/clusters/{cluster.cluster_id}:checkModificationRisks"
      body: "*"
    };
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Check if the modification will affect the stability of the cluster"};
  }

  rpc EstimatePrice(EstimatePriceRequest) returns (EstimatePriceResponse) {
    option (google.api.http) = {
      post: "/clusters:estimatePrice"
      body: "*"
    };
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Estimate the price for the wanted cluster"};
  }
}

message Cluster {
  ClusterType cluster_type = 1;

  oneof cluster {
    tidb_cloud_open_api.dedicated.v1beta1.Cluster dedicated_cluster = 2;
    tidb_cloud_open_api.serverless.v1beta1.Cluster serverless_cluster = 3;
  }

  enum ClusterType {
    CLUSTER_TYPE_UNSPECIFIED = 0;
    DEDICATED = 1;
    SERVERLESS = 2;
  }
}

message GetClusterRequest {
  string cluster_id = 1 [(google.api.field_behavior) = REQUIRED];
}

message ListClustersRequest {
  // If unspecified, the project ID of default project is used.
  string project_id = 1 [(google.api.field_behavior) = OPTIONAL];

  // if unspecified, all clusters are returned.
  Cluster.ClusterType cluster_type = 2 [(google.api.field_behavior) = OPTIONAL];

  // Only for dedicated clusters.
  // If ture, only clusters annotated with "tidb.cloud/insufficient-resource" will be returned.
  optional bool only_resource_insufficient = 3 [(google.api.field_behavior) = OPTIONAL];

  // If specified, only clusters with the specified name will be returned.
  string name = 4 [(google.api.field_behavior) = OPTIONAL];

  // The maximum number of clusters to return. The service may return fewer than this value.
  // If unspecified, at most X clusters will be returned.
  // The maximum value is X; values above X will be coerced to X.
  int32 page_size = 100 [(google.api.field_behavior) = OPTIONAL];

  // A page token, received from a previous `ListClusters` call.
  // Provide this to retrieve the subsequent page.
  //
  // When paginating, all other parameters provided to `ListClusters` must match
  // the call that provided the page token.
  string page_token = 101 [(google.api.field_behavior) = OPTIONAL];

  // The number of individual resources to skip before starting to return results.
  // If the skip value causes the cursor to move past the end of the collection,
  // the response will be 200 OK with an empty result set and no next_page_token.
  int32 skip = 102 [(.google.api.field_behavior) = OPTIONAL];
}

message ListClustersResponse {
  repeated Cluster clusters = 1;

  // The total number of clusters that matched the query.
  int32 total_size = 100;

  // A token, which can be sent as `page_token` to retrieve the next page.
  // If this field is omitted, there are no subsequent pages.
  string next_page_token = 101;
}

message AdvancedModifyDedicatedClusterRequest {
  Cluster cluster = 1 [(google.api.field_behavior) = REQUIRED];

  message Cluster {
    string cluster_id = 1 [(google.api.field_behavior) = REQUIRED];

    TidbNodeSetting tidb_node_setting = 2 [(google.api.field_behavior) = OPTIONAL];

    tidb_cloud_open_api.dedicated.v1beta1.UpdateClusterRequest.StorageNodeSetting tikv_node_setting = 3 [(google.api.field_behavior) = OPTIONAL];

    tidb_cloud_open_api.dedicated.v1beta1.UpdateClusterRequest.StorageNodeSetting tiflash_node_setting = 4 [(google.api.field_behavior) = OPTIONAL];

    string display_name = 5 [(google.api.field_behavior) = OPTIONAL];
  }

  message TidbNodeSetting {
    // The homogeneous node spec key for all TiDB nodes in the cluster. If specified, all TiDB nodes across different groups in the cluster will update to the same node spec.
    string node_spec_key = 1 [(google.api.field_behavior) = OPTIONAL];

    repeated TidbNodeGroupCreate create_tidb_node_groups = 2 [(google.api.field_behavior) = OPTIONAL];

    repeated TidbNodeGroupUpdate update_tidb_node_groups = 3 [(google.api.field_behavior) = OPTIONAL];

    repeated string delete_tidb_node_group_ids = 4 [(google.api.field_behavior) = OPTIONAL];
  }

  message TidbNodeGroupCreate {
    // The display name of the TiDB group.
    string display_name = 1 [(google.api.field_behavior) = REQUIRED];

    // The number of TiDB nodes in the TiDB group.
    int32 node_count = 2 [(google.api.field_behavior) = REQUIRED];

    tidb_cloud_open_api.dedicated.v1beta1.TidbNodeGroup.TiProxySetting tiproxy_setting = 3 [
      (google.api.field_behavior) = OPTIONAL,
      (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
        description: "The TiProxy setting for the TiDB group. If not specified, the default TiProxy setting will be used."
        title: "TiProxySetting"
      }
    ];

    // The heterogeneous node spec key for TiDB nodes in this TiDB group. If specified, only the TiDB nodes in this TiDB group will update to the specified node spec, while other TiDB groups in the cluster remain unchanged. Only takes effect when homogeneous `node_spec_key` in `tidb_node_setting` is not set.
    string node_spec_key = 4 [
      (google.api.field_behavior) = OPTIONAL,
      (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
        example: "\"8C32G\""
        description: "The node spec key of the TiDB group. For example, `8C32G`."
      }
    ];
  }

  message TidbNodeGroupUpdate {
    string tidb_node_group_id = 1 [(google.api.field_behavior) = REQUIRED];

    // The number of TiDB nodes in the TiDB group.
    int32 node_count = 2 [(google.api.field_behavior) = OPTIONAL];

    tidb_cloud_open_api.dedicated.v1beta1.TidbNodeGroup.TiProxySetting tiproxy_setting = 3 [(google.api.field_behavior) = OPTIONAL];

    // The heterogeneous node spec key for TiDB nodes in this TiDB group. If specified, only the TiDB nodes in this TiDB group will update to the specified node spec, while other TiDB groups in the cluster remain unchanged. Only takes effect when homogeneous `node_spec_key` in `tidb_node_setting` is not set.
    string node_spec_key = 4 [
      (google.api.field_behavior) = OPTIONAL,
      (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
        example: "\"8C32G\""
        description: "The node spec key of the TiDB group. For example, `8C32G`."
      }
    ];
  }
}

message CheckModificationRisksRequest {
  AdvancedModifyDedicatedClusterRequest.Cluster cluster = 1 [(google.api.field_behavior) = REQUIRED];
}

message CheckModificationRisksResponse {
  bool risked = 1;
  repeated Risk risks = 2;

  message Risk {
    tidb_cloud_open_api.dedicated.v1beta1.ComponentType component_type = 1;
    int64 biz_code = 2;
    string description = 3;
  }
}

message EstimatePriceRequest {
  // If not specified, the project ID of the default project will be used.
  string project_id = 1 [(google.api.field_behavior) = OPTIONAL];

  // Format: {cloud_provider}-{region_code}
  string region_id = 2 [(google.api.field_behavior) = REQUIRED];

  // Format: {digits}s
  // If duration is not specified, the default duration is 3600s (1h).
  google.protobuf.Duration duration = 3 [(google.api.field_behavior) = OPTIONAL];

  tidb_cloud_open_api.dedicated.v1beta1.Cluster.TidbNodeSetting tidb_node_setting = 4 [(google.api.field_behavior) = OPTIONAL];

  tidb_cloud_open_api.dedicated.v1beta1.Cluster.StorageNodeSetting tikv_node_setting = 5 [(google.api.field_behavior) = OPTIONAL];

  tidb_cloud_open_api.dedicated.v1beta1.Cluster.StorageNodeSetting tiflash_node_setting = 6 [(google.api.field_behavior) = OPTIONAL];

  // If specified, the price will be estimated based on cluster keep paused during the duration interval.
  bool paused = 7 [(google.api.field_behavior) = OPTIONAL];

  // [https://pingcap.feishu.cn/wiki/SWfnwtCBZid1bdk5a7FcamQcn5g#share-E8Uddcpw2onjx1xnUVuc9pcanSh](price promotion until 2025/May)
  bool promoted = 8 [(google.api.field_behavior) = OPTIONAL];
}

message EstimatePriceResponse {
  repeated Price prices = 1;

  message Price {
    tidb_cloud_open_api.dedicated.v1beta1.ComponentType component_type = 1;

    double compute_original_price = 2;

    double compute_discounted_price = 3;

    double storage_original_price = 4;

    double storage_discounted_price = 5;
  }
}
