// TenancyService and ProjectsService -- public IA-parity surface over
// fraser's existing customers / projects / project_members tables.
//
// Two services to mirror IA's split:
//   - TenancyService handles customers (the top-level tenant).
//   - ProjectsService handles per-customer projects + their members.
//
// Underlying data lives in the same rows fraser's admin.v1 service
// already manages (svc/internal/staff/valet/fraser/repository/customer*
// + project* + project_member*). Handlers here are a thin REST/CLI
// facade over those repositories -- no parallel SQL.
//
// CLI commands are surfaced under `ia customers ...` and `ia projects
// ...` so the muscle memory matches IA's reference CLI.
syntax = "proto3";

package tars.tenancy.v1;

import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/empty.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";
import "tars/tenancy/v1/customer.proto";
import "tars/tenancy/v1/project.proto";

option go_package = "github.com/tetrateio/fraser/api/tars/tenancy/v1;tenancyv1";

// TenancyService manages customers (the top-level tenant). Cross-
// customer operations are platform-scoped.
service TenancyService {
  // CreateCustomer creates a new customer record. A customer owns
  // projects, identities, and billing. Typically called once during
  // provisioning of a new tenant.
  rpc CreateCustomer(CreateCustomerRequest) returns (Customer) {
    option (tars.docs.v1.title) = "Create a customer";
    option (tars.docs.v1.group) = "Customers";
    option (google.api.http) = {
      post: "/v1/customers"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "customers",
        "create"
      ]
    };
  }

  // GetCustomer returns a single customer by id.
  rpc GetCustomer(GetCustomerRequest) returns (Customer) {
    option (tars.docs.v1.title) = "Get a customer";
    option (tars.docs.v1.group) = "Customers";
    option (google.api.http) = {get: "/v1/customers/{customer_id}"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "customers",
        "get"
      ]
    };
  }

  // ListCustomers lists all customers visible to the caller.
  rpc ListCustomers(ListCustomersRequest) returns (ListCustomersResponse) {
    option (tars.docs.v1.title) = "List customers";
    option (tars.docs.v1.group) = "Customers";
    option (google.api.http) = {get: "/v1/customers"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "customers",
        "list"
      ]
    };
  }

  // UpdateCustomer updates mutable customer fields (display_name,
  // description, labels). `customer_id` is immutable.
  rpc UpdateCustomer(UpdateCustomerRequest) returns (Customer) {
    option (tars.docs.v1.title) = "Update a customer";
    option (tars.docs.v1.group) = "Customers";
    option (google.api.http) = {
      patch: "/v1/customers/{customer_id}"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "customers",
        "update"
      ]
    };
  }

  // ArchiveCustomer soft-deletes a customer. The row stays in the
  // database with deleted_at set; future Get / List calls treat it as
  // missing.
  rpc ArchiveCustomer(ArchiveCustomerRequest) returns (google.protobuf.Empty) {
    option (tars.docs.v1.title) = "Archive a customer";
    option (tars.docs.v1.group) = "Customers";
    option (google.api.http) = {delete: "/v1/customers/{customer_id}"};
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "customers",
        "archive"
      ]
    };
  }
}

// ProjectsService manages projects within a customer scope and their
// member assignments.
service ProjectsService {
  // CreateProject creates a project within a customer scope.
  rpc CreateProject(CreateProjectRequest) returns (Project) {
    option (tars.docs.v1.title) = "Create a project";
    option (tars.docs.v1.group) = "Projects";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects"
      body: "*"
    };
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "projects",
        "create"
      ]
    };
  }

  // GetProject returns a single project by id.
  rpc GetProject(GetProjectRequest) returns (Project) {
    option (tars.docs.v1.title) = "Get a project";
    option (tars.docs.v1.group) = "Projects";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["project_owner"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "projects",
        "get"
      ]
    };
  }

  // ListProjects lists projects within a customer scope.
  rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse) {
    option (tars.docs.v1.title) = "List projects";
    option (tars.docs.v1.group) = "Projects";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["admin"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "projects",
        "list"
      ]
    };
  }

  // UpdateProject updates mutable project fields.
  rpc UpdateProject(UpdateProjectRequest) returns (Project) {
    option (tars.docs.v1.title) = "Update a project";
    option (tars.docs.v1.group) = "Projects";
    option (google.api.http) = {
      patch: "/v1/customers/{customer_id}/projects/{project_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: [
        "projects",
        "update"
      ]
    };
  }

  // ArchiveProject soft-deletes a project. Refuses to archive the
  // default project, matching the existing admin.v1 behaviour.
  rpc ArchiveProject(ArchiveProjectRequest) returns (google.protobuf.Empty) {
    option (tars.docs.v1.title) = "Archive a project";
    option (tars.docs.v1.group) = "Projects";
    option (google.api.http) = {delete: "/v1/customers/{customer_id}/projects/{project_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: [
        "projects",
        "archive"
      ]
    };
  }

  // AddProjectMember grants an identity access to a project with the
  // given role bundle.
  rpc AddProjectMember(AddProjectMemberRequest) returns (ProjectMember) {
    option (tars.docs.v1.title) = "Add a project member";
    option (tars.docs.v1.group) = "Project Members";
    option (google.api.http) = {
      post: "/v1/customers/{customer_id}/projects/{project_id}/members"
      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: [
        "projects",
        "members",
        "add"
      ]
    };
  }

  // ListProjectMembers lists all members of a project and their roles.
  rpc ListProjectMembers(ListProjectMembersRequest) returns (ListProjectMembersResponse) {
    option (tars.docs.v1.title) = "List project members";
    option (tars.docs.v1.group) = "Project Members";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/members"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [
        AUTH_TYPE_USER_TOKEN,
        AUTH_TYPE_API_KEY
      ]
      scopes: ["project_owner"]
    };
    option (tars.cli.v1.command) = {
      path: [
        "projects",
        "members",
        "list"
      ]
    };
  }

  // UpdateProjectMember updates an existing project member's role.
  rpc UpdateProjectMember(UpdateProjectMemberRequest) returns (ProjectMember) {
    option (tars.docs.v1.title) = "Update a project member";
    option (tars.docs.v1.group) = "Project Members";
    option (google.api.http) = {
      patch: "/v1/customers/{customer_id}/projects/{project_id}/members/{identity_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: [
        "projects",
        "members",
        "update"
      ]
    };
  }

  // RemoveProjectMember revokes an identity's membership in a project.
  rpc RemoveProjectMember(RemoveProjectMemberRequest) returns (google.protobuf.Empty) {
    option (tars.docs.v1.title) = "Remove a project member";
    option (tars.docs.v1.group) = "Project Members";
    option (google.api.http) = {delete: "/v1/customers/{customer_id}/projects/{project_id}/members/{identity_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: [
        "projects",
        "members",
        "remove"
      ]
    };
  }
}

