#!/usr/bin/env bats

setup() {
  load helpers/setup.bash
}

# ==========================================================================
# load_release_config
# ==========================================================================

@test "load_release_config: returns consumer config when file exists" {
  run load_release_config
  assert_success

  local default_group
  default_group="$(jq_get "$output" '.default_group')"
  assert_equal "$default_group" "Uncategorized"
}

@test "load_release_config: falls back to default when consumer config is missing" {
  __releases_api__CONFIG_FILE="/nonexistent/releases.json"

  run --separate-stderr load_release_config
  assert_success
  assert_stderr -p "using default"

  local default_group
  default_group="$(jq_get "$output" '.default_group')"
  assert_equal "$default_group" "Support"
}

@test "load_release_config: fallback output is valid JSON with expected structure" {
  __releases_api__CONFIG_FILE="/nonexistent/releases.json"

  run --separate-stderr load_release_config
  assert_success

  local group_count skip_count
  group_count="$(jq_len "$(jq_get "$output" '.groups')")"
  skip_count="$(jq_len "$(jq_get "$output" '.skip')")"

  assert_equal "$group_count" "4"
  assert_equal "$skip_count" "1"
}

# ==========================================================================
# should_skip_pr
# ==========================================================================

@test "should_skip_pr: returns true when PR has a skip label" {
  local pr_labels='["fix", "ignore-me"]'
  local skip_labels='["ignore-me"]'

  run should_skip_pr "$pr_labels" "$skip_labels"
  assert_success
}

@test "should_skip_pr: returns false when PR has no skip labels" {
  local pr_labels='["fix", "feature"]'
  local skip_labels='["ignore-me"]'

  run should_skip_pr "$pr_labels" "$skip_labels"
  assert_failure
}

@test "should_skip_pr: returns false when PR has empty labels" {
  local pr_labels='[]'
  local skip_labels='["ignore-me"]'

  run should_skip_pr "$pr_labels" "$skip_labels"
  assert_failure
}

@test "should_skip_pr: returns false when skip_labels is empty" {
  local pr_labels='["fix", "urgent"]'
  local skip_labels='[]'

  run should_skip_pr "$pr_labels" "$skip_labels"
  assert_failure
}

# ==========================================================================
# find_matching_group
# ==========================================================================

@test "find_matching_group: matches first group by priority" {
  local pr_labels='["fix", "urgent"]'
  local groups
  groups="$(jq_get "$(load_release_config)" '.groups')"

  run find_matching_group "$pr_labels" "$groups"
  assert_success

  local header
  header="$(jq_get "$output" '.header')"
  assert_equal "$header" "Critical"
}

@test "find_matching_group: matches second group when no first-group labels" {
  local pr_labels='["fix"]'
  local groups
  groups="$(jq_get "$(load_release_config)" '.groups')"

  run find_matching_group "$pr_labels" "$groups"
  assert_success

  local header
  header="$(jq_get "$output" '.header')"
  assert_equal "$header" "Features"
}

@test "find_matching_group: returns null when no group matches" {
  local pr_labels='["unrecognized"]'
  local groups
  groups="$(jq_get "$(load_release_config)" '.groups')"

  run find_matching_group "$pr_labels" "$groups"
  assert_success
  assert_output "null"
}

# ==========================================================================
# get_inline_labels
# ==========================================================================

@test "get_inline_labels: strips matched group and skip labels" {
  local pr_labels='["urgent", "godot", "ignore-me"]'
  local matched_labels='["urgent"]'
  local skip_labels='["ignore-me"]'

  run get_inline_labels "$pr_labels" "$matched_labels" "$skip_labels"
  assert_success
  assert_output '["godot"]'
}

@test "get_inline_labels: returns empty when all labels are excluded" {
  local pr_labels='["fix"]'
  local matched_labels='["fix"]'
  local skip_labels='["ignore-me"]'

  run get_inline_labels "$pr_labels" "$matched_labels" "$skip_labels"
  assert_success
  assert_output '[]'
}

@test "get_inline_labels: preserves all labels when none are excluded" {
  local pr_labels='["godot", "parser"]'
  local matched_labels='[]'
  local skip_labels='[]'

  run get_inline_labels "$pr_labels" "$matched_labels" "$skip_labels"
  assert_success
  assert_output '["godot","parser"]'
}

