// main.mbt — httpsig-tool command-line entry point.
//
// Subcommands: parse-input, parse-signature, parse-accept, build-base,
// sign-hmac, verify-hmac, inspect, check-policy, rfc-example, plus --help and
// --version. Every command emits stable JSON and never unwraps external input.

///|
/// Builds a request from CLI flags for build-base / sign-hmac / verify-hmac.
fn request_from_flags(
  m : Map[String, String],
) -> Result[@hsig.RequestContext, @hsig.HsError] {
  let method = get_flag(m, "method", "GET")
  let scheme = get_flag(m, "scheme", "https")
  let authority = get_flag(m, "authority", "example.com")
  let path = get_flag(m, "path", "/")
  let query : String? = match get_flag_opt(m, "query") {
    Some(q) => Some(q)
    None => None
  }
  let headers = @hsig.OrderedHeaders::new()
  let first_header = match m.get("header") {
    Some(line) => line
    None => ""
  }
  if !first_header.is_empty() {
    append_header_line(headers, first_header)
  }
  let mut i = 1
  while true {
    let key = "header" + i.to_string()
    match m.get(key) {
      Some(line) => {
        append_header_line(headers, line)
        i = i + 1
      }
      None => break
    }
  }
  @hsig.RequestContext::new_default(
    method,
    scheme,
    authority,
    path,
    query,
    headers,
    None,
  )
}

///|
/// Appends a `name=value` header line to the header set.
fn append_header_line(headers : @hsig.OrderedHeaders, line : String) -> Unit {
  match line.split_once("=") {
    Some((name, value)) => {
      let _ = headers.append(name.to_owned(), value.to_owned())
    }
    None => {
      let _ = headers.append(line, "")
    }
  }
}

///|
/// Builds the signature parameters for sign-hmac.
fn params_from_flags(m : Map[String, String]) -> @hsig.SignatureParameters {
  let created = get_flag_i64(m, "created")
  let expires = get_flag_i64(m, "expires")
  let keyid = get_flag_opt(m, "keyid")
  let nonce = get_flag_opt(m, "nonce")
  let tag = get_flag_opt(m, "tag")
  {
    created,
    expires,
    keyid,
    alg: Some("hmac-sha256"),
    nonce,
    tag,
    extensions: Array::new(),
  }
}

///|
/// Reads a hex secret from a flag and returns the key record.
fn key_from_flags(
  m : Map[String, String],
  keyid : String,
) -> Result[@hsig.KeyRecord, @hsig.HsError] {
  let secret_hex = get_flag(m, "secret-hex", "")
  match @hsig.hex_decode_bytes(secret_hex) {
    Ok(bytes) =>
      Ok({ keyid, algorithm: "hmac-sha256", material: SharedSecret(bytes) })
    Err(e) => Err(e)
  }
}

///|
/// Maps a component name to a CoveredComponent.
fn component_from_string(name : String) -> @hsig.CoveredComponent {
  match name {
    "@method" => @hsig.covered_derived(@hsig.DerivedComponent::Method)
    "@target-uri" => @hsig.covered_derived(@hsig.DerivedComponent::TargetUri)
    "@authority" => @hsig.covered_derived(@hsig.DerivedComponent::Authority)
    "@scheme" => @hsig.covered_derived(@hsig.DerivedComponent::Scheme)
    "@path" => @hsig.covered_derived(@hsig.DerivedComponent::Path)
    "@query" => @hsig.covered_derived(@hsig.DerivedComponent::Query)
    "@request-target" =>
      @hsig.covered_derived(@hsig.DerivedComponent::RequestTarget)
    _ => @hsig.covered_field(name)
  }
}

///|
/// Parses the `--component` flags into covered components.
fn parse_components_flag(
  m : Map[String, String],
) -> Array[@hsig.CoveredComponent] {
  let components : Array[@hsig.CoveredComponent] = Array::new()
  let first = match m.get("component") {
    Some(name) => name
    None => ""
  }
  if !first.is_empty() {
    components.push(component_from_string(first))
  }
  let mut i = 1
  while true {
    match m.get("component" + i.to_string()) {
      Some(name) => {
        components.push(component_from_string(name))
        i = i + 1
      }
      None => break
    }
  }
  if components.is_empty() {
    components.push(@hsig.covered_derived(@hsig.DerivedComponent::Method))
  }
  components
}

///|
/// Command: parse Signature-Input.
fn cmd_parse_input(m : Map[String, String]) -> String {
  let input = get_flag(m, "signature-input", "")
  match @hsig.parse_signature_input(input, @hsig.Limits::default()) {
    Ok(parsed) => {
      let labels : Array[Json] = Array::new()
      for entry in parsed.entries {
        labels.push(Json::string(entry.label))
      }
      ok_json([("labels", Json::array(labels))])
    }
    Err(e) => error_json(e)
  }
}