// ── TenancyService request/response messages ──────────────────────────
//
// Request messages flatten the Customer / Project resource fields into
// scalar top-level fields so the CLI generator can surface each one as
// a flag (protoc-gen-ia-cli skips message-typed fields). This is the
// same divergence from IA the catalog package documents; the on-the-
// wire HTTP body shape stays semantically equivalent.

message CreateCustomerRequest {
  // Required: operator-scoped slug, e.g. "acme".
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];

  // Optional: human-friendly display name.
  string display_name = 2;

  // Optional: freeform description.
  string description = 3;
}

message GetCustomerRequest {
  // Required: slug of the customer to fetch.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
}

message ListCustomersRequest {
  // Cursor-pagination inputs (page size, page token, filter, order_by).
  tars.common.v1.PageRequest page = 1;
}

message ListCustomersResponse {
  // Page of customers visible to the caller.
  repeated Customer customers = 1;
  // Pagination outputs; carries next_page_token for the following page.
  tars.common.v1.PageResponse page = 2;
}

message UpdateCustomerRequest {
  // Required: the customer to update.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];

  // Optional: new display name.
  string display_name = 2;

  // Optional: new description.
  string description = 3;
}

message ArchiveCustomerRequest {
  // Required: slug of the customer to soft-delete.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
}

// ── ProjectsService request/response messages ─────────────────────────

message CreateProjectRequest {
  // Owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];

  // Required: customer-scoped project slug.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];

  // Optional: human-friendly display name.
  string display_name = 3;

  // Optional: freeform description.
  string description = 4;
}

message GetProjectRequest {
  // Required: owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: customer-scoped slug of the project to fetch.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
}

message ListProjectsRequest {
  // Required: customer scope whose projects to list.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Cursor-pagination inputs (page size, page token, filter, order_by).
  tars.common.v1.PageRequest page = 2;
}

message ListProjectsResponse {
  // Page of projects within the requested customer scope.
  repeated Project projects = 1;
  // Pagination outputs; carries next_page_token for the following page.
  tars.common.v1.PageResponse page = 2;
}

message UpdateProjectRequest {
  // Required: owning customer slug. Immutable.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: the project to update. Immutable.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Optional: new display name.
  string display_name = 3;
  // Optional: new description.
  string description = 4;
}

message ArchiveProjectRequest {
  // Required: owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: the project to soft-delete (the default project cannot be archived).
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
}

message AddProjectMemberRequest {
  // Required: owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: project the identity is being granted access to.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Identity row id (authn.user.id). Required.
  string identity_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Role bundle: "admin" or "member".
  string role = 4 [(google.api.field_behavior) = REQUIRED];
}

message ListProjectMembersRequest {
  // Required: owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: project whose members to list.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Cursor-pagination inputs (page size, page token, filter, order_by).
  tars.common.v1.PageRequest page = 3;
}

message ListProjectMembersResponse {
  // Page of members assigned to the requested project, with their roles.
  repeated ProjectMember members = 1;
  // Pagination outputs; carries next_page_token for the following page.
  tars.common.v1.PageResponse page = 2;
}

message UpdateProjectMemberRequest {
  // Required: owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: project the membership belongs to.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Required: identity row id (authn.user.id) whose role is being changed.
  string identity_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Required: new role bundle: "admin" or "member".
  string role = 4 [(google.api.field_behavior) = REQUIRED];
}

message RemoveProjectMemberRequest {
  // Required: owning customer slug.
  string customer_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Required: project the membership belongs to.
  string project_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Required: identity row id (authn.user.id) whose membership is revoked.
  string identity_id = 3 [(google.api.field_behavior) = REQUIRED];
}
