// Serializer and generator tests: deterministic rendering, round-trips and the validated SecurityTxtBuilder.

///|
test "serializer round-trips a parsed document and always ends with a newline" {
  let original = unwrap_parse(valid_document())
  let text = serialize_security_txt(original)
  let again = unwrap_parse(text)
  assert_int_eq(again.field_count(), original.field_count())
  assert_int_eq(again.comments().length(), original.comments().length())
  let mut i = 0
  while i < original.fields().length() {
    assert_str_eq(again.fields()[i].name(), original.fields()[i].name())
    assert_str_eq(again.fields()[i].value(), original.fields()[i].value())
    i += 1
  }
  assert_true(text.has_suffix("\n"))
}

///|
test "serializer emits comments first and normalizes field names" {
  let commented = unwrap_parse(
    "# one\n# two\nContact: mailto:a@example.com\nExpires: 2027-01-01T00:00:00Z\n",
  )
  assert_str_eq(
    serialize_security_txt(commented),
    "# one\n# two\nContact: mailto:a@example.com\nExpires: 2027-01-01T00:00:00Z\n",
  )
  let mixed_case = unwrap_parse(
    "cOnTaCt: mailto:a@example.com\nexpires: 2027-01-01T00:00:00Z\n",
  )
  assert_str_eq(
    serialize_security_txt(mixed_case),
    "Contact: mailto:a@example.com\nExpires: 2027-01-01T00:00:00Z\n",
  )
}

///|
test "serializer keeps extension fields" {
  let document = unwrap_parse(
    "X-One: 1\nContact: mailto:a@example.com\nExpires: 2027-01-01T00:00:00Z\n",
  )
  let text = serialize_security_txt(document)
  assert_true(text.has_prefix("X-One: 1\n"))
}

///|
test "builder produces a valid minimal document" {
  let expires = unwrap_datetime(make_utc(2027, 1, 1, 0, 0, 0))
  match new_builder().contact("mailto:a@example.com").expires(expires).build() {
    Ok(document) => {
      assert_int_eq(document.field_count(), 2)
      assert_true(validate(document) is Ok(_))
      assert_str_eq(document.fields()[0].name(), "Contact")
      assert_str_eq(document.fields()[1].name(), "Expires")
    }
    Err(err) => fail("build failed: \{err.to_string()}")
  }
}

///|
test "builder assembles fields in the documented fixed order" {
  let expires = unwrap_datetime(make_utc(2027, 1, 1, 0, 0, 0))
  match
    new_builder()
    .hiring("https://example.com/jobs")
    .canonical("https://example.com/.well-known/security.txt")
    .contact("mailto:b@example.com")
    .policy("https://example.com/policy")
    .contact("mailto:a@example.com")
    .extension("X-Last", "v")
    .encryption("https://example.com/key.txt")
    .acknowledgments("https://example.com/thanks")
    .preferred_languages(["en", "de"])
    .expires(expires)
    .build() {
    Ok(document) => {
      let names : Array[String] = []
      for field in document.fields() {
        names.push(field.name())
      }
      assert_str_eq(
        names.join(","),
        "Contact,Contact,Expires,Canonical,Encryption,Acknowledgments,Policy,Hiring,Preferred-Languages,X-Last",
      )
    }
    Err(err) => fail("build failed: \{err.to_string()}")
  }
}

///|
test "builder joins preferred languages with commas" {
  let expires = unwrap_datetime(make_utc(2027, 1, 1, 0, 0, 0))
  match
    new_builder()
    .contact("mailto:a@example.com")
    .expires(expires)
    .preferred_languages(["en", "de"])
    .build() {
    Ok(document) =>
      assert_str_eq(document.preferred_languages_value().unwrap(), "en, de")
    Err(err) => fail("build failed: \{err.to_string()}")
  }
}

///|
test "builder rejects missing required fields and invalid field values" {
  let expires = unwrap_datetime(make_utc(2027, 1, 1, 0, 0, 0))
  match new_builder().expires(expires).build() {
    Ok(_) => fail("expected MissingContact")
    Err(err) => assert_err_kind(err, MissingContact)
  }
  match new_builder().contact("mailto:a@example.com").build() {
    Ok(_) => fail("expected MissingExpires")
    Err(err) => assert_err_kind(err, MissingExpires)
  }
  match new_builder().contact("not a uri").expires(expires).build() {
    Ok(_) => fail("expected InvalidUri")
    Err(err) => assert_err_kind(err, InvalidUri)
  }
}

///|
test "builder rejects extension and comment content that would break syntax" {
  let expires = unwrap_datetime(make_utc(2027, 1, 1, 0, 0, 0))
  let base = new_builder().contact("mailto:a@example.com").expires(expires)
  assert_true(base.extension("Bad Name", "value").build() is Err(_))
  assert_true(base.extension("X-Test", "").build() is Err(_))
  assert_true(base.extension("X-Test", "line\nbreak").build() is Err(_))
  assert_true(base.comment("one\ntwo").build() is Err(_))
}