#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# qa-unit-test-run.sh   (DEV Ground Rule §Precondition.1 — line coverage ≥ 80%)
#
# Stack-agnostic: runs whatever unit-test + coverage command is configured in
# .cqa-runner.conf, then parses the resulting coverage report and checks the
# line-coverage gate. Works for any language whose coverage tool can emit one of
# the supported report formats (see COVERAGE_FORMAT below / stack-recipes.md).
#
# Usage:
#   ./qa-unit-test-run.sh            # run tests + coverage, print line %
#   ./qa-unit-test-run.sh --strict   # exit ≠ 0 if the gate fails
#   ./qa-unit-test-run.sh --md       # also write a Markdown summary
#   ./qa-unit-test-run.sh --out DIR  # override OUT_DIR
#
# Config (.cqa-runner.conf, overridable via env):
#   COVERAGE_CMD / UNIT_TEST_CMD   command(s) to run the suite (+ emit coverage)
#   COVERAGE_FILE                  path to the coverage report
#   COVERAGE_FORMAT                lcov | cobertura | go-func | json
#   COVERAGE_JSON_KEY              dotted key (COVERAGE_FORMAT=json only)
#   GATE_LINE                      line-coverage threshold (default 80)
#   OUT_DIR                        output dir (default qa-coverage)
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

GREEN='\033[0;32m'; RED='\033[0;31m'; YEL='\033[0;33m'
CYAN='\033[0;36m';  BOLD='\033[1m';   NC='\033[0m'

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"

# ── Load config ──────────────────────────────────────────────────────────────
CONF="${CQA_RUNNER_CONF:-$ROOT_DIR/.cqa-runner.conf}"
if [[ -f "$CONF" ]]; then
  # shellcheck disable=SC1090
  source "$CONF"
else
  echo -e "${RED}❌ config not found: $CONF${NC}"
  echo "   Create it from reference/qa-runner.conf.example (the skill does this)."
  exit 1
fi

STRICT=0
WRITE_MD=0
while [[ $# -gt 0 ]]; do
  case "$1" in
    --strict) STRICT=1; shift ;;
    --md)     WRITE_MD=1; shift ;;
    --out)    OUT_DIR="$2"; shift 2 ;;
    -h|--help) sed -n '4,26p' "$0"; exit 0 ;;
    *) echo "Unknown option: $1" >&2; exit 2 ;;
  esac
done

OUT_DIR="${OUT_DIR:-qa-coverage}"
GATE_LINE="${GATE_LINE:-80}"
mkdir -p "$OUT_DIR"

# Expand ${OUT_DIR} inside configured paths/commands — literal substitution only,
# never eval (commands may legitimately contain && / ; / pipes).
expand_out() { local s="${1:-}"; printf '%s' "${s//\$\{OUT_DIR\}/$OUT_DIR}"; }
COVERAGE_FILE="$(expand_out "${COVERAGE_FILE:-}")"
RUN_CMD="$(expand_out "${COVERAGE_CMD:-}")"
[[ -z "$RUN_CMD" ]] && RUN_CMD="$(expand_out "${UNIT_TEST_CMD:-}")"

if [[ -z "$RUN_CMD" ]]; then
  echo -e "${RED}❌ neither COVERAGE_CMD nor UNIT_TEST_CMD is set in $CONF${NC}"; exit 1
fi

echo -e "${CYAN}${BOLD}[1/3] running unit tests + coverage${NC}"
echo "  \$ $RUN_CMD"
START=$(date +%s)
set +e
bash -c "$RUN_CMD"
TEST_EXIT=$?
set -e
ELAPSED=$(( $(date +%s) - START ))

if [[ $TEST_EXIT -ne 0 ]]; then
  echo -e "${RED}tests failed (exit=$TEST_EXIT)${NC}"
  exit $TEST_EXIT
fi

# ── Parse line coverage % ─────────────────────────────────────────────────────
echo -e "${CYAN}${BOLD}[2/3] reading coverage (${COVERAGE_FORMAT:-?}) → ${COVERAGE_FILE:-?}${NC}"

if [[ -z "${COVERAGE_FORMAT:-}" ]]; then
  echo -e "${YEL}⚠ COVERAGE_FORMAT not set — skipping coverage gate.${NC}"
  echo -e "${CYAN}${BOLD}[3/3] done${NC} (tests passed, ${ELAPSED}s)"
  exit 0
