///|
pub fn analyze_suite(
  domain : String,
  spf_text : String,
  dmarc_text : String,
  dkim_text : String,
  mtasts_text : String,
  tlsrpt_text : String,
  bimi_text : String,
) -> MailSecuritySuiteReport {
  let clean_domain = normalize_domain(domain)
  let spf = parse_spf(spf_text)
  let dmarc = parse_dmarc(dmarc_text)
  let dkim = parse_dkim(dkim_text)
  let mtasts = parse_mtasts(mtasts_text)
  let tlsrpt = parse_tlsrpt(tlsrpt_text)
  let bimi = parse_bimi(bimi_text)
  let findings = Array::new(capacity=64)
  append_domain_findings(findings, clean_domain)
  append_findings(findings, spf.findings)
  append_findings(findings, dmarc.findings)
  append_findings(findings, dkim.findings)
  append_findings(findings, mtasts.findings)
  append_findings(findings, tlsrpt.findings)
  append_findings(findings, bimi.findings)
  append_cross_record_findings(findings, spf, dmarc, dkim, mtasts, tlsrpt, bimi)
  let score = score_findings(findings)
  let summary = summarize_findings(findings)
  let actions = action_items(findings)
  {
    domain: clean_domain,
    spf,
    dmarc,
    dkim,
    mtasts,
    tlsrpt,
    bimi,
    score,
    verdict: suite_verdict(summary, score),
    summary,
    actions,
    findings,
  }
}

///|
pub fn suite_report_markdown(
  domain : String,
  spf_text : String,
  dmarc_text : String,
  dkim_text : String,
  mtasts_text : String,
  tlsrpt_text : String,
  bimi_text : String,
) -> String {
  analyze_suite(
    domain, spf_text, dmarc_text, dkim_text, mtasts_text, tlsrpt_text, bimi_text,
  ).to_markdown()
}

///|
pub fn suite_report_json(
  domain : String,
  spf_text : String,
  dmarc_text : String,
  dkim_text : String,
  mtasts_text : String,
  tlsrpt_text : String,
  bimi_text : String,
) -> String {
  analyze_suite(
    domain, spf_text, dmarc_text, dkim_text, mtasts_text, tlsrpt_text, bimi_text,
  ).to_json()
}

///|
fn append_domain_findings(
  findings : Array[PolicyFinding],
  domain : String,
) -> Unit {
  if domain.is_empty() {
    findings.push(
      fail(
        "domain.name", "Domain name is missing", "Suite reports should identify the domain being assessed.",
        "", "Pass a stable domain name to analyze_suite.",
      ),
    )
  } else if domain.contains(" ") {
    findings.push(
      fail(
        "domain.space", "Domain name contains spaces", "A DNS domain label cannot contain whitespace.",
        domain, "Pass a normalized DNS domain such as example.com.",
      ),
    )
  } else if domain.contains("_") {
    findings.push(
      warn(
        "domain.underscore",
        "Domain contains an underscore",
        "Underscores are normal for DNS service labels but unusual for an organizational domain.",
        domain,
        "Pass the organizational domain, not a service label, when building an overall report.",
        penalty=3,
      ),
    )
  } else {
    findings.push(pass("domain.name", "Domain name is present", domain))
  }
}

///|
fn append_findings(
  target : Array[PolicyFinding],
  source : Array[PolicyFinding],
) -> Unit {
  for item in source {
    target.push(item)
  }
}

