///|
pub struct SecuritySignal {
  code : String
  weight : Int
  detail : String
} derive(Eq, Debug)

///|
pub fn security_signals(message : MailMessage) -> Array[SecuritySignal] {
  let out : Array[SecuritySignal] = []
  if has_auth_fail(message, "spf") {
    out.push(signal("auth.spf.fail", 30, "SPF failed"))
  }
  if has_auth_fail(message, "dkim") {
    out.push(signal("auth.dkim.fail", 30, "DKIM failed"))
  }
  if has_auth_fail(message, "dmarc") {
    out.push(signal("auth.dmarc.fail", 40, "DMARC failed"))
  }
  if sender_domain(message) != "" &&
    reply_to_domain(message) != "" &&
    sender_domain(message) != reply_to_domain(message) {
    out.push(
      signal(
        "reply_to.mismatch", 15, "Reply-To domain differs from From domain",
      ),
    )
  }
  for url in message_urls(message) {
    if url_looks_risky(url) {
      out.push(signal("url.risky", 15, url))
    }
  }
  for item in attachment_infos(message) {
    if is_suspicious_filename(item.filename) {
      out.push(signal("attachment.risky", 25, item.filename))
    }
  }
  for finding in privacy_findings(message) {
    if finding.code == "tracking.pixel" {
      out.push(signal("privacy.tracking_pixel", 5, finding.detail))
    }
  }
  out
}

///|
pub fn signal(code : String, weight : Int, detail : String) -> SecuritySignal {
  { code, weight, detail }
}

///|
pub fn security_score(message : MailMessage) -> Int {
  let mut score = 0
  for item in security_signals(message) {
    score = score + item.weight
  }
  if score > 100 {
    100
  } else {
    score
  }
}

///|
pub fn is_high_risk(message : MailMessage) -> Bool {
  security_score(message) >= 60
}

///|
pub fn reply_to_domain(message : MailMessage) -> String {
  let reply = parse_address_list(header_value(message.headers, "Reply-To"))
  if reply.length() > 0 {
    mailbox_domain(reply[0].mailbox)
  } else {
    ""
  }
}

///|
fn url_looks_risky(url : String) -> Bool {
  let lower = lower_ascii(url)
  lower.contains("@") ||
  lower.contains("%00") ||
  lower.contains("xn--") ||
  lower.contains("login") ||
  lower.contains("verify") ||
  lower.contains("reset")
}

///|
pub fn has_security_signal(
  signals : Array[SecuritySignal],
  code : String,
) -> Bool {
  for item in signals {
    if item.code == code {
      return true
    }
  }
  false
}

///|
pub fn security_summary(message : MailMessage) -> String {
  let mut out = "score=" + security_score(message).to_string()
  for item in security_signals(message) {
    out = out + " " + item.code
  }
  out
}