fi
if [[ ! -f "$COVERAGE_FILE" ]]; then
  echo -e "${RED}❌ coverage file not found: $COVERAGE_FILE${NC}"
  echo "   Check that COVERAGE_CMD actually writes it. Tests passed, gate not checked."
  [[ $STRICT -eq 1 ]] && exit 2 || exit 0
fi

read_json_key() {  # $1=file $2=dotted.key  → prints number
  local f="$1" k="$2"
  if command -v jq >/dev/null 2>&1; then
    jq -r ".${k} // empty" "$f"
  elif command -v python3 >/dev/null 2>&1; then
    python3 -c 'import json,sys
d=json.load(open(sys.argv[1]))
for p in sys.argv[2].split("."):
    d=d.get(p) if isinstance(d,dict) else None
    if d is None: break
print(d if d is not None else "")' "$f" "$k"
  elif command -v node >/dev/null 2>&1; then
    node -e 'const d=require(process.argv[1]);let v=d;for(const p of process.argv[2].split("."))v=(v==null?undefined:v[p]);console.log(v==null?"":v)' "$f" "$k"
  else
    echo ""
  fi
}

LINE_PCT=""
case "$COVERAGE_FORMAT" in
  lcov)
    LINE_PCT=$(awk -F: '/^LF:/{f+=$2} /^LH:/{h+=$2} END{ if(f>0) printf "%.2f", h/f*100; else print "" }' "$COVERAGE_FILE")
    ;;
  cobertura)
    LINE_PCT=$(awk 'match($0,/line-rate="[0-9.]+"/){ s=substr($0,RSTART,RLENGTH); gsub(/[^0-9.]/,"",s); printf "%.2f", s*100; exit }' "$COVERAGE_FILE")
    ;;
  go-func)
    LINE_PCT=$(awk '/^total:/{ s=$NF; gsub(/%/,"",s); print s; exit }' "$COVERAGE_FILE")
    ;;
  json)
    LINE_PCT=$(read_json_key "$COVERAGE_FILE" "${COVERAGE_JSON_KEY:-totals.percent_covered}")
    ;;
  *)
    echo -e "${RED}❌ unknown COVERAGE_FORMAT='$COVERAGE_FORMAT'${NC}"; exit 2 ;;
esac

if [[ -z "$LINE_PCT" ]]; then
  echo -e "${RED}❌ could not parse line coverage from $COVERAGE_FILE${NC}"
  [[ $STRICT -eq 1 ]] && exit 2 || exit 0
fi

bar="═════════════════════════════════════════════"
printf "\n%s\n %bcoverage summary%b\n%s\n" "$bar" "$BOLD" "$NC" "$bar"
awk -v p="$LINE_PCT" -v g="$GATE_LINE" 'BEGIN{
  c=(p>=95)?"\033[0;32m":(p>=g)?"\033[0;33m":"\033[0;31m";
  printf "  Line coverage : %s%6.2f%%\033[0m   (gate ≥ %s%%)\n", c, p, g }'
echo "$bar"

if [[ $WRITE_MD -eq 1 ]]; then
  MD="$OUT_DIR/coverage_report.md"
  { echo "# Coverage Report"
    echo
    echo "- command: \`$RUN_CMD\`"
    echo "- line coverage: **${LINE_PCT}%** (gate ≥ ${GATE_LINE}%)"
    echo "- source report: \`$COVERAGE_FILE\` (${COVERAGE_FORMAT})"
  } > "$MD"
  echo -e "${GREEN}📝 $MD${NC}"
fi

echo -e "${CYAN}${BOLD}[3/3] done${NC} (${ELAPSED}s)"

GATE_OK=$(awk -v p="$LINE_PCT" -v g="$GATE_LINE" 'BEGIN{print (p+0>=g+0)?1:0}')
if [[ "$GATE_OK" -eq 1 ]]; then
  echo -e "${GREEN}✅ line coverage gate passed (${LINE_PCT}% ≥ ${GATE_LINE}%)${NC}"
else
  msg="line coverage ${LINE_PCT}% < ${GATE_LINE}%"
  if [[ $STRICT -eq 1 ]]; then echo -e "${RED}❌ $msg${NC}"; exit 2
  else echo -e "${YEL}⚠ $msg (not --strict — exit 0)${NC}"; fi
fi
