// mbauth 命令行入口。
//
// 判定逻辑全部在核心包;这里只负责参数、文件读写、渲染与退出码。

///|
const VERSION : String = "0.1.0"

///|
fn usage() -> String {
  let s = StringBuilder()
  s.write_string(
    "mbauth \{VERSION} — 邮件身份验证(DKIM / SPF / DMARC)\n\n",
  )
  s.write_string("用法:\n")
  s.write_string("  mbauth  --ip  [选项]\n\n")
  s.write_string("选项:\n")
  s.write_string("  --ip            SMTP 对端 IP,SPF 必需\n")
  s.write_string(
    "  --zone          离线 DNS zone(每行 `name TYPE value`)\n",
  )
  s.write_string("  --mail-from   MAIL FROM 域,默认取 From 头\n")
  s.write_string("  --helo          HELO 名,供 SPF 宏使用\n")
  s.write_string("  --format         text | json | header,默认 text\n")
  s.write_string("  --now           校验签名 x= 过期时间\n")
  s.write_string("  --sample <0-99>       DMARC pct 采样值\n")
  s.write_string("  -v, --version         显示版本\n")
  s.write_string("  -h, --help            显示本帮助\n\n")
  s.write_string(
    "退出码:0 通过,1 未通过,2 用法或读写错误\n",
  )
  s.to_string()
}

///|
fn parse_int(text : String) -> Int? {
  if text.length() == 0 {
    return None
  }
  let mut value = 0
  for c in text {
    if c < '0' || c > '9' {
      return None
    }
    value = value * 10 + (c.to_int() - '0'.to_int())
  }
  Some(value)
}

///|
struct Options {
  file : String
  ip : String
  zone : String
  mail_from : String?
  helo : String?
  format : String
  now : Int?
  sample : Int?
}

///|
fn parse_args(args : Array[String]) -> Options? {
  let mut file = ""
  let mut ip = ""
  let mut zone = ""
  let mut mail_from : String? = None
  let mut helo : String? = None
  let mut format = "text"
  let mut now : Int? = None
  let mut sample : Int? = None
  let mut i = 1
  while i < args.length() {
    let arg = args[i]
    if arg == "--ip" && i + 1 < args.length() {
      ip = args[i + 1]
      i = i + 2
    } else if arg == "--zone" && i + 1 < args.length() {
      zone = args[i + 1]
      i = i + 2
    } else if arg == "--mail-from" && i + 1 < args.length() {
      mail_from = Some(args[i + 1])
      i = i + 2
    } else if arg == "--helo" && i + 1 < args.length() {
      helo = Some(args[i + 1])
      i = i + 2
    } else if arg == "--format" && i + 1 < args.length() {
      format = args[i + 1]
      i = i + 2
    } else if arg == "--now" && i + 1 < args.length() {
      now = parse_int(args[i + 1])
      i = i + 2
    } else if arg == "--sample" && i + 1 < args.length() {
      sample = parse_int(args[i + 1])
      i = i + 2
    } else if arg == "-h" ||
      arg == "--help" ||
      arg == "-v" ||
      arg == "--version" {
      i = i + 1
    } else if arg.has_prefix("--") {
      return None
    } else {
      if file.length() == 0 {
        file = arg
      }
      i = i + 1
    }
  }
  if file.length() == 0 || ip.length() == 0 {
    return None
  }
  Some({ file, ip, zone, mail_from, helo, format, now, sample, })
}

///|
fn read_bytes(path : String) -> Result[Bytes, String] {
  Ok(@fs.read_file_to_bytes(path)) catch {
    _ => Err("读取文件失败")
  }
}

///|
fn read_text(path : String) -> Result[String, String] {
  Ok(@fs.read_file_to_string(path)) catch {
    _ => Err("读取文件失败")
  }
}

///|
fn main {
  let args = @env.args()
  if args.contains("-h") || args.contains("--help") {
    println(usage())
    @sys.exit(0)
  }
  if args.contains("-v") || args.contains("--version") {
    println("mbauth \{VERSION}")
    @sys.exit(0)
  }
  let options = match parse_args(args) {
    Some(value) => value
    None => {
      println(usage())
      @sys.exit(2)
      return
    }
  }
  let raw = match read_bytes(options.file) {
    Ok(value) => value
    Err(message) => {
      println("\{message}: \{options.file}")
      @sys.exit(2)
      return
    }
  }
  let zone_text = if options.zone.length() == 0 {
    ""
  } else {
    match read_text(options.zone) {
      Ok(value) => value
      Err(message) => {
        println("\{message}: \{options.zone}")
        @sys.exit(2)
        return
      }
    }
  }
  let zone = @Mr-Houjie/mbt-mailauth.Zone::from_text(zone_text)
  let report = @Mr-Houjie/mbt-mailauth.verify_message(
    zone,
    raw,
    options.ip,
    options.mail_from.unwrap_or(""),
    options.helo.unwrap_or(""),
    options.now.unwrap_or(0),
    options.sample.unwrap_or(-1),
  )
  match options.format {
    "json" => println(@Mr-Houjie/mbt-mailauth.render_json(report))
    "header" =>
      println(
        @Mr-Houjie/mbt-mailauth.authentication_results(report, "mbauth.local"),
      )
    _ => println(@Mr-Houjie/mbt-mailauth.render_text(report))
  }
  let failed = match report.dmarc {
    Some(result) => result.status.label() == "fail"
    None => false
  }
  @sys.exit(if failed { 1 } else { 0 })
}