# ==========================================================================
# create_empty_groups
# ==========================================================================

@test "create_empty_groups: creates empty buckets for all groups including default" {
  local config groups default_group result
  config="$(load_release_config)"
  groups="$(jq_get "$config" '.groups')"
  default_group="$(jq_get "$config" '.default_group')"

  result="$(create_empty_groups "$groups" "$default_group")"

  assert_equal "$(jq_get "$result" '.Critical')"      '[]'
  assert_equal "$(jq_get "$result" '.Features')"      '[]'
  assert_equal "$(jq_get "$result" '.Maintenance')"   '[]'
  assert_equal "$(jq_get "$result" '.Uncategorized')" '[]'
}

@test "create_empty_groups: default_group gets its own bucket when not a group header" {
  local groups='[{"header":"Alpha","labels":["a"]},{"header":"Beta","labels":["b"]}]'
  local default_group="Overflow"

  local result
  result="$(create_empty_groups "$groups" "$default_group")"

  assert_equal "$(jq_get "$result" '.Alpha')"    '[]'
  assert_equal "$(jq_get "$result" '.Beta')"     '[]'
  assert_equal "$(jq_get "$result" '.Overflow')" '[]'
  assert_equal "$(jq_len "$result")"             "3"
}

# ==========================================================================
# group_prs
# ==========================================================================

make_pr() {
  local pr_number="$1" title="$2" author="$3"
  shift 3
  local labels
  labels="$(printf '%s\n' "$@" | jq -R . | jq -sc .)"
  jq_set "{}" \
    '.pr_number' "$pr_number" \
    '.title' "$title" \
    '.author' "$author" \
    '.labels' "$labels"
}

@test "group_prs: PR with first-group label lands in Critical" {
  local prs pr
  pr="$(make_pr 1 "Hotfix" "dev" "urgent")"
  prs="$(jq_push "[]" "$pr")"

  local result entries
  result="$(group_prs "$prs")"
  entries="$(jq_get "$result" '.Critical')"

  assert_equal "$(jq_len "$entries")" "1"
  assert_equal "$(jq_get "$(jq -c '.[0]' <<< "$entries")" '.title')" "Hotfix"
}

@test "group_prs: PR with second-group label lands in Features" {
  local prs pr
  pr="$(make_pr 2 "Fix crash" "dev" "fix")"
  prs="$(jq_push "[]" "$pr")"

  local result entries
  result="$(group_prs "$prs")"
  entries="$(jq_get "$result" '.Features')"

  assert_equal "$(jq_len "$entries")" "1"
  assert_equal "$(jq_get "$(jq -c '.[0]' <<< "$entries")" '.title')" "Fix crash"
}

@test "group_prs: PR with labels in two groups lands in higher-priority group" {
  local prs pr
  pr="$(make_pr 3 "Urgent fix" "dev" "urgent" "fix")"
  prs="$(jq_push "[]" "$pr")"

  local result
  result="$(group_prs "$prs")"

  assert_equal "$(jq_len "$(jq_get "$result" '.Critical')")" "1"
  assert_equal "$(jq_len "$(jq_get "$result" '.Features')")" "0"
}

@test "group_prs: PR with skip label is excluded entirely" {
  local prs pr
  pr="$(make_pr 4 "Hidden change" "dev" "ignore-me" "fix")"
  prs="$(jq_push "[]" "$pr")"

  local result
  result="$(group_prs "$prs")"

  assert_equal "$(jq_len "$(jq_get "$result" '.Critical')")"      "0"
  assert_equal "$(jq_len "$(jq_get "$result" '.Features')")"      "0"
  assert_equal "$(jq_len "$(jq_get "$result" '.Maintenance')")"   "0"
  assert_equal "$(jq_len "$(jq_get "$result" '.Uncategorized')")" "0"
}

@test "group_prs: PR with no recognized labels lands in default group" {
  local prs pr
  pr="$(make_pr 5 "Random change" "dev" "unrecognized")"
  prs="$(jq_push "[]" "$pr")"

  local result
  result="$(group_prs "$prs")"

  assert_equal "$(jq_len "$(jq_get "$result" '.Uncategorized')")" "1"
}