///|
fn append_cross_record_findings(
  findings : Array[PolicyFinding],
  spf : SpfRecord,
  dmarc : DmarcRecord,
  dkim : DkimRecord,
  mtasts : MtastsRecord,
  tlsrpt : TlsRptRecord,
  bimi : BimiRecord,
) -> Unit {
  if spf.valid_version && dmarc.valid_version {
    findings.push(
      pass("suite.auth-pair", "SPF and DMARC are both present", "spf+dmarc"),
    )
  } else {
    findings.push(
      fail(
        "suite.auth-pair-missing", "SPF and DMARC are not both valid", "A production sending domain should have both SPF and DMARC policy records.",
        "", "Fix SPF and DMARC before onboarding the domain.",
      ),
    )
  }
  if dkim.is_configured() && dmarc.valid_version {
    findings.push(
      pass(
        "suite.dkim-dmarc",
        "DKIM and DMARC can work together",
        dkim.key_strength_label(),
      ),
    )
  } else if dmarc.policy == "reject" && !dkim.is_configured() {
    findings.push(
      warn(
        "suite.dkim-needed",
        "DMARC enforcement has no DKIM selector input",
        "SPF-only DMARC alignment is fragile when forwarding occurs.",
        "p=" + dmarc.policy,
        "Add at least one production DKIM selector record.",
      ),
    )
  }
  if bimi.is_configured() &&
    dmarc.policy != "reject" &&
    dmarc.policy != "quarantine" {
    findings.push(
      fail(
        "suite.bimi-dmarc",
        "BIMI is configured without DMARC enforcement",
        "BIMI should be used after DMARC is at quarantine or reject.",
        "p=" + dmarc.policy,
        "Move DMARC to quarantine or reject before advertising BIMI.",
      ),
    )
  } else if bimi.is_configured() {
    findings.push(
      pass(
        "suite.bimi-ready",
        "BIMI has a compatible DMARC policy",
        dmarc.policy,
      ),
    )
  }
  if mtasts.is_enforcing() && tlsrpt.is_configured() {
    findings.push(
      pass(
        "suite.transport-observable", "MTA-STS enforcement has TLS reporting", "mtasts+tlsrpt",
      ),
    )
  } else if mtasts.is_enforcing() && !tlsrpt.is_configured() {
    findings.push(
      warn(
        "suite.transport-reporting", "MTA-STS is enforced without TLS reporting",
        "TLS-RPT helps detect delivery failures after strict TLS rollout.", "mode=enforce",
        "Publish a TLS-RPT record before or with enforcement.",
      ),
    )
  } else if tlsrpt.is_configured() && !mtasts.is_configured() {
    findings.push(
      pass(
        "suite.transport-reporting-early", "TLS-RPT is available before MTA-STS enforcement",
        "tlsrpt",
      ),
    )
  }
  if spf.dns_lookup_count >= 8 && dkim.key_strength_label() == "missing" {
    findings.push(
      warn(
        "suite.sender-fragility",
        "Sender authentication is concentrated in SPF",
        "High SPF lookup usage plus missing DKIM makes forwarding and provider changes riskier.",
        spf.dns_lookup_count.to_string() + " SPF lookups",
        "Add DKIM and reduce unnecessary SPF mechanisms.",
      ),
    )
  }
  if dmarc.policy == "reject" &&
    spf.all_qualifier == "-" &&
    dkim.is_configured() &&
    mtasts.is_enforcing() &&
    tlsrpt.is_configured() {
    findings.push(
      pass(
        "suite.production-ready", "Core mail-domain controls look production ready",
        "spf+dmarc+dkim+mtasts+tlsrpt",
      ),
    )
  }
}

///|
pub fn summarize_findings(findings : Array[PolicyFinding]) -> ReportSummary {
  let mut pass_count = 0
  let mut warn_count = 0
  let mut fail_count = 0
  for finding in findings {
    if finding.severity == Pass {
      pass_count += 1
    } else if finding.severity == Warn {
      warn_count += 1
    } else {
      fail_count += 1
    }
  }
  {
    pass_count,
    warn_count,
    fail_count,
    total_count: pass_count + warn_count + fail_count,
  }
}

///|
fn suite_verdict(summary : ReportSummary, score : Int) -> String {
  if summary.fail_count > 0 {
    "risky"
  } else if summary.warn_count > 0 || score < 90 {
    "monitor"
  } else {
    "protected"
  }
}

