///|
pub struct SeenEntry {
  key : String
  expires_at : Int
} derive(Eq, Debug)

///|
pub fn SeenEntry::new(key : String, expires_at : Int) -> SeenEntry {
  { key, expires_at }
}

///|
pub struct ReplayStore {
  entries : Array[SeenEntry]
} derive(Eq, Debug)

///|
pub fn ReplayStore::empty() -> ReplayStore {
  { entries: [] }
}

///|
pub fn ReplayStore::contains(
  self : ReplayStore,
  key : String,
  now : Int,
) -> Bool {
  for i in 0..= now {
      return true
    }
  }
  false
}

///|
pub fn ReplayStore::remember(
  self : ReplayStore,
  key : String,
  now : Int,
  ttl_seconds : Int,
) -> ReplayStore {
  let expires_at = now + ttl_seconds
  let old = self.entries
  let entry = SeenEntry::new(key, expires_at)
  let entries = Array::makei(old.length() + 1, i => {
    if i < old.length() {
      old[i]
    } else {
      entry
    }
  })
  { entries, }
}

///|
pub fn ReplayStore::prune(self : ReplayStore, now : Int) -> ReplayStore {
  let mut count = 0
  for i in 0..= now {
      count = count + 1
    }
  }
  let mut offset = 0
  let entries = Array::makei(count, _ => {
    while offset < self.entries.length() &&
          self.entries[offset].expires_at < now {
      offset = offset + 1
    }
    let entry = self.entries[offset]
    offset = offset + 1
    entry
  })
  { entries, }
}

///|
pub fn replay_key(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> String {
  policy.provider.name +
  ":" +
  provider_event_id(request, policy.provider) +
  ":" +
  request.signature(policy.provider.signature_header)
}

///|
pub struct IdempotencyStore {
  entries : Array[SeenEntry]
} derive(Eq, Debug)

///|
pub fn IdempotencyStore::empty() -> IdempotencyStore {
  { entries: [] }
}

///|
pub fn IdempotencyStore::contains(
  self : IdempotencyStore,
  key : String,
  now : Int,
) -> Bool {
  for i in 0..= now {
      return true
    }
  }
  false
}

///|
pub fn IdempotencyStore::remember(
  self : IdempotencyStore,
  key : String,
  now : Int,
  ttl_seconds : Int,
) -> IdempotencyStore {
  let old = self.entries
  let entry = SeenEntry::new(key, now + ttl_seconds)
  let entries = Array::makei(old.length() + 1, i => {
    if i < old.length() {
      old[i]
    } else {
      entry
    }
  })
  { entries, }
}

///|
pub fn idempotency_key(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> String {
  let event_id = provider_event_id(request, policy.provider)
  if event_id == "" {
    stable_sign(policy.secret, canonical_base(request, policy.provider)).value
  } else {
    policy.provider.name + ":" + event_id
  }
}

///|
pub struct GuardState {
  replay_store : ReplayStore
  idempotency_store : IdempotencyStore
} derive(Eq, Debug)

///|
pub fn GuardState::empty() -> GuardState {
  {
    replay_store: ReplayStore::empty(),
    idempotency_store: IdempotencyStore::empty(),
  }
}

///|
pub fn GuardState::remember(
  self : GuardState,
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> GuardState {
  {
    replay_store: self.replay_store.remember(
      replay_key(request, policy),
      policy.now,
      policy.provider.timestamp_tolerance_seconds,
    ),
    idempotency_store: self.idempotency_store.remember(
      idempotency_key(request, policy),
      policy.now,
      policy.provider.timestamp_tolerance_seconds,
    ),
  }
}