///|
/// SLO window kind for operational change detection services.
pub(all) enum ProductionSloWindowKind {
  ShortSloWindow
  LongSloWindow
  RollingSloWindow
}

///|
pub fn production_slo_window_name(kind : ProductionSloWindowKind) -> String {
  match kind {
    ShortSloWindow => "short"
    LongSloWindow => "long"
    RollingSloWindow => "rolling"
  }
}

///|
/// Objective for one service-level indicator.
pub struct ProductionSloObjective {
  name : String
  target : Double
  window : Int64
  burn_limit : Double
  kind : ProductionSloWindowKind
}

///|
pub fn ProductionSloObjective::new(
  name : String,
  target? : Double = 0.99,
  window? : Int64 = 3600L,
  burn_limit? : Double = 2.0,
  kind? : ProductionSloWindowKind = RollingSloWindow,
) -> ProductionSloObjective {
  {
    name,
    target: if target < 0.0 {
      0.0
    } else if target > 1.0 {
      1.0
    } else {
      target
    },
    window: if window < 1L {
      1L
    } else {
      window
    },
    burn_limit: if burn_limit < 0.0 {
      0.0
    } else {
      burn_limit
    },
    kind,
  }
}

///|
pub fn ProductionSloObjective::name(self : ProductionSloObjective) -> String {
  self.name
}

///|
pub fn ProductionSloObjective::target(self : ProductionSloObjective) -> Double {
  self.target
}

///|
pub fn ProductionSloObjective::window(self : ProductionSloObjective) -> Int64 {
  self.window
}

///|
pub fn ProductionSloObjective::burn_limit(
  self : ProductionSloObjective,
) -> Double {
  self.burn_limit
}

///|
pub fn ProductionSloObjective::kind(
  self : ProductionSloObjective,
) -> ProductionSloWindowKind {
  self.kind
}

///|
pub fn ProductionSloObjective::error_budget(
  self : ProductionSloObjective,
) -> Double {
  1.0 - self.target
}

///|
pub fn ProductionSloObjective::summary(self : ProductionSloObjective) -> String {
  self.name +
  ":target=" +
  self.target.to_string() +
  ",window=" +
  self.window.to_string() +
  ",burn_limit=" +
  self.burn_limit.to_string() +
  ",kind=" +
  production_slo_window_name(self.kind)
}

///|
/// Current burn and compliance state for one SLO.
pub struct ProductionSloReport {
  objective : ProductionSloObjective
  total : Int
  good : Int
  bad : Int
  compliance : Double
  burn_rate : Double
  remaining_budget : Double
  breached : Bool
}

///|
pub fn ProductionSloReport::objective(
  self : ProductionSloReport,
) -> ProductionSloObjective {
  self.objective
}

///|
pub fn ProductionSloReport::total(self : ProductionSloReport) -> Int {
  self.total
}

///|
pub fn ProductionSloReport::good(self : ProductionSloReport) -> Int {
  self.good
}

///|
pub fn ProductionSloReport::bad(self : ProductionSloReport) -> Int {
  self.bad
}

///|
pub fn ProductionSloReport::compliance(self : ProductionSloReport) -> Double {
  self.compliance
}

///|
pub fn ProductionSloReport::burn_rate(self : ProductionSloReport) -> Double {
  self.burn_rate
}

///|
pub fn ProductionSloReport::remaining_budget(
  self : ProductionSloReport,
) -> Double {
  self.remaining_budget
}

///|
pub fn ProductionSloReport::breached(self : ProductionSloReport) -> Bool {
  self.breached
}

///|
pub fn ProductionSloReport::summary(self : ProductionSloReport) -> String {
  self.objective.name() +
  ":compliance=" +
  self.compliance.to_string() +
  ",burn=" +
  self.burn_rate.to_string() +
  ",budget=" +
  self.remaining_budget.to_string() +
  ",breached=" +
  self.breached.to_string()
}

///|
/// Mutable SLO tracker fed by accepted/rejected monitor outcomes.
pub struct ProductionSloTracker {
  objective : ProductionSloObjective
  mut total : Int
  mut good : Int
  mut bad : Int
  mut window_start : Int64?
}

///|
pub fn ProductionSloTracker::new(
  objective : ProductionSloObjective,
) -> ProductionSloTracker {
  { objective, total: 0, good: 0, bad: 0, window_start: None }
}

///|
pub fn ProductionSloTracker::objective(
  self : ProductionSloTracker,
) -> ProductionSloObjective {
  self.objective
}

///|
pub fn ProductionSloTracker::total(self : ProductionSloTracker) -> Int {
  self.total
}

///|
pub fn ProductionSloTracker::good(self : ProductionSloTracker) -> Int {
  self.good
}

///|
pub fn ProductionSloTracker::bad(self : ProductionSloTracker) -> Int {
  self.bad
}

///|
pub fn ProductionSloTracker::observe(
  self : ProductionSloTracker,
  timestamp : Int64,
  good : Bool,
) -> Unit {
  match self.window_start {
    None => self.window_start = Some(timestamp)
    Some(start) =>
      if timestamp - start >= self.objective.window() {
        self.window_start = Some(timestamp)
        self.total = 0
        self.good = 0
        self.bad = 0
      }
  }
  self.total += 1
  if good {
    self.good += 1
  } else {
    self.bad += 1
  }
}

///|
pub fn ProductionSloTracker::observe_event(
  self : ProductionSloTracker,
  timestamp : Int64,
  event : ProductionMonitorEvent,
) -> Unit {
  self.observe(timestamp, !event.result().changed)
}

///|
pub fn ProductionSloTracker::report(
  self : ProductionSloTracker,
) -> ProductionSloReport {
  let compliance = if self.total == 0 {
    1.0
  } else {
    self.good.to_double() / self.total.to_double()
  }
  let budget = self.objective.error_budget()
  let error_rate = 1.0 - compliance
  let burn = if budget <= 1.0e-12 {
    if error_rate > 0.0 {
      1.0e6
    } else {
      0.0
    }
  } else {
    error_rate / budget
  }
  {
    objective: self.objective,
    total: self.total,
    good: self.good,
    bad: self.bad,
    compliance,
    burn_rate: burn,
    remaining_budget: clamp_probability(
      1.0 - error_rate / (if budget <= 1.0e-12 { 1.0 } else { budget }),
    ),
    breached: compliance < self.objective.target ||
    burn > self.objective.burn_limit,
  }
}

///|
pub fn ProductionSloTracker::reset(self : ProductionSloTracker) -> Unit {
  self.total = 0
  self.good = 0
  self.bad = 0
  self.window_start = None
}

///|
pub fn production_slo_reports_markdown(
  reports : Array[ProductionSloReport],
) -> String {
  let mut output = "| SLO | total | good | bad | compliance | burn rate | budget | breached |\n|---|---:|---:|---:|---:|---:|---:|---|\n"
  for report in reports {
    output = output +
      "| " +
      report.objective().name() +
      " | " +
      report.total().to_string() +
      " | " +
      report.good().to_string() +
      " | " +
      report.bad().to_string() +
      " | " +
      report.compliance().to_string() +
      " | " +
      report.burn_rate().to_string() +
      " | " +
      report.remaining_budget().to_string() +
      " | " +
      report.breached().to_string() +
      " |\n"
  }
  output
}