syntax = "proto3";
package mcp.v1;

option go_package = "git.wntrmute.dev/mc/mcp/gen/mcp/v1;mcpv1";

import "google/protobuf/timestamp.proto";

service McpAgentService {
  // Service lifecycle
  rpc Deploy(DeployRequest) returns (DeployResponse);
  rpc StopService(StopServiceRequest) returns (StopServiceResponse);
  rpc StartService(StartServiceRequest) returns (StartServiceResponse);
  rpc RestartService(RestartServiceRequest) returns (RestartServiceResponse);

  // Desired state
  rpc SyncDesiredState(SyncDesiredStateRequest) returns (SyncDesiredStateResponse);

  // Status and registry
  rpc ListServices(ListServicesRequest) returns (ListServicesResponse);
  rpc GetServiceStatus(GetServiceStatusRequest) returns (GetServiceStatusResponse);
  rpc LiveCheck(LiveCheckRequest) returns (LiveCheckResponse);

  // Adopt
  rpc AdoptContainers(AdoptContainersRequest) returns (AdoptContainersResponse);

  // Purge
  rpc PurgeComponent(PurgeRequest) returns (PurgeResponse);

  // File transfer
  rpc PushFile(PushFileRequest) returns (PushFileResponse);
  rpc PullFile(PullFileRequest) returns (PullFileResponse);

  // Node
  rpc NodeStatus(NodeStatusRequest) returns (NodeStatusResponse);
}

// --- Service lifecycle ---

message RouteSpec {
  string name = 1;      // route name (used for $PORT_<NAME>)
  int32 port = 2;       // mc-proxy listener port (e.g. 443, 8443, 9443); NOT the container internal port
  string mode = 3;      // "l4" or "l7"
  string hostname = 4;  // optional public hostname override
}

message ComponentSpec {
  string name = 1;
  string image = 2;
  string network = 3;
  string user = 4;
  string restart = 5;
  repeated string ports = 6;
  repeated string volumes = 7;
  repeated string cmd = 8;
  repeated RouteSpec routes = 9;
  repeated string env = 10;
}

message ServiceSpec {
  string name = 1;
  bool active = 2;
  repeated ComponentSpec components = 3;
}

message DeployRequest {
  ServiceSpec service = 1;
  // Deploy a single component by name. Empty means deploy all.
  string component = 2;
}

message DeployResponse {
  repeated ComponentResult results = 1;
}

message ComponentResult {
  string name = 1;
  bool success = 2;
  string error = 3;
}

message StopServiceRequest {
  string name = 1;
}

message StopServiceResponse {
  repeated ComponentResult results = 1;
}

message StartServiceRequest {
  string name = 1;
}

message StartServiceResponse {
  repeated ComponentResult results = 1;
}

message RestartServiceRequest {
  string name = 1;
}

message RestartServiceResponse {
  repeated ComponentResult results = 1;
}

// --- Desired state ---

message SyncDesiredStateRequest {
  // All services for this node.
  repeated ServiceSpec services = 1;
}

message SyncDesiredStateResponse {
  repeated ServiceSyncResult results = 1;
}

message ServiceSyncResult {
  string name = 1;
  // Whether the desired state was updated.
  bool changed = 2;
  string summary = 3;
}

// --- Status and registry ---

message ListServicesRequest {}

message ServiceInfo {
  string name = 1;
  bool active = 2;
  repeated ComponentInfo components = 3;
}

message ComponentInfo {
  string name = 1;
  string image = 2;
  // "running", "stopped", "ignore"
  string desired_state = 3;
  // "running", "stopped", "exited", "removed", "unknown"
  string observed_state = 4;
  // Extracted from the image tag.
  string version = 5;
  google.protobuf.Timestamp started = 6;
}

message ListServicesResponse {
  repeated ServiceInfo services = 1;
}

message GetServiceStatusRequest {
  // Empty means all services.
  string name = 1;
}

message DriftInfo {
  string service = 1;
  string component = 2;
  string desired_state = 3;
  string observed_state = 4;
}

message EventInfo {
  string service = 1;
  string component = 2;
  string prev_state = 3;
  string new_state = 4;
  google.protobuf.Timestamp timestamp = 5;
}

message GetServiceStatusResponse {
  repeated ServiceInfo services = 1;
  repeated DriftInfo drift = 2;
  repeated EventInfo recent_events = 3;
}

message LiveCheckRequest {}

message LiveCheckResponse {
  // Services with freshly observed state from the container runtime.
  repeated ServiceInfo services = 1;
}

// --- Adopt ---

message AdoptContainersRequest {
  // Service name. The agent matches containers named <service>-*.
  string service = 1;
}

message AdoptResult {
  // Runtime container name (e.g., "metacrypt-api").
  string container = 1;
  // Derived component name (e.g., "api").
  string component = 2;
  bool success = 3;
  string error = 4;
}

message AdoptContainersResponse {
  repeated AdoptResult results = 1;
}

// --- File transfer ---
// All paths are relative to /srv/<service>/ on the node.

message PushFileRequest {
  string service = 1;
  // Relative path within the service directory.
  string path = 2;
  bytes content = 3;
  // File permissions (e.g., 0600).
  uint32 mode = 4;
}

message PushFileResponse {
  bool success = 1;
  string error = 2;
}

message PullFileRequest {
  string service = 1;
  // Relative path within the service directory.
  string path = 2;
}

message PullFileResponse {
  bytes content = 1;
  uint32 mode = 2;
  string error = 3;
}

// --- Node ---

message NodeStatusRequest {}

message NodeStatusResponse {
  string node_name = 1;
  // "podman" or "docker"
  string runtime = 2;
  string runtime_version = 3;
  uint32 service_count = 4;
  uint32 component_count = 5;
  uint64 disk_total_bytes = 6;
  uint64 disk_free_bytes = 7;
  uint64 memory_total_bytes = 8;
  uint64 memory_free_bytes = 9;
  double cpu_usage_percent = 10;
  google.protobuf.Timestamp uptime_since = 11;
}

// --- Purge ---

message PurgeRequest {
  // Service name (empty = all services).
  string service = 1;
  // Component name (empty = all eligible in service).
  string component = 2;
  // Preview only, do not modify registry.
  bool dry_run = 3;
  // Currently-defined service/component pairs (e.g., "mcns/mcns").
  // The agent uses this to determine what is "not in any service definition".
  repeated string defined_components = 4;
}

message PurgeResponse {
  repeated PurgeResult results = 1;
}

message PurgeResult {
  string service = 1;
  string component = 2;
  // true if removed (or would be, in dry-run).
  bool purged = 3;
  // Why eligible, or why refused.
  string reason = 4;
}
