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

tmp_dir="$(mktemp -d)"
cleanup() {
  rm -rf "$tmp_dir"
}
trap cleanup EXIT

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
agentrail="${repo_dir}/scripts/agentrail"

assert_grep() {
  local pattern="$1"
  local file="$2"
  local message="$3"

  if ! grep -q -- "$pattern" "$file"; then
    echo "$message" >&2
    echo "--- ${file} ---" >&2
    cat "$file" >&2
    exit 1
  fi
}

make_fixture() {
  local fixture="$1"
  mkdir -p "$fixture"
  git -C "$fixture" init --quiet
  "$agentrail" install --target "$fixture" >/dev/null
}

mutate_registry() {
  local fixture="$1"
  local expression="$2"

  node - "${fixture}/docs/agents/skill-registry.json" "$expression" <<'NODE'
const fs = require("fs");
const file = process.argv[2];
const expression = process.argv[3];
const registry = JSON.parse(fs.readFileSync(file, "utf8"));
const fn = new Function("registry", `"use strict"; ${expression}`);
fn(registry);
fs.writeFileSync(file, `${JSON.stringify(registry, null, 2)}\n`);
NODE
}

write_registry_root() {
  local fixture="$1"
  local json="$2"

  printf '%s\n' "$json" >"${fixture}/docs/agents/skill-registry.json"
}

expect_invalid() {
  local fixture="$1"
  local pattern="$2"
  local message="$3"
  local output="${tmp_dir}/$(basename "$fixture").out"

  if "$agentrail" skills validate --target "$fixture" >"$output" 2>&1; then
    echo "$message" >&2
    cat "$output" >&2
    exit 1
  fi

  assert_grep "$pattern" "$output" "$message"
}

valid="${tmp_dir}/valid"
make_fixture "$valid"
"$agentrail" skills validate --target "$valid" >"${tmp_dir}/valid.out"
assert_grep "ok skill registry" "${tmp_dir}/valid.out" "valid registry was not accepted"

for root_case in null false 0 '""' '[]'; do
  root_fixture="${tmp_dir}/root-${root_case//[^a-zA-Z0-9]/root}"
  make_fixture "$root_fixture"
  write_registry_root "$root_fixture" "$root_case"
  expect_invalid "$root_fixture" "registry root must be an object" "registry root ${root_case} was not rejected as a non-object"
done

duplicate="${tmp_dir}/duplicate"
make_fixture "$duplicate"
mutate_registry "$duplicate" "registry.skills[1].name = registry.skills[0].name;"
expect_invalid "$duplicate" "duplicate skill name" "duplicate registry names were not rejected"

missing="${tmp_dir}/missing"
make_fixture "$missing"
mutate_registry "$missing" "delete registry.skills[0].description;"
expect_invalid "$missing" "missing required field description" "missing registry fields were not rejected"

invalid_path="${tmp_dir}/invalid-path"
make_fixture "$invalid_path"
mutate_registry "$invalid_path" "registry.skills[0].localPath = 'skills/nope/SKILL.md';"
expect_invalid "$invalid_path" "localPath does not exist" "invalid registry local paths were not rejected"

missing_registry="${tmp_dir}/missing-registry"
make_fixture "$missing_registry"
rm "${missing_registry}/docs/agents/skill-registry.json"
expect_invalid "$missing_registry" "cannot read registry" "missing registry file was not rejected"

doctor_missing_registry="${tmp_dir}/doctor-missing-registry"
make_fixture "$doctor_missing_registry"
rm "${doctor_missing_registry}/docs/agents/skill-registry.json"
if "$agentrail" doctor --target "$doctor_missing_registry" >"${tmp_dir}/doctor-missing-registry.out" 2>&1; then
  echo "doctor did not reject a missing skill registry" >&2
  cat "${tmp_dir}/doctor-missing-registry.out" >&2
  exit 1
fi
assert_grep "status: corrupt" "${tmp_dir}/doctor-missing-registry.out" "doctor did not mark missing registry as corrupt"
assert_grep "missing docs/agents/skill-registry.json" "${tmp_dir}/doctor-missing-registry.out" "doctor did not report missing registry core file"
assert_grep "cannot read registry" "${tmp_dir}/doctor-missing-registry.out" "doctor did not print missing registry validation error"

doctor_broken_skill_path="${tmp_dir}/doctor-broken-skill-path"
make_fixture "$doctor_broken_skill_path"
mutate_registry "$doctor_broken_skill_path" "registry.skills[0].localPath = 'skills/nope/SKILL.md';"
if "$agentrail" doctor --target "$doctor_broken_skill_path" >"${tmp_dir}/doctor-broken-skill-path.out" 2>&1; then
  echo "doctor did not reject a broken skill path" >&2
  cat "${tmp_dir}/doctor-broken-skill-path.out" >&2
  exit 1
fi
assert_grep "status: corrupt" "${tmp_dir}/doctor-broken-skill-path.out" "doctor did not mark broken skill paths as corrupt"
assert_grep "localPath does not exist: skills/nope/SKILL.md" "${tmp_dir}/doctor-broken-skill-path.out" "doctor did not report the broken skill path clearly"

missing_skill_content="${tmp_dir}/missing-skill-content"
make_fixture "$missing_skill_content"
cat >"${missing_skill_content}/skills/frontend-web/SKILL.md" <<'EOF'
# Frontend Web

Use this skill when changing frontend code.
EOF
expect_invalid "$missing_skill_content" "missing SKILL.md section ## Provenance / Audit" "local skill files without required audit/provenance sections were not rejected"

malformed_trigger="${tmp_dir}/malformed-trigger"
make_fixture "$malformed_trigger"
mutate_registry "$malformed_trigger" "registry.skills[0].triggers.keywords = 'frontend';"
expect_invalid "$malformed_trigger" "triggers.keywords must be an array" "malformed registry triggers were not rejected"

doctor_invalid="${tmp_dir}/doctor-invalid"
make_fixture "$doctor_invalid"
write_registry_root "$doctor_invalid" "null"
if "$agentrail" doctor --target "$doctor_invalid" >"${tmp_dir}/doctor-invalid.out" 2>&1; then
  echo "doctor did not reject an invalid registry" >&2
  cat "${tmp_dir}/doctor-invalid.out" >&2
  exit 1
fi
assert_grep "status: corrupt" "${tmp_dir}/doctor-invalid.out" "doctor did not mark invalid registry as corrupt"
assert_grep "registry root must be an object" "${tmp_dir}/doctor-invalid.out" "doctor did not print invalid registry root errors"

echo "skill registry validation test passed"
