///|
pub(all) struct WindowStatistics {
  window_count : Int
  total_requests : Int
  total_good : Int
  total_bad : Int
  min_availability_bp : Int
  max_availability_bp : Int
  average_availability_bp : Int
} derive(Eq, Debug)

///|
pub fn WindowStatistics::from_windows(
  windows : Array[RequestWindow],
) -> WindowStatistics {
  if windows.length() == 0 {
    {
      window_count: 0,
      total_requests: 0,
      total_good: 0,
      total_bad: 0,
      min_availability_bp: 10000,
      max_availability_bp: 0,
      average_availability_bp: 10000,
    }
  } else {
    let mut total_requests = 0
    let mut total_good = 0
    let mut total_bad = 0
    let mut min_availability_bp = 10000
    let mut max_availability_bp = 0
    let mut availability_sum = 0
    for window in windows {
      let availability = window.availability_bp()
      total_requests = total_requests + window.total
      total_good = total_good + window.good
      total_bad = total_bad + window.bad()
      availability_sum = availability_sum + availability
      if availability < min_availability_bp {
        min_availability_bp = availability
      }
      if availability > max_availability_bp {
        max_availability_bp = availability
      }
    }
    {
      window_count: windows.length(),
      total_requests,
      total_good,
      total_bad,
      min_availability_bp,
      max_availability_bp,
      average_availability_bp: availability_sum / windows.length(),
    }
  }
}

///|
pub fn WindowStatistics::error_rate_bp(self : WindowStatistics) -> Int {
  if self.total_requests == 0 {
    0
  } else {
    self.total_bad * 10000 / self.total_requests
  }
}

///|
pub fn WindowStatistics::healthy(
  self : WindowStatistics,
  target : SloTarget,
) -> Bool {
  self.error_rate_bp() <= target.allowed_error_bp()
}

///|
pub fn WindowStatistics::to_json(self : WindowStatistics) -> String {
  "{\"window_count\":\{self.window_count},\"total_requests\":\{self.total_requests},\"total_good\":\{self.total_good},\"total_bad\":\{self.total_bad},\"min_availability_bp\":\{self.min_availability_bp},\"max_availability_bp\":\{self.max_availability_bp},\"average_availability_bp\":\{self.average_availability_bp}}"
}

///|
pub fn WindowStatistics::merge(
  self : WindowStatistics,
  other : WindowStatistics,
) -> WindowStatistics {
  let count = self.window_count + other.window_count
  let min_value = if self.min_availability_bp < other.min_availability_bp {
    self.min_availability_bp
  } else {
    other.min_availability_bp
  }
  let max_value = if self.max_availability_bp > other.max_availability_bp {
    self.max_availability_bp
  } else {
    other.max_availability_bp
  }
  {
    window_count: count,
    total_requests: self.total_requests + other.total_requests,
    total_good: self.total_good + other.total_good,
    total_bad: self.total_bad + other.total_bad,
    min_availability_bp: min_value,
    max_availability_bp: max_value,
    average_availability_bp: if count == 0 {
      10000
    } else {
      (
        self.average_availability_bp * self.window_count +
        other.average_availability_bp * other.window_count
      ) /
      count
    },
  }
}

///|
pub fn windows_in_range(
  windows : Array[RequestWindow],
  start : Int,
  end : Int,
) -> Array[RequestWindow] {
  let result = []
  for window in windows {
    if window.minutes >= start && window.minutes <= end {
      result.push(window)
    }
  }
  result
}

///|
pub fn availability_band(availability_bp : Int) -> String {
  if availability_bp >= 9990 {
    "excellent"
  } else if availability_bp >= 9900 {
    "healthy"
  } else if availability_bp >= 9500 {
    "degraded"
  } else {
    "critical"
  }
}

///|
pub fn error_rate_percent(error_bp : Int) -> String {
  let value = if error_bp < 0 { 0 } else { error_bp }
  "\{value / 100}.\{two_digits(value % 100)}%"
}