///|
pub fn action_items(findings : Array[PolicyFinding]) -> Array[ActionItem] {
  let actions = Array::new()
  for finding in findings {
    if finding.severity != Pass {
      actions.push(action_from_finding(finding))
    }
  }
  actions
}

///|
fn action_from_finding(finding : PolicyFinding) -> ActionItem {
  {
    id: finding.id,
    module_name: finding_module_name(finding.id),
    title: finding.title,
    priority: action_priority(finding),
    detail: finding.detail,
    next_step: finding.repair,
  }
}

///|
fn finding_module_name(id : String) -> String {
  let parts = id.split(".").to_array()
  if parts.length() == 0 {
    "general"
  } else {
    parts[0].to_owned()
  }
}

///|
fn action_priority(finding : PolicyFinding) -> Int {
  if finding.severity == Fail {
    100 + finding.penalty
  } else if finding.severity == Warn {
    40 + finding.penalty
  } else {
    0
  }
}

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

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

///|
pub fn MailSecuritySuiteReport::action_count(
  self : MailSecuritySuiteReport,
) -> Int {
  self.actions.length()
}

///|
pub fn MailSecuritySuiteReport::coverage_label(
  self : MailSecuritySuiteReport,
) -> String {
  let mut configured = 0
  if self.spf.valid_version {
    configured += 1
  }
  if self.dmarc.valid_version {
    configured += 1
  }
  if self.dkim.is_configured() {
    configured += 1
  }
  if self.mtasts.is_configured() {
    configured += 1
  }
  if self.tlsrpt.is_configured() {
    configured += 1
  }
  if self.bimi.is_configured() {
    configured += 1
  }
  if configured >= 6 {
    "complete"
  } else if configured >= 4 {
    "broad"
  } else if configured >= 2 {
    "basic"
  } else {
    "thin"
  }
}

///|
pub fn MailSecuritySuiteReport::to_markdown(
  self : MailSecuritySuiteReport,
) -> String {
  let out = StringBuilder()
  out.write_string("# MailShield Suite Report\n\n")
  out.write_string("- Domain: " + self.domain + "\n")
  out.write_string("- Verdict: " + self.verdict + "\n")
  out.write_string("- Score: " + self.score.to_string() + "/100\n")
  out.write_string("- Coverage: " + self.coverage_label() + "\n")
  out.write_string("- Summary: " + self.summary.to_short_text() + "\n")
  out.write_string(
    "- Action items: " + self.actions.length().to_string() + "\n\n",
  )
  out.write_string("## Records\n\n")
  out.write_string("- SPF: ")
  out.write_string(if self.spf.valid_version { "valid" } else { "invalid" })
  out.write_string(", lookups=")
  out.write_string(self.spf.dns_lookup_count.to_string())
  out.write_string(", all=")
  out.write_string(self.spf.all_qualifier)
  out.write_string("\n")
  out.write_string("- DMARC: ")
  out.write_string(self.dmarc.policy)
  out.write_string(", pct=")
  out.write_string(self.dmarc.pct.to_string())
  out.write_string(", rua=")
  out.write_string(self.dmarc.rua_count.to_string())
  out.write_string("\n")
  out.write_string("- DKIM: ")
  out.write_string(self.dkim.key_type)
  out.write_string(", strength=")
  out.write_string(self.dkim.key_strength_label())
  out.write_string(", testing=")
  out.write_string(bool_text(self.dkim.testing_mode))
  out.write_string("\n")
  out.write_string("- MTA-STS: ")
  out.write_string(self.mtasts.mode)
  out.write_string(", max_age=")
  out.write_string(self.mtasts.max_age.to_string())
  out.write_string(", mx=")
  out.write_string(self.mtasts.mx_count.to_string())
  out.write_string("\n")
  out.write_string("- TLS-RPT: rua=")
  out.write_string(self.tlsrpt.rua_count.to_string())
  out.write_string(", mailto=")
  out.write_string(self.tlsrpt.mailto_count.to_string())
  out.write_string(", https=")
  out.write_string(self.tlsrpt.https_count.to_string())
  out.write_string("\n")
  out.write_string("- BIMI: logo=")
  out.write_string(
    if self.bimi.logo_url.is_empty() {
      "missing"
    } else {
      "present"
    },
  )
  out.write_string(", authority=")
  out.write_string(
    if self.bimi.has_authority() {
      "present"
    } else {
      "missing"
    },
  )
  out.write_string("\n\n")
  out.write_string("## Action Items\n\n")
  if self.actions.length() == 0 {
    out.write_string("- No action items.\n")
  } else {
    for action in self.actions {
      out.write_string(action.to_markdown())
      out.write_string("\n")
    }
  }
  out.write_string("\n## Findings\n\n")
  for finding in self.findings {
    out.write_string("- [")
    out.write_string(severity_text(finding.severity))
    out.write_string("] ")
    out.write_string(finding.id)
    out.write_string(": ")
    out.write_string(finding.title)
    if !finding.evidence.is_empty() {
      out.write_string(" -- ")
      out.write_string(finding.evidence)
    }
    if finding.severity != Pass && !finding.repair.is_empty() {
      out.write_string(" | repair: ")
      out.write_string(finding.repair)
    }
    out.write_string("\n")
  }
  out.to_string()
}

