///|
pub struct ProviderRequestMetadata {
  provider : String
  event_type : String
  event_id : String
  timestamp : Int
  nonce : String
  signature_present : Bool
} derive(Eq, Debug)

///|
pub fn ProviderRequestMetadata::new(
  provider : String,
  event_type : String,
  event_id : String,
  timestamp : Int,
  nonce : String,
  signature_present : Bool,
) -> ProviderRequestMetadata {
  { provider, event_type, event_id, timestamp, nonce, signature_present }
}

///|
fn header_or_empty(request : WebhookRequest, name : String) -> String {
  if name == "" {
    ""
  } else {
    request.header(name).unwrap_or("")
  }
}

///|
pub fn provider_timestamp(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> Int {
  match profile.name {
    "stripe" =>
      parse_stripe_signature(request.signature(profile.signature_header)).timestamp
    "github" => 0
    _ => parse_positive_int(header_or_empty(request, profile.timestamp_header))
  }
}

///|
pub fn provider_timestamp_text(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> String {
  match profile.name {
    "stripe" =>
      parse_stripe_signature(request.signature(profile.signature_header)).timestamp_text
    "github" => ""
    _ => header_or_empty(request, profile.timestamp_header)
  }
}

///|
fn stripe_event_type(request : WebhookRequest) -> String {
  payload_path_text(request.body, ["type"]).unwrap_or("")
}

///|
fn stripe_event_id(request : WebhookRequest) -> String {
  payload_path_text(request.body, ["id"]).unwrap_or("")
}

///|
fn feishu_event_type(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> String {
  payload_first_path(request.body, [
    ["header", "event_type"],
    ["event_type"],
    ["type"],
  ]).unwrap_or(header_or_empty(request, profile.event_type_header))
}

///|
fn feishu_event_id(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> String {
  payload_first_path(request.body, [
    ["header", "event_id"],
    ["event_id"],
    ["uuid"],
  ]).unwrap_or(header_or_empty(request, profile.event_id_header))
}

///|
pub fn provider_event_type(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> String {
  match profile.name {
    "stripe" => stripe_event_type(request)
    "feishu" => feishu_event_type(request, profile)
    _ => header_or_empty(request, profile.event_type_header)
  }
}

///|
pub fn provider_event_id(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> String {
  match profile.name {
    "stripe" => stripe_event_id(request)
    "feishu" => feishu_event_id(request, profile)
    _ => header_or_empty(request, profile.event_id_header)
  }
}

///|
pub fn provider_nonce(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> String {
  match profile.name {
    "feishu" => request.header("x-lark-request-nonce").unwrap_or("")
    "stripe" => provider_timestamp_text(request, profile)
    _ => provider_event_id(request, profile)
  }
}

///|
pub fn provider_metadata(
  request : WebhookRequest,
  profile : ProviderProfile,
) -> ProviderRequestMetadata {
  ProviderRequestMetadata::new(
    profile.name,
    provider_event_type(request, profile),
    provider_event_id(request, profile),
    provider_timestamp(request, profile),
    provider_nonce(request, profile),
    request.signature(profile.signature_header) != "",
  )
}

///|
pub fn provider_signature_algorithm(profile : ProviderProfile) -> String {
  match profile.name {
    "github" => "HMAC-SHA256(raw-body)"
    "stripe" => "HMAC-SHA256(timestamp.raw-body)"
    "feishu" => "SHA256(timestamp+nonce+secret+raw-body)"
    _ => "HMAC-SHA256(canonical-request)"
  }
}

///|
pub fn provider_metadata_text(metadata : ProviderRequestMetadata) -> String {
  let builder = StringBuilder::new()
  builder.write_string("provider=")
  builder.write_string(metadata.provider)
  builder.write_string(" event=")
  builder.write_string(metadata.event_type)
  builder.write_string(" id=")
  builder.write_string(metadata.event_id)
  builder.write_string(" timestamp=")
  builder.write_string(metadata.timestamp.to_string())
  builder.write_string(" nonce=")
  if metadata.nonce == "" {
    builder.write_string("missing")
  } else {
    builder.write_string("present")
  }
  builder.write_string(" signature=")
  builder.write_string(
    if metadata.signature_present {
      "present"
    } else {
      "missing"
    },
  )
  builder.to_string()
}

///|
pub fn provider_metadata_is_complete(
  metadata : ProviderRequestMetadata,
  require_timestamp : Bool,
  require_event_id : Bool,
) -> Bool {
  metadata.signature_present &&
  (!require_timestamp || metadata.timestamp > 0) &&
  (!require_event_id || metadata.event_id != "")
}