///|
fn timestamp_diagnostic(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> GuardDiagnostic? {
  let value = provider_timestamp(request, policy.provider)
  if policy.require_timestamp && value == 0 {
    Some(GuardDiagnostic::new(MissingTimestamp, "timestamp header is missing"))
  } else if value != 0 &&
    abs_int(policy.now - value) > policy.provider.timestamp_tolerance_seconds {
    Some(
      GuardDiagnostic::new(
        TimestampOutsideWindow,
        "timestamp is outside allowed window",
      ),
    )
  } else {
    None
  }
}

///|
fn signature_diagnostic(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> GuardDiagnostic? {
  let signature = request.signature(policy.provider.signature_header)
  if policy.require_signature && signature == "" {
    Some(GuardDiagnostic::new(MissingSignature, "signature header is missing"))
  } else if signature != "" && !verify_signature(request, policy) {
    Some(
      GuardDiagnostic::new(
        SignatureMismatch,
        "signature does not match canonical payload",
      ),
    )
  } else {
    None
  }
}

///|
fn event_id_diagnostic(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> GuardDiagnostic? {
  let event_id = provider_event_id(request, policy.provider)
  if policy.require_event_id && event_id == "" {
    Some(GuardDiagnostic::new(MissingEventId, "event id header is missing"))
  } else {
    None
  }
}

///|
fn provider_diagnostic(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> GuardDiagnostic? {
  if request.provider != "" && request.provider != policy.provider.name {
    Some(
      GuardDiagnostic::new(
        ProviderMismatch,
        "request provider does not match policy",
      ),
    )
  } else {
    None
  }
}

///|
fn collect_base_diagnostics(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> Array[GuardDiagnostic] {
  let candidates = [
    provider_diagnostic(request, policy),
    timestamp_diagnostic(request, policy),
    signature_diagnostic(request, policy),
    event_id_diagnostic(request, policy),
  ]
  let mut count = 0
  for i in 0.. {
    while offset < candidates.length() {
      let value = candidates[offset]
      offset = offset + 1
      match value {
        Some(diag) => return diag
        None => ()
      }
    }
    GuardDiagnostic::new(AcceptedDiagnostic, "accepted")
  })
}

///|
pub fn verify_request(
  request : WebhookRequest,
  policy : VerificationPolicy,
) -> VerificationResult {
  let diagnostics = collect_base_diagnostics(request, policy)
  let event_type = provider_event_type(request, policy.provider)
  let event_id = provider_event_id(request, policy.provider)
  if diagnostics.length() == 0 {
    VerificationResult::accepted(policy.provider.name, event_type, event_id)
  } else {
    VerificationResult::rejected(
      policy.provider.name,
      event_type,
      event_id,
      diagnostics,
    )
  }
}

///|
pub fn verify_request_with_state(
  request : WebhookRequest,
  policy : VerificationPolicy,
  state : GuardState,
) -> (VerificationResult, GuardState) {
  let base = verify_request(request, policy)
  if !base.is_ok() {
    (base, state)
  } else {
    let replay = replay_key(request, policy)
    let idem = idempotency_key(request, policy)
    if state.replay_store.contains(replay, policy.now) {
      (
        VerificationResult::rejected(
          policy.provider.name,
          base.event_type,
          base.event_id,
          [
            GuardDiagnostic::new(
              ReplayDetected,
              "replay key has already been seen",
            ),
          ],
        ),
        state,
      )
    } else if state.idempotency_store.contains(idem, policy.now) {
      (
        VerificationResult::rejected(
          policy.provider.name,
          base.event_type,
          base.event_id,
          [
            GuardDiagnostic::new(
              DuplicateIdempotencyKey,
              "idempotency key has already been seen",
            ),
          ],
        ),
        state,
      )
    } else {
      (base, state.remember(request, policy))
    }
  }
}

///|
pub fn verify_batch(
  requests : Array[WebhookRequest],
  policy : VerificationPolicy,
) -> Array[VerificationResult] {
  Array::makei(requests.length(), i => verify_request(requests[i], policy))
}

///|
pub fn accepted_count(results : Array[VerificationResult]) -> Int {
  let mut count = 0
  for i in 0.. Int {
  results.length() - accepted_count(results)
}