///|
pub(all) struct HandlerMetrics {
  handler : String
  handled : Int
  ignored : Int
  failed : Int
} derive(Eq, Debug)

///|
pub(all) struct TopicMetrics {
  topic : String
  published : Int
  delivered : Int
  failed : Int
} derive(Eq, Debug)

///|
/// Immutable metrics snapshot built from dispatch reports.
pub(all) struct BusMetrics {
  published : Int
  successful_events : Int
  failed_events : Int
  stopped_events : Int
  handler_deliveries : Int
  handler_ignored : Int
  handler_failures : Int
  handlers : Array[HandlerMetrics]
  topics : Array[TopicMetrics]
} derive(Eq, Debug)

///|
pub fn BusMetrics::new() -> BusMetrics {
  {
    published: 0,
    successful_events: 0,
    failed_events: 0,
    stopped_events: 0,
    handler_deliveries: 0,
    handler_ignored: 0,
    handler_failures: 0,
    handlers: [],
    topics: [],
  }
}

///|
fn update_handler_metrics(
  items : Array[HandlerMetrics],
  report : HandlerReport,
) -> Array[HandlerMetrics] {
  let result : Array[HandlerMetrics] = []
  let mut found = false
  for item in items {
    if item.handler == report.handler {
      found = true
      match report.status {
        Handled => result.push({ ..item, handled: item.handled + 1 })
        Ignored => result.push({ ..item, ignored: item.ignored + 1 })
        Failed(_) => result.push({ ..item, failed: item.failed + 1 })
      }
    } else {
      result.push(item)
    }
  }
  if !found {
    match report.status {
      Handled =>
        result.push({
          handler: report.handler,
          handled: 1,
          ignored: 0,
          failed: 0,
        })
      Ignored =>
        result.push({
          handler: report.handler,
          handled: 0,
          ignored: 1,
          failed: 0,
        })
      Failed(_) =>
        result.push({
          handler: report.handler,
          handled: 0,
          ignored: 0,
          failed: 1,
        })
    }
  }
  result
}

///|
fn update_topic_metrics(
  items : Array[TopicMetrics],
  report : DispatchReport,
) -> Array[TopicMetrics] {
  let result : Array[TopicMetrics] = []
  let mut found = false
  for item in items {
    if item.topic == report.event.topic {
      found = true
      result.push({
        ..item,
        published: item.published + 1,
        delivered: item.delivered + report.delivered,
        failed: item.failed + report.failed,
      })
    } else {
      result.push(item)
    }
  }
  if !found {
    result.push({
      topic: report.event.topic,
      published: 1,
      delivered: report.delivered,
      failed: report.failed,
    })
  }
  result
}

///|
pub fn BusMetrics::record(
  self : BusMetrics,
  report : DispatchReport,
) -> BusMetrics {
  let mut handlers = self.handlers
  for item in report.reports {
    handlers = update_handler_metrics(handlers, item)
  }
  {
    published: self.published + 1,
    successful_events: self.successful_events +
    (if report.is_ok() { 1 } else { 0 }),
    failed_events: self.failed_events + (if report.is_ok() { 0 } else { 1 }),
    stopped_events: self.stopped_events + (if report.stopped { 1 } else { 0 }),
    handler_deliveries: self.handler_deliveries + report.delivered,
    handler_ignored: self.handler_ignored + report.ignored,
    handler_failures: self.handler_failures + report.failed,
    handlers,
    topics: update_topic_metrics(self.topics, report),
  }
}

///|
pub fn BusMetrics::record_many(
  self : BusMetrics,
  reports : Array[DispatchReport],
) -> BusMetrics {
  let mut metrics = self
  for report in reports {
    metrics = metrics.record(report)
  }
  metrics
}

///|
pub fn BusMetrics::handler(self : BusMetrics, name : String) -> HandlerMetrics? {
  for item in self.handlers {
    if item.handler == name {
      return Some(item)
    }
  }
  None
}

///|
pub fn BusMetrics::topic(self : BusMetrics, name : String) -> TopicMetrics? {
  for item in self.topics {
    if item.topic == name {
      return Some(item)
    }
  }
  None
}

///|
pub fn BusMetrics::success_percent(self : BusMetrics) -> Int {
  if self.published == 0 {
    100
  } else {
    self.successful_events * 100 / self.published
  }
}

///|
pub fn BusMetrics::delivery_percent(self : BusMetrics) -> Int {
  let attempts = self.handler_deliveries +
    self.handler_ignored +
    self.handler_failures
  if attempts == 0 {
    100
  } else {
    self.handler_deliveries * 100 / attempts
  }
}

///|
fn merge_handler_metric(
  items : Array[HandlerMetrics],
  added : HandlerMetrics,
) -> Array[HandlerMetrics] {
  let result : Array[HandlerMetrics] = []
  let mut found = false
  for item in items {
    if item.handler == added.handler {
      found = true
      result.push({
        handler: item.handler,
        handled: item.handled + added.handled,
        ignored: item.ignored + added.ignored,
        failed: item.failed + added.failed,
      })
    } else {
      result.push(item)
    }
  }
  if !found {
    result.push(added)
  }
  result
}

///|
fn merge_topic_metric(
  items : Array[TopicMetrics],
  added : TopicMetrics,
) -> Array[TopicMetrics] {
  let result : Array[TopicMetrics] = []
  let mut found = false
  for item in items {
    if item.topic == added.topic {
      found = true
      result.push({
        topic: item.topic,
        published: item.published + added.published,
        delivered: item.delivered + added.delivered,
        failed: item.failed + added.failed,
      })
    } else {
      result.push(item)
    }
  }
  if !found {
    result.push(added)
  }
  result
}

///|
pub fn BusMetrics::merge(self : BusMetrics, other : BusMetrics) -> BusMetrics {
  let mut handlers = self.handlers
  for item in other.handlers {
    handlers = merge_handler_metric(handlers, item)
  }
  let mut topics = self.topics
  for item in other.topics {
    topics = merge_topic_metric(topics, item)
  }
  {
    published: self.published + other.published,
    successful_events: self.successful_events + other.successful_events,
    failed_events: self.failed_events + other.failed_events,
    stopped_events: self.stopped_events + other.stopped_events,
    handler_deliveries: self.handler_deliveries + other.handler_deliveries,
    handler_ignored: self.handler_ignored + other.handler_ignored,
    handler_failures: self.handler_failures + other.handler_failures,
    handlers,
    topics,
  }
}

///|
pub fn BusMetrics::failing_handlers(self : BusMetrics) -> Array[HandlerMetrics] {
  let result : Array[HandlerMetrics] = []
  for item in self.handlers {
    if item.failed > 0 {
      result.push(item)
    }
  }
  result
}

///|
pub fn BusMetrics::summary(self : BusMetrics) -> String {
  "published=" +
  self.published.to_string() +
  ",success=" +
  self.successful_events.to_string() +
  ",failed=" +
  self.failed_events.to_string() +
  ",delivered=" +
  self.handler_deliveries.to_string()
}

///|
pub fn metrics_from_reports(reports : Array[DispatchReport]) -> BusMetrics {
  BusMetrics::new().record_many(reports)
}