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

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALLER_SOURCE="$(cd "$SCRIPT_DIR/.." && pwd)"
TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/ours-rp-installer-env-contract.XXXXXX")"

cleanup() {
  rm -rf "$TEST_ROOT"
}
trap cleanup EXIT

fail() {
  printf 'FAIL: %s\n' "$*" >&2
  exit 1
}

assert_equals() {
  local expected="$1"
  local actual="$2"
  local message="$3"
  [[ "$actual" == "$expected" ]] || fail "$message (expected=$expected actual=$actual)"
}

assert_not_contains() {
  local needle="$1"
  local path="$2"
  ! grep -Fq "$needle" "$path" || fail "unexpected value in $path: $needle"
}

expect_exit() {
  local expected_status="$1"
  local output_path="$2"
  shift 2
  local actual_status

  set +e
  "$@" >"$output_path" 2>&1
  actual_status=$?
  set -e
  [[ "$actual_status" == "$expected_status" ]] || {
    cat "$output_path" >&2 || true
    fail "unexpected exit status (expected=$expected_status actual=$actual_status)"
  }
}

host_arch() {
  case "$(uname -m)" in
    x86_64) printf 'amd64\n' ;;
    aarch64) printf 'arm64\n' ;;
    *) fail "unsupported test host architecture: $(uname -m)" ;;
  esac
}

env_value() {
  local env_path="$1"
  local key="$2"
  (
    # shellcheck disable=SC1090
    source "$INSTALLER_SOURCE/scripts/common.sh"
    env_file_value "$env_path" "$key"
  )
}

create_fixture() {
  local name="$1"
  local fixture="$TEST_ROOT/$name"
  local arch
  arch="$(host_arch)"

  mkdir -p "$fixture"
  cp -a "$INSTALLER_SOURCE"/. "$fixture"/
  rm -rf "$fixture/tests"
  mkdir -p "$fixture/images" "$fixture/fake-bin"
  sed -i \
    -e "s|__PACKAGE_ARCH__|$arch|g" \
    -e "s|__PACKAGE_PLATFORM__|linux/$arch|g" \
    -e "s|__RUNTIME_IMAGE__|ours-rp-runtime-$arch:new|g" \
    -e "s|__METRICS_IMAGE__|ours-rp-metrics-$arch:new|g" \
    -e "s|__HOST_DATA_DIR__|$fixture/data|g" \
    "$fixture/.env.example"
  cat > "$fixture/PACKAGE-MANIFEST.env" <<EOF
PACKAGE_ARCH=$arch
PACKAGE_PLATFORM=linux/$arch
EOF
  cat > "$fixture/fake-bin/docker" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$*" >> "$FAKE_DOCKER_LOG"
if [[ "${1:-}" == "compose" && "${2:-}" == "version" ]]; then
  exit 0
fi
exit 99
EOF
  chmod +x "$fixture/fake-bin/docker"
  for command in jq rsync curl gzip tar; do
    cat > "$fixture/fake-bin/$command" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
    chmod +x "$fixture/fake-bin/$command"
  done
  printf '%s\n' "$fixture"
}

run_upgrade() {
  local fixture="$1"
  local old_env="$2"
  shift 2
  (
    cd "$fixture"
    export PATH="$fixture/fake-bin:$PATH"
    export FAKE_DOCKER_LOG="$fixture/fake-docker.log"
    "$@" ./scripts/upgrade.sh --reuse-env-from "$old_env"
  )
}

run_upgrade_without_reuse() {
  local fixture="$1"
  (
    cd "$fixture"
    export PATH="$fixture/fake-bin:$PATH"
    export FAKE_DOCKER_LOG="$fixture/fake-docker.log"
    ./scripts/upgrade.sh
  )
}

new_fixture_with_old_env() {
  local name="$1"
  local fixture
  fixture="$(create_fixture "$name")"
  cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=120
RPKI_IMAGE=ours-rp-runtime-old:old
REMOVED_LEGACY_KEY=legacy-value
EOF
  printf '%s\n' "$fixture"
}

fixture="$(create_fixture template-defaults)"
expect_exit 99 "$fixture/template.log" run_upgrade_without_reuse "$fixture"
assert_equals "600" "$(env_value "$fixture/.env" INTERVAL_SECS)" "template default must be retained without reuse"

