///|
pub(all) enum Severity {
  Pass
  Warn
  Fail
} derive(Eq, Debug)

///|
pub(all) struct PolicyFinding {
  id : String
  severity : Severity
  title : String
  detail : String
  evidence : String
  repair : String
  penalty : Int
} derive(Eq, Debug)

///|
pub(all) struct SpfMechanism {
  raw : String
  qualifier : String
  name : String
  value : String
  dns_lookup : Bool
  terminal : Bool
} derive(Eq, Debug)

///|
pub(all) struct SpfRecord {
  raw : String
  valid_version : Bool
  mechanisms : Array[SpfMechanism]
  redirect : String
  explanation : String
  dns_lookup_count : Int
  all_qualifier : String
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct DmarcRecord {
  raw : String
  valid_version : Bool
  policy : String
  subdomain_policy : String
  pct : Int
  rua_count : Int
  ruf_count : Int
  alignment_spf : String
  alignment_dkim : String
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct MailPolicyReport {
  domain : String
  spf : SpfRecord
  dmarc : DmarcRecord
  score : Int
  verdict : String
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct ReportSummary {
  pass_count : Int
  warn_count : Int
  fail_count : Int
  total_count : Int
} derive(Eq, Debug)

///|
pub(all) struct ActionItem {
  id : String
  module_name : String
  title : String
  priority : Int
  detail : String
  next_step : String
} derive(Eq, Debug)

///|
pub(all) struct DkimRecord {
  raw : String
  valid_version : Bool
  key_type : String
  key_length_hint : Int
  public_key_length : Int
  testing_mode : Bool
  service_count : Int
  hash_count : Int
  notes : String
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct MtastsRecord {
  raw : String
  valid_version : Bool
  version : String
  mode : String
  max_age : Int
  id : String
  mx_count : Int
  mx_hosts : Array[String]
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct TlsRptRecord {
  raw : String
  valid_version : Bool
  version : String
  rua_count : Int
  mailto_count : Int
  https_count : Int
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct BimiRecord {
  raw : String
  valid_version : Bool
  version : String
  logo_url : String
  authority_url : String
  logo_svg : Bool
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct MailSecuritySuiteReport {
  domain : String
  spf : SpfRecord
  dmarc : DmarcRecord
  dkim : DkimRecord
  mtasts : MtastsRecord
  tlsrpt : TlsRptRecord
  bimi : BimiRecord
  score : Int
  verdict : String
  summary : ReportSummary
  actions : Array[ActionItem]
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct MailSecuritySuiteInput {
  domain : String
  spf_text : String
  dmarc_text : String
  dkim_text : String
  mtasts_text : String
  tlsrpt_text : String
  bimi_text : String
} derive(Eq, Debug)

///|
pub(all) struct BatchSecurityReport {
  reports : Array[MailSecuritySuiteReport]
  domains_checked : Int
  average_score : Int
  lowest_score : Int
  highest_score : Int
  summary : ReportSummary
  actions : Array[ActionItem]
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
pub(all) struct MailSecurityProfile {
  name : String
  minimum_score : Int
  require_spf_hardfail : Bool
  require_dmarc_enforcement : Bool
  require_dkim : Bool
  require_mtasts : Bool
  require_tlsrpt : Bool
  require_bimi : Bool
  allow_testing_modes : Bool
} derive(Eq, Debug)

///|
pub(all) struct ProfileAssessment {
  profile_name : String
  passed : Bool
  score : Int
  missing_count : Int
  failed_requirements : Array[String]
  findings : Array[PolicyFinding]
} derive(Eq, Debug)

///|
fn finding(
  id : String,
  severity : Severity,
  title : String,
  detail : String,
  evidence : String,
  repair : String,
  penalty : Int,
) -> PolicyFinding {
  { id, severity, title, detail, evidence, repair, penalty }
}

///|
fn pass(id : String, title : String, evidence : String) -> PolicyFinding {
  finding(id, Pass, title, "Requirement satisfied.", evidence, "", 0)
}

///|
fn warn(
  id : String,
  title : String,
  detail : String,
  evidence : String,
  repair : String,
  penalty? : Int = 6,
) -> PolicyFinding {
  finding(id, Warn, title, detail, evidence, repair, penalty)
}

///|
fn fail(
  id : String,
  title : String,
  detail : String,
  evidence : String,
  repair : String,
  penalty? : Int = 18,
) -> PolicyFinding {
  finding(id, Fail, title, detail, evidence, repair, penalty)
}

///|
pub fn ReportSummary::has_failures(self : ReportSummary) -> Bool {
  self.fail_count > 0
}

///|
pub fn ReportSummary::has_warnings(self : ReportSummary) -> Bool {
  self.warn_count > 0
}

///|
pub fn ReportSummary::is_clean(self : ReportSummary) -> Bool {
  self.fail_count == 0 && self.warn_count == 0
}

///|
pub fn ReportSummary::to_short_text(self : ReportSummary) -> String {
  "pass=" +
  self.pass_count.to_string() +
  " warn=" +
  self.warn_count.to_string() +
  " fail=" +
  self.fail_count.to_string()
}

///|
pub fn ActionItem::to_markdown(self : ActionItem) -> String {
  let out = StringBuilder()
  out.write_string("- [")
  out.write_string(self.module_name)
  out.write_string("] ")
  out.write_string(self.id)
  out.write_string(": ")
  out.write_string(self.title)
  out.write_string(" | priority=")
  out.write_string(self.priority.to_string())
  if !self.detail.is_empty() {
    out.write_string(" | ")
    out.write_string(self.detail)
  }
  if !self.next_step.is_empty() {
    out.write_string(" | next: ")
    out.write_string(self.next_step)
  }
  out.to_string()
}