@test "group_prs: extra labels are preserved as inline labels" {
  local prs pr
  pr="$(make_pr 6 "Godot fix" "dev" "fix" "godot")"
  prs="$(jq_push "[]" "$pr")"

  local result entry inline_labels
  result="$(group_prs "$prs")"
  entry="$(jq -c '.Features[0]' <<< "$result")"
  inline_labels="$(jq -c '.inline_labels' <<< "$entry")"

  assert_equal "$inline_labels" '["godot"]'
}

@test "group_prs: multiple PRs distribute across groups correctly" {
  local prs
  prs="[]"
  prs="$(jq_push "$prs" "$(make_pr 1 "Rollback"    "a" "rollback")")"
  prs="$(jq_push "$prs" "$(make_pr 2 "New feature"  "b" "feature")")"
  prs="$(jq_push "$prs" "$(make_pr 3 "Update deps"  "c" "deps")")"
  prs="$(jq_push "$prs" "$(make_pr 4 "Random"       "d" "misc")")"
  prs="$(jq_push "$prs" "$(make_pr 5 "Hidden"       "e" "ignore-me")")"

  local result
  result="$(group_prs "$prs")"

  assert_equal "$(jq_len "$(jq_get "$result" '.Critical')")"      "1"
  assert_equal "$(jq_len "$(jq_get "$result" '.Features')")"      "1"
  assert_equal "$(jq_len "$(jq_get "$result" '.Maintenance')")"   "1"
  assert_equal "$(jq_len "$(jq_get "$result" '.Uncategorized')")" "1"
}

# ==========================================================================
# format_bullet
# ==========================================================================

@test "format_bullet: formats with inline labels" {
  local entry
  entry="$(jq_set "{}" \
    '.title' "Fix crash" \
    '.pr_number' "42" \
    '.author' "dev" \
    '.inline_labels' '["godot", "parser"]'
  )"

  run format_bullet "$entry"
  assert_success
  # shellcheck disable=SC2016
  assert_output '`godot` `parser` Fix crash (#42) @dev'
}

@test "format_bullet: formats without inline labels" {
  local entry
  entry="$(jq_set "{}" \
    '.title' "Fix crash" \
    '.pr_number' "42" \
    '.author' "dev" \
    '.inline_labels' '[]'
  )"

  run format_bullet "$entry"
  assert_success
  assert_output "Fix crash (#42) @dev"
}

# ==========================================================================
# build_release_doc
# ==========================================================================

@test "build_release_doc: empty groups are omitted" {
  local grouped
  grouped="$(jq_set "{}" \
    '.Critical' '[]' \
    '.Features' '[]' \
    '.Maintenance' '[]'
  )"

  local doc
  doc="$(build_release_doc "$grouped" "2025-01-15")"
  run markdown_render "$doc"

  assert_line --index 0 "Released on 2025-01-15"
  refute_line --index 1 --regexp '##'
}

@test "build_release_doc: non-empty groups get h2 headers and bullet lists" {
  local pr grouped
  pr="$(jq_set "{}" \
    '.title' "New feature" \
    '.pr_number' "10" \
    '.author' "dev" \
    '.inline_labels' '[]'
  )"
  grouped="$(jq_set "{}" \
    '.Critical' '[]' \
    '.Features' "$(jq_push "[]" "$pr")" \
    '.Maintenance' '[]'
  )"

  local doc
  doc="$(build_release_doc "$grouped" "2025-01-15")"
  run --keep-empty-lines markdown_render "$doc"

  assert_line --index 0 "Released on 2025-01-15"
  assert_line --index 1 ""
  assert_line --index 2 "## Features"
  assert_line --index 3 "- New feature (#10) @dev"
}