///|
/// Command: parse Signature.
fn cmd_parse_signature(m : Map[String, String]) -> String {
  let input = get_flag(m, "signature", "")
  match @hsig.parse_signature_field(input, @hsig.Limits::default()) {
    Ok(parsed) => {
      let labels : Array[Json] = Array::new()
      for entry in parsed.entries {
        labels.push(Json::string(entry.label))
      }
      ok_json([("labels", Json::array(labels))])
    }
    Err(e) => error_json(e)
  }
}

///|
/// Command: parse Accept-Signature.
fn cmd_parse_accept(m : Map[String, String]) -> String {
  let input = get_flag(m, "accept-signature", "")
  match @hsig.parse_accept_signature(input, @hsig.Limits::default()) {
    Ok(parsed) => {
      let labels : Array[Json] = Array::new()
      for entry in parsed.entries {
        labels.push(Json::string(entry.label))
      }
      ok_json([("labels", Json::array(labels))])
    }
    Err(e) => error_json(e)
  }
}

///|
/// Command: build a signature base.
fn cmd_build_base(m : Map[String, String]) -> String {
  match request_from_flags(m) {
    Err(e) => error_json(e)
    Ok(req) => {
      let input_str = get_flag(m, "signature-input", "")
      match @hsig.parse_signature_input(input_str, @hsig.Limits::default()) {
        Err(e) => error_json(e)
        Ok(parsed) =>
          if parsed.entries.is_empty() {
            error_json(
              @hsig.hs_error(
                @hsig.HsErrorStage::SignatureInputParsing,
                @hsig.HsErrorKind::MissingSignatureInput,
                "no signature entry",
              ),
            )
          } else {
            let entry = parsed.entries[0]
            match
              @hsig.build_request_signature_base(
                req,
                entry,
                @hsig.Limits::default(),
              ) {
              Err(e) => error_json(e)
              Ok(base) =>
                ok_json([
                  ("label", Json::string(entry.label)),
                  ("base", Json::string(base.text)),
                  (
                    "base_sha256",
                    Json::string(
                      @hsig.hex_encode_bytes(@hsig.sha256_raw(base.bytes)),
                    ),
                  ),
                ])
            }
          }
      }
    }
  }
}

///|
/// Command: sign an HMAC-SHA256 request.
fn cmd_sign_hmac(m : Map[String, String]) -> String {
  let label = get_flag(m, "label", "sig1")
  let keyid = get_flag(m, "keyid", "test-key")
  match key_from_flags(m, keyid) {
    Err(e) => error_json(e)
    Ok(key) =>
      match request_from_flags(m) {
        Err(e) => error_json(e)
        Ok(req) => {
          let components = parse_components_flag(m)
          let params = params_from_flags(m)
          let options : @hsig.SignOptions = {
            label,
            components,
            parameters: params,
            algorithm: "hmac-sha256",
            key,
          }
          match @hsig.sign_request(req, options, @hsig.Limits::default()) {
            Err(e) => error_json(e)
            Ok(signed) =>
              ok_json([
                ("label", Json::string(label)),
                ("algorithm", Json::string("hmac-sha256")),
                ("keyid", Json::string(keyid)),
                ("signature_input", Json::string(signed.signature_input)),
                ("signature", Json::string(signed.signature)),
              ])
          }
        }
      }
  }
}

///|
/// Command: verify an HMAC-SHA256 request.
fn cmd_verify_hmac(m : Map[String, String]) -> String {
  let keyid = get_flag(m, "keyid", "test-key")
  let now = get_flag_i64(m, "now").unwrap_or(0L)
  match key_from_flags(m, keyid) {
    Err(e) => error_json(e)
    Ok(key) =>
      match request_from_flags(m) {
        Err(e) => error_json(e)
        Ok(req) => {
          let input_str = get_flag(m, "signature-input", "")
          let sig_str = get_flag(m, "signature", "")
          let resolver = @hsig.InMemoryKeyResolver::new()
          let _ = resolver.add(key, @hsig.Limits::default())
          let clock = @hsig.FixedClock::new(now)
          let nonces = @hsig.InMemoryNonceStore::new()
          match
            @hsig.verify_request(
              req,
              input_str,
              sig_str,
              resolver,
              @hsig.VerificationPolicy::hmac_only(),
              clock,
              nonces,
              @hsig.Limits::default(),
              @hsig.MultiSignaturePolicy::AnyValid,
            ) {
            Err(e) => error_json(e)
            Ok(report) => {
              let labels : Array[Json] = Array::new()
              for v in report.verified {
                labels.push(Json::string(v.label))
              }
              ok_json([
                ("verified", Json::array(labels)),
                (
                  "rejected_count",
                  Json::number(
                    report.rejected.length().to_double(),
                    repr=report.rejected.length().to_string(),
                  ),
                ),
              ])
            }
          }
        }
      }
  }
}

