///|
pub(all) enum ReportSeverity {
  Stable
  Watch
  ActionRequired
  Emergency
} derive(Eq, Debug)

///|
pub(all) struct HealthReport {
  service : String
  target : SloTarget
  statistics : WindowStatistics
  latest : BudgetReport
  peak_burn_x100 : Int
  incident_count : Int
  severity : ReportSeverity
  incidents : IncidentTimeline
} derive(Eq, Debug)

///|
fn report_severity(
  latest : BudgetReport,
  stats : WindowStatistics,
  peak_burn_x100 : Int,
  incidents : Int,
) -> ReportSeverity {
  if !latest.healthy() {
    Emergency
  } else if peak_burn_x100 >= 500 || stats.error_rate_bp() >= 100 {
    ActionRequired
  } else if incidents > 0 || peak_burn_x100 >= 100 {
    Watch
  } else {
    Stable
  }
}

///|
pub fn HealthReport::from_windows(
  service : String,
  target : SloTarget,
  windows : Array[RequestWindow],
  incidents : IncidentTimeline,
) -> HealthReport {
  let stats = WindowStatistics::from_windows(windows)
  let latest_window = if windows.length() == 0 {
    RequestWindow::new("latest", 1, 0, 0)
  } else {
    windows[windows.length() - 1]
  }
  let latest = evaluate_budget(target, latest_window)
  let mut peak = 0
  for window in windows {
    let burn = calculate_burn_rate(target, window)
    if burn.burn_rate_x100 > peak {
      peak = burn.burn_rate_x100
    }
  }
  let severity = report_severity(latest, stats, peak, incidents.count())
  {
    service,
    target,
    statistics: stats,
    latest,
    peak_burn_x100: peak,
    incident_count: incidents.count(),
    severity,
    incidents,
  }
}

///|
pub fn HealthReport::healthy(self : HealthReport) -> Bool {
  self.severity == Stable || self.severity == Watch
}

///|
pub fn HealthReport::severity_name(self : HealthReport) -> String {
  match self.severity {
    Stable => "stable"
    Watch => "watch"
    ActionRequired => "action_required"
    Emergency => "emergency"
  }
}

///|
pub fn HealthReport::risk_score(self : HealthReport) -> Int {
  let base = match self.severity {
    Stable => 0
    Watch => 25
    ActionRequired => 60
    Emergency => 100
  }
  let burn_penalty = if self.peak_burn_x100 > 100 { 10 } else { 0 }
  let incident_penalty = if self.incident_count > 0 { 10 } else { 0 }
  let score = base + burn_penalty + incident_penalty
  if score > 100 {
    100
  } else {
    score
  }
}

///|
pub fn HealthReport::recommendation(self : HealthReport) -> String {
  match self.severity {
    Stable => "release may proceed"
    Watch => "keep the release under observation"
    ActionRequired => "pause rollout and inspect recent errors"
    Emergency => "stop rollout and page the on-call owner"
  }
}

///|
pub fn HealthReport::to_json(self : HealthReport) -> String {
  "{\"service\":\"\{escape_json(self.service)}\",\"severity\":\"\{self.severity_name()}\",\"risk_score\":\{self.risk_score()},\"peak_burn_x100\":\{self.peak_burn_x100},\"incident_count\":\{self.incident_count},\"recommendation\":\"\{self.recommendation()}\",\"latest\":\{self.latest.to_json()},\"statistics\":\{self.statistics.to_json()}}"
}

///|
pub fn HealthReport::to_markdown(self : HealthReport) -> String {
  let result = StringBuilder()
  result.write_string("# SLO health report\n\n")
  result.write_string("- Service: \{self.service}\n")
  result.write_string("- Severity: \{self.severity_name()}\n")
  result.write_string("- Risk score: \{self.risk_score()}/100\n")
  result.write_string(
    "- Availability: \{self.statistics.average_availability_bp} bp\n",
  )
  result.write_string(
    "- Peak burn: \{self.peak_burn_x100 / 100}.\{two_digits(self.peak_burn_x100 % 100)}x\n",
  )
  result.write_string("- Incidents: \{self.incident_count}\n")
  result.write_string("\nRecommendation: \{self.recommendation()}\n")
  result.to_string()
}

///|
pub fn HealthReport::to_metrics(self : HealthReport) -> String {
  let labels = "service=\"\{escape_json(self.service)}\""
  "moonslokit_health_risk_score{\{labels}} \{self.risk_score()}\nmoonslokit_health_peak_burn_x100{\{labels}} \{self.peak_burn_x100}\nmoonslokit_health_incidents{\{labels}} \{self.incident_count}\n"
}

///|
pub fn compare_reports(before : HealthReport, after : HealthReport) -> String {
  let direction = if after.risk_score() < before.risk_score() {
    "improved"
  } else if after.risk_score() > before.risk_score() {
    "regressed"
  } else {
    "unchanged"
  }
  "\{before.service}: \{direction} (\{before.risk_score()} -> \{after.risk_score()})"
}

///|
pub fn severity_from_score(score : Int) -> ReportSeverity {
  if score >= 80 {
    Emergency
  } else if score >= 50 {
    ActionRequired
  } else if score >= 20 {
    Watch
  } else {
    Stable
  }
}

///|
pub fn report_is_regression(
  before : HealthReport,
  after : HealthReport,
) -> Bool {
  after.risk_score() > before.risk_score()
}