#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
WORKFLOWS="$ROOT/.github/workflows"

fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }

expect_file_contains() {
  local file="$1"
  local needle="$2"
  local msg="$3"
  if ! grep -Fq -- "$needle" "$file"; then
    fail "$msg (missing: '$needle' in ${file#"$ROOT"/})"
  fi
}

line_of() {
  local file="$1"
  local needle="$2"
  local line
  line="$(grep -nFm1 -- "$needle" "$file" || true)"
  line="${line%%:*}"
  if [[ -z "$line" ]]; then
    fail "missing expected workflow line '$needle' in ${file#"$ROOT"/}"
  fi
  printf '%s\n' "$line"
}

input_section() {
  local file="$1"
  local input="$2"
  awk -v needle="      ${input}:" '
    index($0, needle) { active = 1 }
    active { print }
    active && index($0, "type: boolean") { exit }
  ' "$file"
}

workflow_section() {
  local file="$1"
  local start="$2"
  local end="$3"
  awk -v start="$start" -v end="$end" '
    index($0, start) { active = 1 }
    active { print }
    active && index($0, end) { exit }
  ' "$file"
}

expect_section_contains() {
  local section="$1"
  local needle="$2"
  local msg="$3"
  if ! grep -Fq -- "$needle" <<<"$section"; then
    fail "$msg (missing: '$needle')"
  fi
}

assert_no_github_script_injected_binding_redeclarations() {
  local workflow="$1"
  awk -v file="${workflow#"$ROOT"/}" '
    /uses: actions\/github-script@/ {
      pending_script = 1
      in_script = 0
      next
    }
    pending_script && /^[[:space:]]*script: \|/ {
      in_script = 1
      next
    }
    in_script && /^[[:space:]]{6}- name:/ {
      pending_script = 0
      in_script = 0
    }
    in_script && /^[[:space:]]*(const|let|var)[[:space:]]+(core|github|context|glob|io|exec)[[:space:]]*=/ {
      printf("[FAIL] %s redeclares actions/github-script injected binding at line %d: %s\n", file, NR, $0) > "/dev/stderr"
      exit 1
    }
  ' "$workflow"
}