fixture="$(new_fixture_with_old_env default-old)"
expect_exit 99 "$fixture/default.log" run_upgrade "$fixture" "$fixture/old.env"
assert_equals "120" "$(env_value "$fixture/.env" INTERVAL_SECS)" "old value must override template default"
assert_equals "ours-rp-runtime-$(host_arch):new" "$(env_value "$fixture/.env" RPKI_IMAGE)" "standard upgrade must retain package image"
assert_equals "10" "$(env_value "$fixture/.env" METRICS_POLL_SECS)" "template default must fill a key absent from old environment"
! grep -q '^REMOVED_LEGACY_KEY=' "$fixture/.env" || fail "removed old key was copied into target environment"
grep -q 'reused_keys=INTERVAL_SECS' "$fixture/default.log" || fail "reused key summary missing"
grep -q 'ignored_removed_old_keys=REMOVED_LEGACY_KEY' "$fixture/default.log" || fail "removed key summary missing"
grep -q 'template_only_keys=' "$fixture/default.log" || fail "template key summary missing"

fixture="$(new_fixture_with_old_env invocation-overrides)"
expect_exit 99 "$fixture/override.log" run_upgrade "$fixture" "$fixture/old.env" env INTERVAL_SECS=300 GRAFANA_ADMIN_PASSWORD='secret with space'
assert_equals "300" "$(env_value "$fixture/.env" INTERVAL_SECS)" "invocation value must override old value"
assert_equals "secret with space" "$(env_value "$fixture/.env" GRAFANA_ADMIN_PASSWORD)" "invocation value must preserve shell-sensitive content"
grep -q 'command_override_keys=INTERVAL_SECS,GRAFANA_ADMIN_PASSWORD' "$fixture/override.log" || fail "override key summary missing"
assert_not_contains 'secret with space' "$fixture/override.log"

fixture="$(new_fixture_with_old_env invocation-special-characters)"
special_password="O'Reilly"
special_password+=' $literal `tick` slash\'
expect_exit 99 "$fixture/special.log" run_upgrade "$fixture" "$fixture/old.env" env GRAFANA_ADMIN_PASSWORD="$special_password"
assert_equals "$special_password" "$(env_value "$fixture/.env" GRAFANA_ADMIN_PASSWORD)" "invocation value with dotenv-sensitive characters must round-trip"
assert_not_contains "$special_password" "$fixture/special.log"

fixture="$(create_fixture existing-target-env)"
cp "$fixture/.env.example" "$fixture/.env"
sed -i 's/^INTERVAL_SECS=.*/INTERVAL_SECS=180/' "$fixture/.env"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=120
EOF
expect_exit 99 "$fixture/existing.log" run_upgrade "$fixture" "$fixture/old.env" env INTERVAL_SECS=300
assert_equals "300" "$(env_value "$fixture/.env" INTERVAL_SECS)" "invocation value must override an existing target environment"

fixture="$(new_fixture_with_old_env explicit-empty)"
expect_exit 1 "$fixture/empty.log" run_upgrade "$fixture" "$fixture/old.env" env INTERVAL_SECS=
[[ ! -s "$fixture/fake-docker.log" ]] || fail "explicit empty value reached Docker"
grep -q 'INTERVAL_SECS' "$fixture/empty.log" || fail "empty-value error did not identify key"

fixture="$(create_fixture empty-old-value)"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=
EOF
expect_exit 1 "$fixture/old-empty.log" run_upgrade "$fixture" "$fixture/old.env"
[[ ! -s "$fixture/fake-docker.log" ]] || fail "empty reused value reached Docker"
grep -q 'INTERVAL_SECS' "$fixture/old-empty.log" || fail "empty reused value error did not identify key"

fixture="$(create_fixture empty-template)"
sed -i 's/^INTERVAL_SECS=.*/INTERVAL_SECS=/' "$fixture/.env.example"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=120
EOF
expect_exit 1 "$fixture/template-empty.log" run_upgrade "$fixture" "$fixture/old.env"
[[ ! -s "$fixture/fake-docker.log" ]] || fail "empty template default reached Docker"
grep -q 'INTERVAL_SECS' "$fixture/template-empty.log" || fail "empty template error did not identify key"

fixture="$(new_fixture_with_old_env package-guard)"
arch="$(host_arch)"
other_arch="amd64"
[[ "$arch" == "amd64" ]] && other_arch="arm64"
expect_exit 1 "$fixture/arch.log" run_upgrade "$fixture" "$fixture/old.env" env PACKAGE_ARCH="$other_arch"
[[ ! -s "$fixture/fake-docker.log" ]] || fail "package architecture mismatch reached Docker"
grep -q 'mismatches manifest PACKAGE_ARCH' "$fixture/arch.log" || fail "package architecture guard did not reject override"

printf 'PASS: Docker installer upgrade environment contract\n'