///|
/// Command: inspect (parse only, no verification).
fn cmd_inspect(m : Map[String, String]) -> String {
  let input_str = get_flag(m, "signature-input", "")
  match @hsig.parse_signature_input(input_str, @hsig.Limits::default()) {
    Err(e) => error_json(e)
    Ok(parsed) => {
      let entries : Array[Json] = Array::new()
      for entry in parsed.entries {
        entries.push(
          json_object([
            ("label", Json::string(entry.label)),
            ("covered_components", components_json(entry.covered_components)),
          ]),
        )
      }
      ok_json([("signatures", Json::array(entries))])
    }
  }
}

///|
/// Command: check-policy (parse + policy checks without key resolution).
fn cmd_check_policy(m : Map[String, String]) -> String {
  let input_str = get_flag(m, "signature-input", "")
  match @hsig.parse_signature_input(input_str, @hsig.Limits::default()) {
    Err(e) => error_json(e)
    Ok(parsed) => {
      let mut valid = true
      for entry in parsed.entries {
        let p = entry.parameters
        if p.created is Some(c) && p.expires is Some(e) {
          if e < c {
            valid = false
          }
        }
        if p.keyid is None {
          valid = false
        }
        match get_flag_opt(m, "required-component") {
          Some(rc) => {
            let wanted = component_from_string(rc)
            if !@hsig.components_cover(entry.covered_components, wanted) {
              valid = false
            }
          }
          None => ()
        }
      }
      ok_json([("valid", Json::boolean(valid))])
    }
  }
}

///|
/// Command: run the RFC 9421 B.2.5 HMAC example.
fn cmd_rfc_example() -> String {
  match
    @hsig.base64_decode_bytes(
      "uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ==",
    ) {
    Err(e) => error_json(e)
    Ok(secret_bytes) => {
      let base = "\"date\": Tue, 20 Apr 2021 02:07:55 GMT\n\"@authority\": example.com\n\"content-type\": application/json\n\"@signature-params\": (\"date\" \"@authority\" \"content-type\");created=1618884473;keyid=\"test-shared-secret\""
      let mac = @hsig.hmac_sha256(secret_bytes, @utf8.encode(base))
      let expected = "pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8="
      let actual = @hsig.base64_encode_bytes(mac)
      ok_json([
        ("example", Json::string("rfc-9421-b.2.5")),
        ("algorithm", Json::string("hmac-sha256")),
        ("mac", Json::string(actual)),
        ("expected", Json::string(expected)),
        ("match", Json::boolean(actual == expected)),
      ])
    }
  }
}

///|
/// Prints usage text.
fn cmd_help() -> String {
  ok_json([
    (
      "help",
      Json::string("httpsig-tool — RFC 9421 HTTP Message Signatures CLI"),
    ),
    (
      "commands",
      Json::string(
        "parse-input, parse-signature, parse-accept, build-base, sign-hmac, verify-hmac, inspect, check-policy, rfc-example",
      ),
    ),
  ])
}

///|
/// The command dispatch table.
fn dispatch(args : Array[String]) -> String {
  if args.is_empty() {
    return cmd_help()
  }
  let first = args[0]
  match first {
    "--help" | "help" => cmd_help()
    "--version" | "version" =>
      ok_json([
        ("version", Json::string(@hsig.library_version())),
        ("name", Json::string("httpsig-tool")),
      ])
    "parse-input" => cmd_parse_input(parse_flags(arg_tail(args)))
    "parse-signature" => cmd_parse_signature(parse_flags(arg_tail(args)))
    "parse-accept" => cmd_parse_accept(parse_flags(arg_tail(args)))
    "build-base" => cmd_build_base(parse_flags(arg_tail(args)))
    "sign-hmac" => cmd_sign_hmac(parse_flags(arg_tail(args)))
    "verify-hmac" => cmd_verify_hmac(parse_flags(arg_tail(args)))
    "inspect" => cmd_inspect(parse_flags(arg_tail(args)))
    "check-policy" => cmd_check_policy(parse_flags(arg_tail(args)))
    "rfc-example" => cmd_rfc_example()
    _ =>
      error_json(
        @hsig.hs_error(
          @hsig.HsErrorStage::StructuredFieldParsing,
          @hsig.HsErrorKind::InvalidStructuredField,
          "unknown command: " + first,
        ),
      )
  }
}

///|
fn main {
  let args = @env.args()
  println(dispatch(arg_tail(args)))
}