///|
pub fn MailSecuritySuiteReport::to_json(
  self : MailSecuritySuiteReport,
) -> String {
  let out = StringBuilder()
  out.write_string("{")
  append_json_string(out, "domain", self.domain)
  out.write_string(",")
  append_json_string(out, "verdict", self.verdict)
  out.write_string(",")
  append_json_int(out, "score", self.score)
  out.write_string(",")
  append_json_string(out, "coverage", self.coverage_label())
  out.write_string(",\"summary\":{")
  append_json_int(out, "pass", self.summary.pass_count)
  out.write_string(",")
  append_json_int(out, "warn", self.summary.warn_count)
  out.write_string(",")
  append_json_int(out, "fail", self.summary.fail_count)
  out.write_string(",")
  append_json_int(out, "total", self.summary.total_count)
  out.write_string("},\"records\":{")
  out.write_string("\"spf\":{")
  append_json_bool(out, "valid", self.spf.valid_version)
  out.write_string(",")
  append_json_int(out, "lookups", self.spf.dns_lookup_count)
  out.write_string("},\"dmarc\":{")
  append_json_string(out, "policy", self.dmarc.policy)
  out.write_string(",")
  append_json_int(out, "pct", self.dmarc.pct)
  out.write_string("},\"dkim\":{")
  append_json_string(out, "key_type", self.dkim.key_type)
  out.write_string(",")
  append_json_string(out, "strength", self.dkim.key_strength_label())
  out.write_string("},\"mtasts\":{")
  append_json_string(out, "mode", self.mtasts.mode)
  out.write_string(",")
  append_json_int(out, "max_age", self.mtasts.max_age)
  out.write_string("},\"tlsrpt\":{")
  append_json_int(out, "rua", self.tlsrpt.rua_count)
  out.write_string("},\"bimi\":{")
  append_json_bool(out, "configured", self.bimi.is_configured())
  out.write_string("}},\"actions\":[")
  for i in 0.. 0 {
      out.write_string(",")
    }
    let action = self.actions[i]
    out.write_string("{")
    append_json_string(out, "id", action.id)
    out.write_string(",")
    append_json_string(out, "module", action.module_name)
    out.write_string(",")
    append_json_string(out, "title", action.title)
    out.write_string(",")
    append_json_int(out, "priority", action.priority)
    out.write_string("}")
  }
  out.write_string("]}")
  out.to_string()
}