syntax = "proto3";

package tars.insights.v1;

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

option go_package = "github.com/tetrateio/fraser/api/tars/insights/v1;insightsv1";

// CustomerRequestLogsService is the customer-facing read surface over
// request_logs. Identical wire shape to RequestLogsService but the
// handler resolves the calling user via the identitysvc.Authenticator
// (the same path the inference handler uses) and enforces
// `user_id = caller.UserID` on every list/get. The customer_id /
// project_id flags identify the tenancy scope the caller is querying
// against; the underlying `request_logs` table does not yet carry
// customer_id / project_id columns, so the physical isolation today is
// user_id-based -- a customer's users only see their own rows. Adding
// customer_id / project_id columns to request_logs in a future migration
// will let the handler tighten the filter to a true WHERE customer_id =
// $1 AND project_id = $2 without breaking the wire contract.
service CustomerRequestLogsService {
  // ListCustomerRequestLogs returns the caller's own request logs,
  // filtered by the supplied tenancy scope. Forced filter:
  // `user_id = caller.UserID`.
  rpc ListCustomerRequestLogs(ListCustomerRequestLogsRequest) returns (ListCustomerRequestLogsResponse) {
    option (tars.docs.v1.title) = "List request logs scoped to the calling user";
    option (tars.docs.v1.group) = "Request Logs";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/request-logs"};
    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: [
        "insights",
        "logs",
        "list"
      ]
    };
  }

  // GetCustomerRequestLog returns a single request log row owned by the
  // calling user. Cross-user NotFound: the user cannot read another
  // user's log even within the same customer/project.
  rpc GetCustomerRequestLog(GetCustomerRequestLogRequest) returns (RequestLog) {
    option (tars.docs.v1.title) = "Get a single request log";
    option (tars.docs.v1.group) = "Request Logs";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/request-logs/{request_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: [
        "insights",
        "logs",
        "get"
      ]
    };
  }

  // GetCustomerRequestLogStats returns aggregated statistics over the
  // calling user's request logs within the supplied tenancy scope.
  rpc GetCustomerRequestLogStats(GetCustomerRequestLogStatsRequest) returns (RequestLogStatsResponse) {
    option (tars.docs.v1.title) = "Aggregate stats over the caller's request logs";
    option (tars.docs.v1.group) = "Request Logs";
    option (google.api.http) = {get: "/v1/customers/{customer_id}/projects/{project_id}/request-logs/stats"};
    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: [
        "insights",
        "logs",
        "stats"
      ]
    };
  }
}