test_maven_workflow_jobs_setup_jdk25_before_maven() {
  echo "Running test_maven_workflow_jobs_setup_jdk25_before_maven"

  local workflow
  for workflow in "$WORKFLOWS"/*.yml; do
    if ! grep -Eq '(^|[[:space:]"({;])xvfb-run[[:space:]]+mvn[[:space:]-]|(^|[[:space:]"({;])mvn[[:space:]-]' "$workflow"; then
      continue
    fi

    awk -v file="${workflow#"$ROOT"/}" '
      /^jobs:/ { in_jobs = 1; next }
      in_jobs && /^  [A-Za-z0-9_-]+:$/ {
        job = $1
        sub(/:$/, "", job)
        setup = 0
        temurin = 0
        jdk25 = 0
        next
      }
      /^[^[:space:]]/ && $0 !~ /^jobs:/ {
        in_jobs = 0
      }
      /uses: actions\/setup-java@v5/ {
        setup = 1
        temurin = 0
        jdk25 = 0
      }
      setup && /distribution: temurin/ { temurin = 1 }
      setup && /java-version: 25/ { jdk25 = 1 }
      /^[[:space:]]*#/ { next }
      /(^|[[:space:]"({;])xvfb-run[[:space:]]+mvn[[:space:]-]|(^|[[:space:]"({;])mvn[[:space:]-]/ {
        if (!setup || !temurin || !jdk25) {
          printf("[FAIL] %s job %s runs Maven before Temurin JDK 25 setup at line %d\n", file, job ? job : "(unknown)", NR) > "/dev/stderr"
          exit 1
        }
      }
    ' "$workflow"
  done

  pass "test_maven_workflow_jobs_setup_jdk25_before_maven"
}

test_mutating_manual_workflows_default_to_dry_run() {
  echo "Running test_mutating_manual_workflows_default_to_dry_run"

  local workflow
  for workflow in \
    release-scheduler.yml \
    prepare-release.yml \
    publish-release.yml \
    github-release.yml \
    snapshot.yml \
    release-health.yml; do
    local file="$WORKFLOWS/$workflow"
    local section
    section="$(input_section "$file" dryRun)"
    expect_section_contains "$section" "default: true" "${workflow} manual dryRun input should default true"
    expect_section_contains "$section" "type: boolean" "${workflow} manual dryRun input should be typed as boolean"
  done

  pass "test_mutating_manual_workflows_default_to_dry_run"
}

test_official_triggers_normalize_to_non_dry_run() {
  echo "Running test_official_triggers_normalize_to_non_dry_run"

  expect_file_contains "$WORKFLOWS/release-scheduler.yml" 'if [ "$event" = "workflow_dispatch" ]; then' \
    "release scheduler should branch normalization by event"
  expect_file_contains "$WORKFLOWS/release-scheduler.yml" "raw=\"\${DRY_RUN_INPUT:-true}\"" \
    "release scheduler manual default should normalize to dry-run"
  expect_file_contains "$WORKFLOWS/release-scheduler.yml" "dry_run=false" \
    "release scheduler scheduled runs should normalize to non-dry-run"

  expect_file_contains "$WORKFLOWS/publish-release.yml" 'if [ "${{ github.event_name }}" = "pull_request" ]; then' \
    "publish-release should normalize merged release PRs to non-dry-run"
  expect_file_contains "$WORKFLOWS/github-release.yml" 'if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then' \
    "github-release should keep tag pushes non-dry-run"
  expect_file_contains "$WORKFLOWS/snapshot.yml" 'if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then' \
    "snapshot should keep master pushes non-dry-run"
  expect_file_contains "$WORKFLOWS/release-health.yml" 'if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then' \
    "release-health should keep schedule/push/workflow-run triggers non-dry-run"

  pass "test_official_triggers_normalize_to_non_dry_run"
}

test_downstream_dispatches_explicitly_pass_dry_run() {
  echo "Running test_downstream_dispatches_explicitly_pass_dry_run"

  local scheduler_dispatch
  scheduler_dispatch="$(workflow_section "$WORKFLOWS/release-scheduler.yml" 'workflow_id: "prepare-release.yml"' "dryRun")"
  expect_section_contains "$scheduler_dispatch" "dryRun" \
    "release scheduler dispatch should pass dryRun to prepare-release"

  local prepare_dispatch
  prepare_dispatch="$(workflow_section "$WORKFLOWS/prepare-release.yml" 'workflow_id: "publish-release.yml"' "dryRun")"
  expect_section_contains "$prepare_dispatch" "dryRun" \
    "prepare-release dispatch should pass dryRun to publish-release"

  local publish_dispatch
  publish_dispatch="$(workflow_section "$WORKFLOWS/publish-release.yml" 'workflow_id: "snapshot.yml"' 'dryRun: "false"')"
  expect_section_contains "$publish_dispatch" 'dryRun: "false"' \
    "publish-release should explicitly dispatch snapshot publication as non-dry-run"

  pass "test_downstream_dispatches_explicitly_pass_dry_run"
}

test_mutating_steps_remain_dry_run_gated() {
  echo "Running test_mutating_steps_remain_dry_run_gated"

  expect_file_contains "$WORKFLOWS/release-scheduler.yml" "if: always() && needs.analyze.result != 'skipped' && needs.analyze.outputs.dryRun != 'true'" \
    "release scheduler discussion mutation should skip dry-run and skipped scheduled runs"

  expect_file_contains "$WORKFLOWS/prepare-release.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
    "prepare-release mutations should be dry-run gated"
  expect_file_contains "$WORKFLOWS/prepare-release.yml" "Create or update removal-ready deprecation issues" \
    "prepare-release should still have managed issue sync"
  expect_file_contains "$WORKFLOWS/prepare-release.yml" "Create or update release PR" \
    "prepare-release should still have release PR mutation"

  expect_file_contains "$WORKFLOWS/publish-release.yml" "Deploy Release to Maven Central" \
    "publish-release should still have Maven Central deployment"
  expect_file_contains "$WORKFLOWS/publish-release.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
    "publish-release mutations should be dry-run gated"
  expect_file_contains "$WORKFLOWS/publish-release.yml" "if: always() && steps.dry_run.outputs.dryRun != 'true'" \
    "publish-release discussion mutation should skip dry-run"

  expect_file_contains "$WORKFLOWS/github-release.yml" "Create GitHub Release" \
    "github-release should still publish GitHub releases"
  expect_file_contains "$WORKFLOWS/github-release.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
    "github-release publication should skip dry-run"

  expect_file_contains "$WORKFLOWS/snapshot.yml" "Deploy Snapshot" \
    "snapshot workflow should still publish snapshots"
  expect_file_contains "$WORKFLOWS/snapshot.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
    "snapshot deployment and publishing-secret steps should skip dry-run"

  expect_file_contains "$WORKFLOWS/release-health.yml" "if: always() && steps.dry_run.outputs.dryRun != 'true'" \
    "release-health discussion mutation should skip dry-run"

  pass "test_mutating_steps_remain_dry_run_gated"
}

test_dry_run_summaries_and_audits_show_rerun_guidance() {
  echo "Running test_dry_run_summaries_and_audits_show_rerun_guidance"

  expect_file_contains "$WORKFLOWS/release-scheduler.yml" "release-scheduler-mutation-plan.txt" \
    "release scheduler audit artifact should include the dry-run mutation plan"
  expect_file_contains "$WORKFLOWS/prepare-release.yml" '"mutationPlan"' \
    "prepare-release audit JSON should include mutation plan"
  expect_file_contains "$WORKFLOWS/publish-release.yml" '"mutationPlan"' \
    "publish-release audit JSON should include mutation plan"
  expect_file_contains "$WORKFLOWS/github-release.yml" '"mutationPlan"' \
    "github-release audit JSON should include mutation plan"
  expect_file_contains "$WORKFLOWS/snapshot.yml" '"snapshotVersion"' \
    "snapshot audit JSON should include computed snapshot version"
  expect_file_contains "$WORKFLOWS/snapshot.yml" '"mutationPlan"' \
    "snapshot audit JSON should include mutation plan"
  expect_file_contains "$WORKFLOWS/release-health.yml" '"mutationPlan"' \
    "release-health audit JSON should include mutation plan"
  expect_file_contains "$WORKFLOWS/prepare-release.yml" "rerun guidance" \
    "prepare-release summary should include rerun guidance"
  expect_file_contains "$WORKFLOWS/publish-release.yml" "rerun guidance" \
    "publish-release summary should include rerun guidance"
  expect_file_contains "$WORKFLOWS/snapshot.yml" "rerun guidance" \
    "snapshot summary should include rerun guidance"

  pass "test_dry_run_summaries_and_audits_show_rerun_guidance"
}

test_snapshot_and_health_manual_dry_runs_do_not_mutate() {
  echo "Running test_snapshot_and_health_manual_dry_runs_do_not_mutate"

  local deploy_section
  deploy_section="$(workflow_section "$WORKFLOWS/snapshot.yml" "Deploy Snapshot" "Snapshot publication summary")"
  expect_section_contains "$deploy_section" "if: steps.dry_run.outputs.dryRun != 'true'" \
    "snapshot deployment should skip dry-run"

  local health_post_section
  health_post_section="$(workflow_section "$WORKFLOWS/release-health.yml" "Post to Release Scheduler discussion" "with:")"
  expect_section_contains "$health_post_section" "if: always() && steps.dry_run.outputs.dryRun != 'true'" \
    "release-health discussion post should skip dry-run"

  pass "test_snapshot_and_health_manual_dry_runs_do_not_mutate"
}

test_line_of_reports_missing_needles_cleanly() {
  echo "Running test_line_of_reports_missing_needles_cleanly"

  local tmp
  local output
  tmp="$(mktemp "${TMPDIR:-/tmp}/release-workflow-safety.XXXXXX")"
  printf 'present\n' > "$tmp"

  if output="$( (line_of "$tmp" "missing") 2>&1 )"; then
    rm -f "$tmp"
    fail "line_of should fail when the workflow line is missing"
  fi

  rm -f "$tmp"
  expect_section_contains "$output" "missing expected workflow line 'missing'" \
    "line_of should surface the explicit missing-line failure message"

  pass "test_line_of_reports_missing_needles_cleanly"
}

test_publish_release_existing_tag_only_fails_real_runs() {
  echo "Running test_publish_release_existing_tag_only_fails_real_runs"

  expect_file_contains "$WORKFLOWS/publish-release.yml" \
    "if: steps.check_tag.outputs.exists == 'true' && steps.dry_run.outputs.dryRun != 'true'" \
    "publish-release should fail existing tags only during real runs"
  expect_file_contains "$WORKFLOWS/publish-release.yml" "Report existing tag in dry-run mode" \
    "publish-release should keep a dry-run audit path for existing tags"
  expect_file_contains "$WORKFLOWS/publish-release.yml" "audit:existing_tag_dry_run" \
    "publish-release dry-run tag detection should emit an audit line"

  pass "test_publish_release_existing_tag_only_fails_real_runs"
}

test_github_release_preserves_workflow_support_checkout() {
  echo "Running test_github_release_preserves_workflow_support_checkout"

  local full_checkout_line
  local support_checkout_line
  local manifest_line
  full_checkout_line="$(line_of "$WORKFLOWS/github-release.yml" "Checkout full history")"
  support_checkout_line="$(line_of "$WORKFLOWS/github-release.yml" "Checkout workflow support files")"
  manifest_line="$(line_of "$WORKFLOWS/github-release.yml" "workflow-support/scripts/release/release_helpers.py artifact-manifest")"

  if (( support_checkout_line <= full_checkout_line )); then
    fail "github-release should checkout workflow support after the release tag checkout"
  fi
  if (( manifest_line <= support_checkout_line )); then
    fail "github-release should validate artifacts after workflow support checkout"
  fi
  expect_file_contains "$WORKFLOWS/github-release.yml" "path: workflow-support" \
    "github-release should keep support helpers outside the release tag checkout"

  pass "test_github_release_preserves_workflow_support_checkout"
}

test_github_script_blocks_do_not_redeclare_injected_bindings() {
  echo "Running test_github_script_blocks_do_not_redeclare_injected_bindings"

  local workflow
  for workflow in "$WORKFLOWS"/*.yml; do
    assert_no_github_script_injected_binding_redeclarations "$workflow"
  done

  pass "test_github_script_blocks_do_not_redeclare_injected_bindings"
}

test_github_script_binding_scan_rejects_bad_fixture() {
  echo "Running test_github_script_binding_scan_rejects_bad_fixture"

  local tmp
  local output
  tmp="$(mktemp "${TMPDIR:-/tmp}/release-workflow-safety.XXXXXX")"
  cat > "$tmp" <<'YAML'
name: Bad fixture
jobs:
  bad:
    steps:
      - name: Bad GitHub Script
        uses: actions/github-script@v9
        with:
          script: |
            const core = require("@actions/core");
YAML

  if output="$(assert_no_github_script_injected_binding_redeclarations "$tmp" 2>&1)"; then
    rm -f "$tmp"
    fail "github-script injected binding scan should fail on a core redeclaration"
  fi

  rm -f "$tmp"
  expect_section_contains "$output" "redeclares actions/github-script injected binding" \
    "github-script injected binding scan should report the redeclaration"

  pass "test_github_script_binding_scan_rejects_bad_fixture"
}

test_maven_workflow_jobs_setup_jdk25_before_maven
test_mutating_manual_workflows_default_to_dry_run
test_official_triggers_normalize_to_non_dry_run
test_downstream_dispatches_explicitly_pass_dry_run
test_mutating_steps_remain_dry_run_gated
test_dry_run_summaries_and_audits_show_rerun_guidance
test_snapshot_and_health_manual_dry_runs_do_not_mutate
test_line_of_reports_missing_needles_cleanly
test_publish_release_existing_tag_only_fails_real_runs
test_github_release_preserves_workflow_support_checkout
test_github_script_blocks_do_not_redeclare_injected_bindings
test_github_script_binding_scan_rejects_bad_fixture
