///|
/// High-level facade that ties registration, enqueueing, delivery, dead
/// letters, idempotency, and audit logging into one object.
pub struct MoonHook {
  registry : HookRegistry
  outbox : InMemoryOutbox
  dead_letter : InMemoryDeadLetter
  idempotency : IdempotencyStore
  audit : AuditLog
  engine : DeliveryEngine
  idempotency_ttl_ms : Int
}

///|
pub fn MoonHook::new(transport : Transport) -> MoonHook {
  let registry = HookRegistry::new()
  let engine = DeliveryEngine::new(registry, transport)
  {
    registry,
    outbox: InMemoryOutbox::new(),
    dead_letter: InMemoryDeadLetter::new(),
    idempotency: IdempotencyStore::new(),
    audit: AuditLog::new(),
    engine,
    idempotency_ttl_ms: 60_000,
  }
}

///|
pub fn MoonHook::with_idempotency_ttl_ms(
  self : MoonHook,
  idempotency_ttl_ms : Int,
) -> MoonHook {
  {
    registry: self.registry,
    outbox: self.outbox,
    dead_letter: self.dead_letter,
    idempotency: self.idempotency,
    audit: self.audit,
    engine: self.engine,
    idempotency_ttl_ms,
  }
}

///|
pub fn MoonHook::register(self : MoonHook, hook : Hook) -> Result[Unit, String] {
  self.registry.register(hook)
}

///|
pub fn MoonHook::hook_count(self : MoonHook) -> Int {
  self.registry.count()
}

///|
pub fn MoonHook::outbox_length(self : MoonHook) -> Int {
  self.outbox.length()
}

///|
pub fn MoonHook::dead_letter_length(self : MoonHook) -> Int {
  self.dead_letter.length()
}

///|
pub fn MoonHook::dead_letters(self : MoonHook) -> Array[DeadLetterEntry] {
  self.dead_letter.list()
}

///|
pub fn MoonHook::audit_entries(self : MoonHook) -> Array[AuditEntry] {
  self.audit.list()
}

///|
/// Enqueues an event into the outbox after checking hook state and the
/// idempotency window. Returns `Err` for unknown/inactive hooks and duplicates.
pub fn MoonHook::enqueue(
  self : MoonHook,
  hook_id : String,
  event : WebhookEvent,
  now_ms : Int,
) -> Result[Unit, String] {
  match self.registry.get(hook_id) {
    None => Err("hook not found: " + hook_id)
    Some(hook) =>
      if !hook.active {
        Err("hook is inactive: " + hook_id)
      } else if !self.idempotency.check_and_mark(
          event.idempotency_key,
          event.id,
          now_ms,
          self.idempotency_ttl_ms,
        ) {
        Err("duplicate idempotency key: " + event.idempotency_key)
      } else {
        self.outbox.enqueue(hook_id, event, now_ms)
        Ok(())
      }
  }
}

///|
/// Delivers immediately, records audit entries, and moves permanent failures
/// into the dead letter queue.
pub fn MoonHook::deliver_now(
  self : MoonHook,
  hook_id : String,
  event : WebhookEvent,
  now_ms : Int,
) -> DeliveryOutcome {
  let outcome = self.engine.deliver(hook_id, event)
  self.record_audit(outcome, now_ms)
  if outcome.status == DeadLetter {
    self.dead_letter.push({
      hook_id,
      event,
      reason: "delivery failed",
      failed_at_ms: now_ms,
      attempts: outcome.attempts.length(),
    })
  }
  outcome
}

///|
/// Drains the outbox through the delivery engine and returns the number of
/// deliveries attempted. Skipped or failed deliveries land in the dead letter
/// queue.
pub fn MoonHook::drain(self : MoonHook, now_ms : Int) -> Int {
  let pending = self.outbox.take_all()
  let mut delivered = 0
  for item in pending {
    match self.registry.get(item.hook_id) {
      None =>
        self.dead_letter.push({
          hook_id: item.hook_id,
          event: item.event,
          reason: "hook not found",
          failed_at_ms: now_ms,
          attempts: item.attempts_used,
        })
      Some(hook) =>
        if !hook.active {
          self.dead_letter.push({
            hook_id: item.hook_id,
            event: item.event,
            reason: "hook is inactive",
            failed_at_ms: now_ms,
            attempts: item.attempts_used,
          })
        } else {
          let outcome = self.engine.deliver(item.hook_id, item.event)
          self.record_audit(outcome, now_ms)
          if outcome.status == DeadLetter {
            self.dead_letter.push({
              hook_id: item.hook_id,
              event: item.event,
              reason: "delivery failed",
              failed_at_ms: now_ms,
              attempts: outcome.attempts.length(),
            })
          }
          delivered = delivered + 1
        }
    }
  }
  delivered
}

///|
fn MoonHook::record_audit(
  self : MoonHook,
  outcome : DeliveryOutcome,
  now_ms : Int,
) -> Unit {
  for attempt in outcome.attempts {
    self.audit.record({
      timestamp_ms: now_ms,
      hook_id: outcome.hook_id,
      event_id: outcome.event_id,
      attempt: attempt.attempt,
      status: outcome.status.describe(),
      error: attempt.error,
      duration_ms: attempt.duration_ms,
    })
  }
}