// 移植自 github.com/milvus-io/milvus client/internal/merr (Apache-2.0)
// Copyright (c) LF AI & Data Foundation

///|
/// 错误信息脱敏。
///
/// 验收要求「错误信息中不泄露 token 等敏感 metadata」。Milvus 的 `Status.detail`
/// 与 `reason` 由服务端拼装,可能回显请求侧的认证头或连接串。上游 Go 版没有这层,
/// 这里补上,且是**纯函数**,不影响任何判定语义。
///
/// 规则:
/// - `Authorization: Bearer xxx` / 裸 `Bearer xxx` → `Bearer ***`
/// - `token=xxx` / `password=xxx` / `api_key=xxx` 等 `key<分隔符>value` → 值打码
/// - URL userinfo(`scheme://user:pass@host`)里的口令段 → `***`
///
/// 非敏感内容(主机名、端口、库名、字段名)原样保留,保证可调试。
pub fn redact(input : String) -> String {
  // 顺序有讲究:先处理 `Authorization:` 这个键,再处理它后面的 `Bearer `。
  // 反过来会把 `Bearer` 当成 `authorization` 的值打码掉。
  let out = redact_scheme_credentials(input)
  let out = redact_key_value_secrets(out)
  redact_authorization(out)
}

///|
/// 需要打码的关键词,大小写不敏感。
let secret_keys : Array[String] = [
  "authorization", "token", "password", "passwd", "secret", "api_key", "apikey",
  "access_key", "private_key",
]

///|
fn redact_key_value_secrets(input : String) -> String {
  let mut out = input
  for key in secret_keys {
    out = redact_all_occurrences(out, key)
  }
  out
}

///|
/// 找 `key` 的所有出现(大小写不敏感),把紧随其后的值替换成 `***`。
fn redact_all_occurrences(input : String, key : String) -> String {
  let lower = input.to_lower()
  let key_lower = key.to_lower()
  let key_len = key_lower.length()
  let out = StringBuilder()
  let mut cursor = 0
  let input_len = input.length()
  while cursor <= input_len {
    let found = find_from(lower, key_lower, cursor)
    if found == -1 {
      out.write_string(slice(input, cursor, input_len))
      break
    }
    out.write_string(slice(input, cursor, found + key_len))
    cursor = found + key_len
    // 跳过 key 与值之间的分隔与空白:`token=abc`、`token: abc`、`token "abc"`
    let mut i = cursor
    while i < input_len && is_separator_or_space(input.at(i)) {
      i = i + 1
    }
    if i > cursor {
      out.write_string(slice(input, cursor, i))
      cursor = i
    }
    // 打码到值的结束(空白、`,`、`;`、`&`、`}`、引号、行尾)
    let mut j = cursor
    while j < input_len && !is_value_terminator(input.at(j)) {
      j = j + 1
    }
    // `Authorization: Bearer xxx` 交给 redact_authorization 处理,
    // 否则这里会把 `Bearer` 本身当成值打码,丢掉 scheme 名。
    if j > cursor && !slice(input, cursor, j).to_lower().has_prefix("bearer") {
      out.write_string("***")
      cursor = j
    }
  }
  out.to_string()
}

///|
/// 取 `[start, end)` 子串,规避已废弃的 `String::substring`。
fn slice(s : String, start : Int, end : Int) -> String {
  s.view(start_offset=start, end_offset=end).to_owned()
}

///|
/// 从 `from` 起找 `needle`(要求 `haystack` 与 `needle` 同为小写),返回下标或 `-1`。
fn find_from(haystack : String, needle : String, from : Int) -> Int {
  let h_len = haystack.length()
  let n_len = needle.length()
  if n_len == 0 || from >= h_len {
    return -1
  }
  let mut i = from
  while i + n_len <= h_len {
    if slice(haystack, i, i + n_len) == needle {
      return i
    }
    i = i + 1
  }
  -1
}

///|
/// 该 UTF-16 码元是否属于「key 与值之间的分隔」。
fn is_separator_or_space(c : UInt16) -> Bool {
  c == 0x3D || c == 0x3A || c == 0x20 || c == 0x09 || c == 0x22 || c == 0x27
}

///|
/// 该 UTF-16 码元是否终止一个值。
fn is_value_terminator(c : UInt16) -> Bool {
  c == 0x20 ||
  c == 0x09 ||
  c == 0x0A ||
  c == 0x0D ||
  c == 0x2C ||
  c == 0x3B ||
  c == 0x26 ||
  c == 0x7D ||
  c == 0x29 ||
  c == 0x22 ||
  c == 0x27
}

///|
/// 打码 `Bearer xxx`(含 `Authorization: Bearer xxx`)。
fn redact_authorization(input : String) -> String {
  let lower = input.to_lower()
  let out = StringBuilder()
  let mut cursor = 0
  let input_len = input.length()
  while cursor <= input_len {
    let found = find_from(lower, "bearer", cursor)
    if found == -1 {
      out.write_string(slice(input, cursor, input_len))
      break
    }
    out.write_string(slice(input, cursor, found + 6))
    cursor = found + 6
    let mut i = cursor
    while i < input_len && (input.at(i) == 0x20 || input.at(i) == 0x09) {
      i = i + 1
    }
    out.write_string(slice(input, cursor, i))
    cursor = i
    let mut j = cursor
    while j < input_len && !is_value_terminator(input.at(j)) {
      j = j + 1
    }
    if j > cursor {
      out.write_string("***")
      cursor = j
    }
  }
  out.to_string()
}

///|
/// 打码 URL userinfo 中的口令:`scheme://user:pass@host` → `scheme://user:***@host`。
fn redact_scheme_credentials(input : String) -> String {
  let lower = input.to_lower()
  let out = StringBuilder()
  let mut cursor = 0
  let input_len = input.length()
  while cursor <= input_len {
    let found = find_from(lower, "://", cursor)
    if found == -1 {
      out.write_string(slice(input, cursor, input_len))
      break
    }
    let user_start = found + 3
    // 在 authority 段内找 userinfo 与 host 的分界 '@'
    let mut at = -1
    let mut i = user_start
    while i < input_len && !is_authority_end(input.at(i)) {
      if input.at(i) == 0x40 {
        at = i
        break
      }
      i = i + 1
    }
    if at == -1 {
      out.write_string(slice(input, cursor, user_start))
      cursor = user_start
      continue
    }
    let userinfo = slice(input, user_start, at)
    out.write_string(slice(input, cursor, user_start))
    match find_from(userinfo, ":", 0) {
      -1 =>
        // 只有用户名、没有口令,原样保留
        out.write_string(userinfo)
      colon => {
        out.write_string(slice(userinfo, 0, colon + 1))
        out.write_string("***")
      }
    }
    out.write_string("@")
    cursor = at + 1
  }
  out.to_string()
}

///|
fn is_authority_end(c : UInt16) -> Bool {
  c == 0x2F || c == 0x20 || c == 0x09 || c == 0x0A || c == 0x0D || c == 0x22
}