///|
test "matcher allows by default when no groups exist" {
  let f = empty_robots_file()
  assert_true(can_fetch(f, "bot", "/x"))
}

///|
test "matcher allows by default when no rule matches" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /private/\n"))
  assert_true(can_fetch(f, "bot", "/public/"))
}

///|
test "matcher denies disallow prefix" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /private/\n"))
  assert_false(can_fetch(f, "bot", "/private/page"))
}

///|
test "matcher accepts matching allow" {
  let f = unwrap_file(
    parse_robots("User-agent: *\nAllow: /public/\nDisallow: /\n"),
  )
  assert_true(can_fetch(f, "bot", "/public/page"))
}

///|
test "matcher uses longest allow over disallow" {
  let f = unwrap_file(
    parse_robots(
      "User-agent: *\nDisallow: /private/\nAllow: /private/public/\n",
    ),
  )
  assert_true(can_fetch(f, "bot", "/private/public/a"))
}

///|
test "matcher uses longest disallow over allow" {
  let f = unwrap_file(
    parse_robots("User-agent: *\nAllow: /private/\nDisallow: /private/secret\n"),
  )
  assert_false(can_fetch(f, "bot", "/private/secret/a"))
}

///|
test "matcher allow wins equal length tie" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /x\nAllow: /x\n"))
  assert_true(can_fetch(f, "bot", "/x"))
}

///|
test "matcher handles empty disallow as no effect" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow:\n"))
  assert_true(can_fetch(f, "bot", "/anything"))
}

///|
test "matcher handles empty allow as no effect" {
  let f = unwrap_file(
    parse_robots("User-agent: *\nAllow:\nDisallow: /private/\n"),
  )
  assert_false(can_fetch(f, "bot", "/private/page"))
}

///|
test "matcher handles wildcard star" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /tmp/*.json\n"))
  assert_false(can_fetch(f, "bot", "/tmp/a/report.json"))
  assert_true(can_fetch(f, "bot", "/tmp/a/report.txt"))
}

///|
test "matcher wildcard star can match zero chars" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /tmp/*.json\n"))
  assert_false(can_fetch(f, "bot", "/tmp/.json"))
}

///|
test "matcher dollar anchors to end" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /download$\n"))
  assert_false(can_fetch(f, "bot", "/download"))
  assert_true(can_fetch(f, "bot", "/download/file"))
}

///|
test "matcher path is case sensitive" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /Case\n"))
  assert_false(can_fetch(f, "bot", "/Case"))
  assert_true(can_fetch(f, "bot", "/case"))
}

///|
test "matcher user-agent is case insensitive" {
  let f = unwrap_file(parse_robots("User-agent: ExampleBot\nDisallow: /\n"))
  assert_false(can_fetch(f, "examplebot", "/x"))
}

///|
test "matcher does not treat product token substring as exact match" {
  let f = unwrap_file(
    parse_robots(
      "User-agent: *\nAllow: /\n\nUser-agent: Example\nDisallow: /\n",
    ),
  )
  assert_true(can_fetch(f, "ExampleBot", "/x"))
}

///|
test "matcher product token API does not accept identification string" {
  let f = unwrap_file(
    parse_robots(
      "User-agent: *\nAllow: /\n\nUser-agent: ExampleBot\nDisallow: /\n",
    ),
  )
  assert_true(can_fetch(f, "ExampleBot/1.0", "/x"))
}

///|
test "matcher specific group beats wildcard" {
  let f = unwrap_file(
    parse_robots(
      "User-agent: *\nDisallow: /\n\nUser-agent: ExampleBot\nAllow: /\n",
    ),
  )
  assert_true(can_fetch(f, "ExampleBot", "/x"))
  assert_false(can_fetch(f, "OtherBot", "/x"))
}

///|
test "matcher merges equally specific groups" {
  let f = unwrap_file(
    parse_robots(
      "User-agent: bot\nDisallow: /a\n\nUser-agent: bot\nDisallow: /b\n",
    ),
  )
  assert_false(can_fetch(f, "bot", "/a"))
  assert_false(can_fetch(f, "bot", "/b"))
}

///|
test "matcher rules_for returns effective merged rules" {
  let f = unwrap_file(
    parse_robots(
      "User-agent: bot\nDisallow: /a\n\nUser-agent: bot\nAllow: /b\n",
    ),
  )
  assert_int_eq(rules_for(f, "bot").length(), 2)
}

///|
test "matcher falls back to wildcard" {
  let f = unwrap_file(
    parse_robots("User-agent: *\nDisallow: /all\nUser-agent: other\nAllow: /\n"),
  )
  assert_false(can_fetch(f, "bot", "/all"))
}

///|
test "matcher no wildcard means unrestricted" {
  let f = unwrap_file(parse_robots("User-agent: other\nDisallow: /\n"))
  assert_true(can_fetch(f, "bot", "/all"))
}

///|
test "matcher always allows robots txt path" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /\n"))
  assert_true(can_fetch(f, "bot", "/robots.txt"))
}

///|
test "matcher normalizes missing leading slash" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /admin\n"))
  assert_false(can_fetch(f, "bot", "admin"))
}

///|
test "matcher returns matched rule details" {
  let f = unwrap_file(parse_robots("User-agent: *\nDisallow: /admin\n"))
  let d = unwrap_decision(evaluate(f, "bot", "/admin/x"))
  assert_false(d.allowed())
  assert_int_eq(d.matched_length(), 6)
  match d.matched_rule() {
    Some(r) => assert_str_eq(r.pattern(), "/admin")
    None => fail("missing matched rule")
  }
}