@test "build_release_doc: multiple groups render in priority order" {
  local critical_pr feature_pr grouped
  critical_pr="$(jq_set "{}" \
    '.title' "Urgent fix" \
    '.pr_number' "1" \
    '.author' "alice" \
    '.inline_labels' '[]'
  )"
  feature_pr="$(jq_set "{}" \
    '.title' "New widget" \
    '.pr_number' "2" \
    '.author' "bob" \
    '.inline_labels' '[]'
  )"
  grouped="$(jq_set "{}" \
    '.Critical' "$(jq_push "[]" "$critical_pr")" \
    '.Features' "$(jq_push "[]" "$feature_pr")" \
    '.Maintenance' '[]'
  )"

  local doc
  doc="$(build_release_doc "$grouped" "2025-06-01")"
  run --keep-empty-lines markdown_render "$doc"

  assert_line --index 0 "Released on 2025-06-01"
  assert_line --index 2 "## Critical"
  assert_line --index 3 "- Urgent fix (#1) @alice"
  assert_line --index 5 "## Features"
  assert_line --index 6 "- New widget (#2) @bob"
}

@test "build_release_doc: date string appears at top" {
  local grouped
  grouped="$(jq_set "{}" \
    '.Critical' '[]' \
    '.Features' '[]' \
    '.Maintenance' '[]'
  )"

  local doc
  doc="$(build_release_doc "$grouped" "March 4, 2026")"
  run markdown_render "$doc"

  assert_line --index 0 "Released on March 4, 2026"
}

# ==========================================================================
# fetch_pull_request (guard)
# ==========================================================================

@test "fetch_pull_request: returns null for null pr_number" {
  run fetch_pull_request "null" "fake-token"
  assert_success
  assert_output "null"
}

@test "fetch_pull_request: returns null for empty pr_number" {
  run fetch_pull_request "" "fake-token"
  assert_success
  assert_output "null"
}

@test "fetch_pull_request: treats 0 as a valid pr_number" {
  # shellcheck disable=SC2317
  get_pull_request() {
    echo '{"title":"Zero PR","user":{"login":"bot"},"labels":[]}'
  }

  run fetch_pull_request "0" "fake-token"
  assert_success
  assert_equal "$(jq_get "$output" '.pr_number')" "0"
  assert_equal "$(jq_get "$output" '.title')"     "Zero PR"
}

@test "fetch_pull_request: returns normalized PR object from API response" {
  # shellcheck disable=SC2317
  get_pull_request() {
    echo '{
      "title": "Add feature",
      "user": {"login": "alice"},
      "labels": [{"name": "enhancement"}, {"name": "godot"}]
    }'
  }

  run fetch_pull_request "42" "fake-token"
  assert_success

  assert_equal "$(jq_get "$output" '.pr_number')" "42"
  assert_equal "$(jq_get "$output" '.title')"     "Add feature"
  assert_equal "$(jq_get "$output" '.author')"    "alice"
  assert_equal "$(jq -c '.labels' <<< "$output")" '["enhancement","godot"]'
}

# ==========================================================================
# fetch_pr_metadata (pipeline)
# ==========================================================================

@test "fetch_pr_metadata: enriches commits with PR metadata, skipping commits without PRs" {
  get_pull_request() {
    local args="$1"
    local pr_number
    pr_number="$(jq_get "$args" '.pr_number')"

    case "$pr_number" in
      10) echo '{"title":"Feature A","user":{"login":"alice"},"labels":[{"name":"enhancement"}]}' ;;
      20) echo '{"title":"Fix B","user":{"login":"bob"},"labels":[{"name":"bug"}]}' ;;
      *)  echo '{}' ;;
    esac
  }

  local commits
  commits='[
    {"hash":"aaa","message":"feat (#10)","pr_number":10},
    {"hash":"bbb","message":"no pr ref","pr_number":null},
    {"hash":"ccc","message":"fix (#20)","pr_number":20}
  ]'

  run fetch_pr_metadata "$commits" "fake-token"
  assert_success

  local count
  count="$(jq_len "$output")"
  assert_equal "$count" "2"

  local first second
  first="$(jq -c '.[0]' <<< "$output")"
  second="$(jq -c '.[1]' <<< "$output")"

  assert_equal "$(jq_get "$first" '.pr_number')" "10"
  assert_equal "$(jq_get "$first" '.title')"     "Feature A"
  assert_equal "$(jq_get "$first" '.author')"    "alice"

  assert_equal "$(jq_get "$second" '.pr_number')" "20"
  assert_equal "$(jq_get "$second" '.title')"     "Fix B"
  assert_equal "$(jq_get "$second" '.author')"    "bob"
}