// RequestLogsService provides access to historical request logs for debugging,
// cost analysis, billing, and compliance auditing.
service RequestLogsService {
  // QueryRequestLogs retrieves request logs with filtering and pagination.
  rpc QueryRequestLogs(QueryRequestLogsRequest) returns (QueryRequestLogsResponse) {
    option (tars.docs.v1.group) = "Request Logs";
    option (google.api.http) = {get: "/v1/request-logs"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // GetRequestLog retrieves a single request log by request ID.
  rpc GetRequestLog(GetRequestLogRequest) returns (RequestLog) {
    option (tars.docs.v1.group) = "Request Logs";
    option (google.api.http) = {get: "/v1/request-logs/{request_id}"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }

  // GetRequestLogStats returns aggregated statistics for request logs.
  rpc GetRequestLogStats(GetRequestLogStatsRequest) returns (RequestLogStatsResponse) {
    option (tars.docs.v1.group) = "Request Logs";
    option (google.api.http) = {get: "/v1/request-logs/stats"};
    option idempotency_level = NO_SIDE_EFFECTS;
    option (tars.auth.v1.auth) = {
      auth_types: [AUTH_TYPE_API_KEY]
      scopes: ["admin"]
    };
  }
}

// ListCustomerRequestLogsRequest carries the (customer_id, project_id)
// tenancy scope the caller is querying against, plus the same filter
// and pagination knobs QueryRequestLogsRequest exposes. The user_id
// filter is forced to the authenticated caller server-side.
message ListCustomerRequestLogsRequest {
  // Tenancy scope. Defaults to the caller's session customer_id /
  // project_id when empty.
  string customer_id = 1;
  // Project within the customer to scope the query to.
  string project_id = 2;

  // Optional filters. Mirror QueryRequestLogsRequest minus user_id
  // (forced to caller).
  // Restrict to logs produced by this API key (UUID).
  string api_key_id = 3;
  // Restrict to a single model name (e.g. "claude-3-opus", "gpt-4").
  string model_name = 4;
  // Restrict by logical status (e.g. "success", "error", "timeout").
  string status = 5;
  // Restrict by HTTP status code returned to the client.
  int32 status_code = 6;
  // Start of the time range (inclusive).
  google.protobuf.Timestamp start_time = 7;
  // End of the time range (exclusive).
  google.protobuf.Timestamp end_time = 8;
  // Only include requests at least this many milliseconds long.
  int32 min_duration_ms = 9;
  // Only include requests at most this many milliseconds long.
  int32 max_duration_ms = 10;

  // Pagination.
  // Maximum logs per page (server applies a default and cap).
  int32 page_size = 11;
  // Opaque token from a prior response's next_page_token to fetch the next page.
  string page_token = 12;

  // Ordering. Valid order_by: timestamp, duration_ms, input_tokens,
  // output_tokens. Valid order_direction: asc, desc.
  string order_by = 13;
  // Sort direction for order_by: "asc" or "desc".
  string order_direction = 14;
}

message ListCustomerRequestLogsResponse {
  // Request logs for the caller on this page, newest first by default.
  repeated RequestLog logs = 1;
  // Token to pass back as page_token for the next page; empty when exhausted.
  string next_page_token = 2;
  // Total number of matching logs across all pages (may be approximate).
  int64 total_count = 3;
}

message GetCustomerRequestLogRequest {
  // Defaults to the caller's session customer_id / project_id when empty.
  string customer_id = 1;
  string project_id = 2;
  // Identifier of the request log to fetch; must be owned by the caller.
  string request_id = 3 [(google.api.field_behavior) = REQUIRED];
}

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

  // Optional filter restricting stats to a single model name.
  string model_name = 3;
  // Start of the aggregation time range (inclusive).
  google.protobuf.Timestamp start_time = 4;
  // End of the aggregation time range (exclusive).
  google.protobuf.Timestamp end_time = 5;
  // Dimension to group by: "model", "user", "api_key", "status", "hour", "day".
  string group_by = 6;
  // Metric to compute: "count", "tokens", "cost", "latency", "errors".
  string metric = 7;
}

// QueryRequestLogsRequest defines filters for querying request logs.
message QueryRequestLogsRequest {
  // Filter by user ID
  string user_id = 1;

  // Filter by API key ID
  string api_key_id = 2;

  // Filter by model name (e.g., "claude-3-opus", "gpt-4")
  string model_name = 3;

  // Filter by status (e.g., "success", "error", "timeout")
  string status = 4;

  // Filter by HTTP status code
  int32 status_code = 5;

  // Start of time range (inclusive)
  google.protobuf.Timestamp start_time = 6;

  // End of time range (exclusive)
  google.protobuf.Timestamp end_time = 7;

  // Minimum duration in milliseconds
  int32 min_duration_ms = 8;

  // Maximum duration in milliseconds
  int32 max_duration_ms = 9;

  // Page size (default: 50, max: 1000)
  int32 page_size = 10;

  // Page token for pagination
  string page_token = 11;

  // Order by field (default: "timestamp")
  // Valid values: "timestamp", "duration_ms", "input_tokens", "output_tokens"
  string order_by = 12;

  // Order direction: "asc" or "desc" (default: "desc")
  string order_direction = 13;
}

// QueryRequestLogsResponse contains the query results and pagination info.
message QueryRequestLogsResponse {
  // List of request logs matching the query
  repeated RequestLog logs = 1;

  // Token for retrieving the next page (empty if no more pages)
  string next_page_token = 2;

  // Total count of matching logs (may be approximate for large datasets)
  int64 total_count = 3;
}

// GetRequestLogRequest retrieves a single log by request ID.
message GetRequestLogRequest {
  // Request identifier
  string request_id = 1;
}

// RequestLog represents a single LLM request/response log entry.
message RequestLog {
  // Unique log identifier
  string id = 1;

  // Timestamp when the request was received
  google.protobuf.Timestamp timestamp = 2;

  // User ID who made the request
  string user_id = 3;

  // API key information
  // UUID of the API key that authenticated the request.
  string api_key_id = 4;
  // Leading visible characters of the key (e.g. "sk-..."), for display.
  string api_key_prefix = 5;
  // Hash of the API key used to look it up without storing the secret.
  string api_key_hash = 6;
  // On-chain/account address the API key is bound to.
  string api_key_address = 7;
  // Human-readable label assigned to the API key.
  string api_key_name = 8;

  // Model name used for the request
  string model_name = 9;

  // Token usage
  // Number of prompt/input tokens consumed by the request.
  int32 input_tokens = 10;
  // Number of completion/output tokens generated in the response.
  int32 output_tokens = 11;

  // Cost information (in microdollars and decimal)
  // Input-token cost in microdollars (1e-6 USD), after fee.
  int64 input_tokens_cost_microdollar = 12;
  string input_tokens_cost = 13; // Decimal string (precision: 18, scale: 10)
  // Input-token cost before the platform fee/markup is applied. Decimal string.
  string input_tokens_cost_before_fee = 14;

  // Output-token cost in microdollars (1e-6 USD), after fee.
  int64 output_tokens_cost_microdollar = 15;
  string output_tokens_cost = 16; // Decimal string (precision: 18, scale: 10)
  // Output-token cost before the platform fee/markup is applied. Decimal string.
  string output_tokens_cost_before_fee = 17;

  // Request/Response status
  // Logical outcome of the request (e.g. "success", "error", "timeout").
  string status = 18;
  // HTTP status code returned to the client.
  int32 status_code = 19;

  // Request metadata
  // Captured request headers as a JSON object.
  google.protobuf.Struct request_headers = 20;
  // Captured request payload as a JSON object.
  google.protobuf.Struct request_body = 21;

  // Response metadata
  // Captured response headers as a JSON object.
  google.protobuf.Struct response_headers = 22;
  string response_body = 23; // Text response body
  bytes response_body_raw = 24; // Raw response body (for binary data)

  // LLM parameters used
  google.protobuf.Struct llm_parameters = 25;

  // Timing information
  int32 upstream_duration_ms = 26; // Duration of upstream LLM call
  int32 duration_ms = 27; // Total request duration

  // Request identifier
  string request_id = 28;

  // Record creation timestamp
  google.protobuf.Timestamp created_at = 29;

  // Object storage information (for Parquet-based storage)
  string storage_location = 30; // GCS path to Parquet file
  int32 storage_row_index = 31; // Row index within Parquet file
  string storage_batch_id = 32; // Batch UUID for grouping
  bool use_object_storage = 33; // true = GCS, false = DB

  // Cache token breakdown.
  // Input tokens served from the prompt cache (cache reads), billed at the cached rate.
  int32 cached_input_tokens = 34;
  string cached_input_tokens_cost = 35; // Decimal string (precision: 18, scale: 10)
  // Cached-input cost before the platform fee/markup is applied. Decimal string.
  string cached_input_tokens_cost_before_fee = 36;
  // Input tokens written to the prompt cache (cache-creation writes).
  int32 cache_creation_input_tokens = 37;
  string cache_creation_input_tokens_cost = 38; // Decimal string (precision: 18, scale: 10)
  // Cache-creation cost before the platform fee/markup is applied. Decimal string.
  string cache_creation_input_tokens_cost_before_fee = 39;
}

// GetRequestLogStatsRequest defines aggregation parameters for log statistics.
message GetRequestLogStatsRequest {
  // Filter by user ID
  string user_id = 1;

  // Filter by model name
  string model_name = 2;

  // Start of time range (inclusive)
  google.protobuf.Timestamp start_time = 3;

  // End of time range (exclusive)
  google.protobuf.Timestamp end_time = 4;

  // Group by dimension
  // Valid values: "model", "user", "api_key", "status", "hour", "day"
  string group_by = 5;

  // Metric to calculate
  // Valid values: "count", "tokens", "cost", "latency", "errors"
  string metric = 6;
}

// RequestLogStatsResponse contains aggregated statistics.
message RequestLogStatsResponse {
  // Statistics grouped by the requested dimension
  repeated StatEntry stats = 1;

  // Total counts across all groups
  // Total number of requests across all groups.
  int64 total_count = 2;
  // Sum of input tokens across all groups.
  int64 total_input_tokens = 3;
  // Sum of output tokens across all groups.
  int64 total_output_tokens = 4;
  string total_cost = 5; // Decimal string
  // Mean request duration in milliseconds across all groups.
  double avg_duration_ms = 6;
  // Total number of error requests across all groups.
  int64 error_count = 7;
}

// StatEntry represents statistics for a single group.
message StatEntry {
  // Group key (model name, user id, date, etc.)
  string key = 1;

  // Request counts
  // Number of requests in this group.
  int64 count = 2;
  // Number of error requests in this group.
  int64 error_count = 3;

  // Token usage
  // Input tokens summed over this group.
  int64 input_tokens = 4;
  // Output tokens summed over this group.
  int64 output_tokens = 5;
  // Input plus output tokens for this group.
  int64 total_tokens = 6;

  // Cost information
  string cost = 7; // Decimal string
  // Group cost in microdollars (1e-6 USD).
  int64 cost_microdollar = 8;

  // Latency metrics
  // Mean request duration in milliseconds for this group.
  int64 avg_duration_ms = 9;
  // Minimum request duration in milliseconds for this group.
  int64 min_duration_ms = 10;
  // Maximum request duration in milliseconds for this group.
  int64 max_duration_ms = 11;
  // Median (p50) request duration in milliseconds for this group.
  int64 p50_duration_ms = 12;
  // 95th-percentile request duration in milliseconds for this group.
  int64 p95_duration_ms = 13;
  // 99th-percentile request duration in milliseconds for this group.
  int64 p99_duration_ms = 14;
}
