///|
/// Verification outcome for request signatures and timestamp checks.
pub(all) enum VerificationStatus {
  Verified
  MissingSignature
  InvalidSignature
  ExpiredTimestamp
  Unsupported
} derive(Eq, Debug)

///|
/// Details returned by a verifier.
pub(all) struct VerificationResult {
  status : VerificationStatus
  reason : String
} derive(Eq, Debug)

///|
/// Stable text used by diagnostics and examples.
pub fn VerificationStatus::to_string(self : VerificationStatus) -> String {
  match self {
    Verified => "Verified"
    MissingSignature => "MissingSignature"
    InvalidSignature => "InvalidSignature"
    ExpiredTimestamp => "ExpiredTimestamp"
    Unsupported => "Unsupported"
  }
}

///|
/// Construct a verification result.
pub fn VerificationResult::VerificationResult(
  status : VerificationStatus,
  reason : StringView,
) -> VerificationResult {
  { status, reason: reason.to_owned() }
}

///|
/// Signature verifier configuration.
pub(all) struct SignatureVerifier {
  algorithm : String
  header_name : String
  secret_hint : String
} derive(Eq, Debug)

///|
/// Construct a verifier descriptor.
pub fn SignatureVerifier::SignatureVerifier(
  algorithm : StringView,
  header_name : StringView,
  secret_hint : StringView,
) -> SignatureVerifier {
  {
    algorithm: algorithm.to_owned(),
    header_name: header_name.to_owned(),
    secret_hint: secret_hint.to_owned(),
  }
}

///|
/// Check whether the required signature header exists.
pub fn SignatureVerifier::verify_presence(
  self : SignatureVerifier,
  request : HookRequest,
) -> VerificationResult {
  match request.header(self.header_name) {
    Some(value) =>
      if value == "" {
        VerificationResult(InvalidSignature, "signature header is empty")
      } else {
        VerificationResult(Verified, "signature header is present")
      }
    None => VerificationResult(MissingSignature, "signature header is missing")
  }
}

///|
/// Verify an HMAC-SHA256 signature against the unmodified request body.
/// The comparison is constant-time and accepts the conventional `sha256=` prefix.
pub fn SignatureVerifier::verify_hmac_sha256(
  self : SignatureVerifier,
  request : HookRequest,
  secret : String,
) -> VerificationResult {
  match request.header(self.header_name) {
    None => VerificationResult(MissingSignature, "signature header is missing")
    Some(actual) =>
      if secret == "" {
        VerificationResult(Unsupported, "verification secret is empty")
      } else {
        let expected = "sha256=" + hmac_sha256_hex(secret, request.body)
        if constant_time_equal(actual.to_lower(), expected) {
          VerificationResult(Verified, "HMAC-SHA256 signature is valid")
        } else {
          VerificationResult(InvalidSignature, "HMAC-SHA256 signature mismatch")
        }
      }
  }
}

///|
/// Verify a GitHub `X-Hub-Signature-256` header using the exact raw body.
pub fn verify_github_signature(
  request : HookRequest,
  secret : String,
) -> VerificationResult {
  github_sha256_verifier().verify_hmac_sha256(request, secret)
}

///|
/// Standard GitHub HMAC-SHA256 verifier descriptor.
pub fn github_sha256_verifier() -> SignatureVerifier {
  SignatureVerifier(
    "hmac-sha256", "x-hub-signature-256", "GITHUB_WEBHOOK_SECRET",
  )
}

///|
/// Standard GitLab token verifier descriptor.
pub fn gitlab_token_verifier() -> SignatureVerifier {
  SignatureVerifier("token", "x-gitlab-token", "GITLAB_WEBHOOK_TOKEN")
}

///|
/// Standard Stripe signature verifier descriptor.
pub fn stripe_signature_verifier() -> SignatureVerifier {
  SignatureVerifier("stripe-v1", "stripe-signature", "STRIPE_WEBHOOK_SECRET")
}