use super::DriftBudgetStateProvider;
use crate::handlers::structural_incidents::StructuralIncidents;
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::Json,
};
use mockforge_foundation::incidents_types::{
DriftIncident, IncidentQuery, IncidentSeverity, IncidentStatus, IncidentType,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
pub struct ListIncidentsRequest {
pub status: Option<String>,
pub severity: Option<String>,
pub endpoint: Option<String>,
pub method: Option<String>,
pub incident_type: Option<String>,
pub workspace_id: Option<String>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
#[derive(Debug, Serialize)]
pub struct ListIncidentsResponse {
pub incidents: Vec<DriftIncident>,
pub total: usize,
}
#[derive(Debug, Deserialize)]
pub struct UpdateIncidentRequest {
pub status: Option<String>,
pub external_ticket_id: Option<String>,
pub external_ticket_url: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ResolveIncidentRequest {
pub note: Option<String>,
}
pub async fn list_incidents<S: DriftBudgetStateProvider>(
State(state): State<S>,
Query(params): Query<HashMap<String, String>>,
) -> Result<Json<ListIncidentsResponse>, StatusCode> {
let mut query = IncidentQuery::default();
if let Some(status_str) = params.get("status") {
query.status = match status_str.as_str() {
"open" => Some(IncidentStatus::Open),
"acknowledged" => Some(IncidentStatus::Acknowledged),
"resolved" => Some(IncidentStatus::Resolved),
"closed" => Some(IncidentStatus::Closed),
_ => None,
};
}
if let Some(severity_str) = params.get("severity") {
query.severity = match severity_str.as_str() {
"critical" => Some(IncidentSeverity::Critical),
"high" => Some(IncidentSeverity::High),
"medium" => Some(IncidentSeverity::Medium),
"low" => Some(IncidentSeverity::Low),
_ => None,
};
}
if let Some(endpoint) = params.get("endpoint") {
query.endpoint = Some(endpoint.clone());
}
if let Some(method) = params.get("method") {
query.method = Some(method.clone());
}
if let Some(incident_type_str) = params.get("incident_type") {
query.incident_type = match incident_type_str.as_str() {
"breaking_change" => Some(IncidentType::BreakingChange),
"threshold_exceeded" => Some(IncidentType::ThresholdExceeded),
_ => None,
};
}
if let Some(workspace_id) = params.get("workspace_id") {
query.workspace_id = Some(workspace_id.clone());
}
if let Some(limit_str) = params.get("limit") {
if let Ok(limit) = limit_str.parse() {
query.limit = Some(limit);
}
}
if let Some(offset_str) = params.get("offset") {
if let Ok(offset) = offset_str.parse() {
query.offset = Some(offset);
}
}
let incidents = state.incident_manager().query_incidents(query).await;
let total = incidents.len();
Ok(Json(ListIncidentsResponse { incidents, total }))
}
pub async fn get_incident<S: DriftBudgetStateProvider>(
State(state): State<S>,
Path(id): Path<String>,
) -> Result<Json<DriftIncident>, StatusCode> {
state
.incident_manager()
.get_incident(&id)
.await
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
pub async fn update_incident<S: DriftBudgetStateProvider>(
State(state): State<S>,
Path(id): Path<String>,
Json(request): Json<UpdateIncidentRequest>,
) -> Result<Json<DriftIncident>, StatusCode> {
let mut incident =
state.incident_manager().get_incident(&id).await.ok_or(StatusCode::NOT_FOUND)?;
if let Some(status_str) = request.status {
match status_str.as_str() {
"acknowledged" => {
incident = state
.incident_manager()
.acknowledge_incident(&id)
.await
.ok_or(StatusCode::NOT_FOUND)?;
}
"resolved" => {
incident = state
.incident_manager()
.resolve_incident(&id)
.await
.ok_or(StatusCode::NOT_FOUND)?;
}
"closed" => {
incident = state
.incident_manager()
.close_incident(&id)
.await
.ok_or(StatusCode::NOT_FOUND)?;
}
other => {
tracing::warn!(
"Invalid incident status '{}': expected acknowledged, resolved, or closed",
other
);
return Err(StatusCode::BAD_REQUEST);
}
}
}
if let Some(ticket_id) = request.external_ticket_id {
incident = state
.incident_manager()
.link_external_ticket(&id, ticket_id, request.external_ticket_url)
.await
.ok_or(StatusCode::NOT_FOUND)?;
}
Ok(Json(incident))
}
pub async fn resolve_incident<S: DriftBudgetStateProvider>(
State(state): State<S>,
Path(id): Path<String>,
Json(_request): Json<ResolveIncidentRequest>,
) -> Result<Json<DriftIncident>, StatusCode> {
state
.incident_manager()
.resolve_incident(&id)
.await
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
pub async fn get_incident_stats<S: DriftBudgetStateProvider>(
State(state): State<S>,
) -> Result<Json<serde_json::Value>, StatusCode> {
let stats = state.incident_manager().get_statistics().await;
Ok(Json(serde_json::json!({
"stats": stats
})))
}