#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# lint-infra.sh — Infrastructure validation for the ByteLyst ecosystem
# ═══════════════════════════════════════════════════════════════════════
# Validates all Dockerfiles (hadolint) and Helm charts (helm lint + template)
# across the 11 ByteLyst repos.
#
# Usage:
#   ./scripts/lint-infra.sh              # Lint everything (Dockerfiles + Helm charts)
#   ./scripts/lint-infra.sh --docker     # Dockerfiles only
#   ./scripts/lint-infra.sh --helm       # Helm charts only
#   ./scripts/lint-infra.sh --fix        # Show suggested fixes alongside errors
#   ./scripts/lint-infra.sh path/to/Dockerfile [path/to/Chart-dir] ...
#
# Prerequisites:
#   brew install hadolint helm
#
# Exit codes:
#   0  All checks passed
#   1  One or more checks failed
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail

# ── Colors ─────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'

# ── Globals ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WORKSPACE_ROOT="$(cd "$REPO_ROOT/.." && pwd)"

LINT_DOCKER=true
LINT_HELM=true
SHOW_FIX=false
EXPLICIT_PATHS=()

PASS=0
FAIL=0
SKIP=0
FAILURES=()

# ── Known Hadolint false-positive suppressions ─────────────────────────
# DL3045: COPY to relative path — all our Dockerfiles set WORKDIR first
# DL3018: Pin versions in apk add — we use node:22-alpine base images
# DL3008: Pin versions in apt-get — impractical for Python sidecar base images
# DL3059: Multiple consecutive RUN — intentional for layer caching
# SC2155: Declare and assign separately — false positive for BuildKit secret mount pattern
#         (export VAR="$(cat /run/secrets/...)" is the canonical Docker secret idiom)
HADOLINT_IGNORE="DL3045,DL3018,DL3008,DL3059,SC2155"

# ── ByteLyst ecosystem repos (relative to workspace root) ─────────────
ECOSYSTEM_REPOS=(
  learning_ai_common_plat
  learning_voice_ai_agent
  learning_multimodal_memory_agents
  learning_ai_clock
  learning_ai_jarvis_jr
  learning_ai_fastgap
  learning_ai_peakpulse
  learning_ai_flowmonk
  learning_ai_notes
  learning_ai_trails
  learning_ai_local_memory_gpt
)

# ── Helpers ────────────────────────────────────────────────────────────
log()  { echo -e "${CYAN}[lint-infra]${NC} $*"; }
ok()   { echo -e "  ${GREEN}✓${NC} $*"; PASS=$((PASS + 1)); }
fail() { echo -e "  ${RED}✗${NC} $*"; FAIL=$((FAIL + 1)); FAILURES+=("$1"); }
skip() { echo -e "  ${DIM}⊘ $*${NC}"; SKIP=$((SKIP + 1)); }
warn() { echo -e "  ${YELLOW}⚠${NC} $*"; }

usage() {
  echo "Usage: $(basename "$0") [OPTIONS] [PATH ...]"
  echo ""
  echo "Options:"
  echo "  --docker    Lint Dockerfiles only"
  echo "  --helm      Lint Helm charts only"
  echo "  --fix       Show suggested fixes alongside errors"
  echo "  -h, --help  Show this help"
  echo ""
  echo "If PATHs are given, only those files/dirs are checked."
  echo "Otherwise, auto-discovers across all 11 ByteLyst repos."
  echo ""
  echo "Prerequisites: brew install hadolint helm"
}

# Check if a tool is available, print install instructions if not
require_tool() {
  local tool="$1" install_cmd="$2"
  if ! command -v "$tool" &>/dev/null; then
    echo -e "${RED}Error:${NC} '${tool}' is not installed."
    echo -e "  Install: ${BOLD}${install_cmd}${NC}"
    echo ""
    return 1
  fi
  return 0
}

# Return a short relative path for display
short_path() {
  local full="$1"
  # Try to make path relative to workspace root
  if [[ "$full" == "$WORKSPACE_ROOT"/* ]]; then
    echo "${full#$WORKSPACE_ROOT/}"
  else
    echo "$full"
  fi
}

# ── Auto-discovery ─────────────────────────────────────────────────────
discover_dockerfiles() {
  local files=()
  for repo in "${ECOSYSTEM_REPOS[@]}"; do
    local repo_dir="${WORKSPACE_ROOT}/${repo}"
    [ -d "$repo_dir" ] || continue
    while IFS= read -r -d '' f; do
      files+=("$f")
    done < <(find "$repo_dir" -maxdepth 4 -name 'Dockerfile*' \
      -not -path '*/node_modules/*' \
      -not -path '*/.git/*' \
      -not -path '*/_deferred*' \
      -print0 2>/dev/null | sort -z)
  done
  echo "${files[@]}"
}

discover_helm_charts() {
  local dirs=()
  for repo in "${ECOSYSTEM_REPOS[@]}"; do
    local repo_dir="${WORKSPACE_ROOT}/${repo}"
    [ -d "$repo_dir" ] || continue
    while IFS= read -r -d '' f; do
      dirs+=("$(dirname "$f")")
    done < <(find "$repo_dir" -maxdepth 4 -name 'Chart.yaml' \
      -not -path '*/node_modules/*' \
      -not -path '*/.git/*' \
      -print0 2>/dev/null | sort -z)
  done
  echo "${dirs[@]+"${dirs[@]}"}"
}

# ── Lint functions ─────────────────────────────────────────────────────

lint_dockerfile() {
  local file="$1"
  local display
  display=$(short_path "$file")

  if [ ! -f "$file" ]; then
    skip "${display} (not found)"
    return
  fi

  local output exit_code=0
  # Build hadolint args
  local hadolint_args=(--no-color)
  IFS=',' read -ra ignore_rules <<< "$HADOLINT_IGNORE"
  for rule in "${ignore_rules[@]}"; do
    hadolint_args+=(--ignore "$rule")
  done

  output=$(hadolint "${hadolint_args[@]}" "$file" 2>&1) || exit_code=$?

  if [ $exit_code -eq 0 ]; then
    ok "${display}"
  else
    fail "${display}"
    # Print each warning/error indented
    while IFS= read -r line; do
      if [[ "$line" == *"error"* ]]; then
        echo -e "    ${RED}${line}${NC}"
      elif [[ "$line" == *"warning"* ]]; then
        echo -e "    ${YELLOW}${line}${NC}"
      else
        echo -e "    ${DIM}${line}${NC}"
      fi
    done <<< "$output"

    if $SHOW_FIX; then
      echo -e "    ${CYAN}Fix: review hadolint wiki — https://github.com/hadolint/hadolint/wiki${NC}"
    fi
  fi
}

lint_helm_chart() {
  local chart_dir="$1"
  local display
  display=$(short_path "$chart_dir")

  if [ ! -f "${chart_dir}/Chart.yaml" ]; then
    skip "${display} (no Chart.yaml)"
    return
  fi

  local output exit_code

  # 1. helm lint (syntax + structure)
  output=$(helm lint "$chart_dir" 2>&1) || exit_code=$?
  if [ "${exit_code:-0}" -ne 0 ]; then
    fail "${display} (helm lint)"
    echo "$output" | while IFS= read -r line; do
      echo -e "    ${DIM}${line}${NC}"
    done
    return
  fi

  # 2. helm template (renders without errors)
  output=$(helm template test-release "$chart_dir" 2>&1) || exit_code=$?
  if [ "${exit_code:-0}" -ne 0 ]; then
    fail "${display} (helm template)"
    echo "$output" | tail -10 | while IFS= read -r line; do
      echo -e "    ${DIM}${line}${NC}"
    done
    return
  fi

  ok "${display}"
}

# ── Parse CLI ──────────────────────────────────────────────────────────
parse_args() {
  while [ $# -gt 0 ]; do
    case "$1" in
      --docker)
        LINT_DOCKER=true; LINT_HELM=false ;;
      --helm)
        LINT_DOCKER=false; LINT_HELM=true ;;
      --fix)
        SHOW_FIX=true ;;
      -h|--help)
        usage; exit 0 ;;
      -*)
        echo "Unknown option: $1"; usage; exit 1 ;;
      *)
        EXPLICIT_PATHS+=("$1") ;;
    esac
    shift
  done
}

# ── Main ───────────────────────────────────────────────────────────────
main() {
  parse_args "$@"

  echo ""
  echo -e "${BOLD}╔═══════════════════════════════════════════════════════════╗${NC}"
  echo -e "${BOLD}║       ByteLyst Infrastructure Lint                        ║${NC}"
  echo -e "${BOLD}╚═══════════════════════════════════════════════════════════╝${NC}"
  echo ""

  # ── Tool checks ────────────────────────────────────────────────────
  local missing=false
  if $LINT_DOCKER; then
    require_tool hadolint "brew install hadolint" || missing=true
  fi
  if $LINT_HELM; then
    require_tool helm "brew install helm" || missing=true
  fi
  if $missing; then
    echo -e "${RED}Missing required tools. Install them and re-run.${NC}"
    exit 1
  fi

  # ── Explicit paths mode ────────────────────────────────────────────
  if [ ${#EXPLICIT_PATHS[@]} -gt 0 ]; then
    log "Linting ${#EXPLICIT_PATHS[@]} explicit path(s)..."
    echo ""
    for p in "${EXPLICIT_PATHS[@]}"; do
      if [[ "$(basename "$p")" == Dockerfile* ]]; then
        lint_dockerfile "$p"
      elif [ -f "${p}/Chart.yaml" ]; then
        lint_helm_chart "$p"
      else
        warn "Unknown target: $p (expected Dockerfile* or dir with Chart.yaml)"
      fi
    done
  else
    # ── Auto-discover mode ─────────────────────────────────────────
    if $LINT_DOCKER; then
      log "Discovering Dockerfiles across ${#ECOSYSTEM_REPOS[@]} repos..."
      echo ""

      local dockerfiles
      IFS=' ' read -ra dockerfiles <<< "$(discover_dockerfiles)"

      if [ ${#dockerfiles[@]} -eq 0 ]; then
        warn "No Dockerfiles found."
      else
        log "Found ${#dockerfiles[@]} Dockerfile(s)"
        echo ""
        for df in "${dockerfiles[@]}"; do
          lint_dockerfile "$df"
        done
      fi
      echo ""
    fi

    if $LINT_HELM; then
      log "Discovering Helm charts across ${#ECOSYSTEM_REPOS[@]} repos..."
      echo ""

      local charts
      IFS=' ' read -ra charts <<< "$(discover_helm_charts)"

      if [ ${#charts[@]} -eq 0 ] || [ -z "${charts[0]:-}" ]; then
        log "No Helm charts found (none in this workspace)."
      else
        log "Found ${#charts[@]} Helm chart(s)"
        echo ""
        for chart in "${charts[@]}"; do
          lint_helm_chart "$chart"
        done
      fi
      echo ""
    fi
  fi

  # ── Summary ──────────────────────────────────────────────────────
  echo ""
  echo -e "${BOLD}═══ Summary ═══${NC}"
  echo -e "  ${GREEN}Passed:${NC}  ${PASS}"
  echo -e "  ${RED}Failed:${NC}  ${FAIL}"
  if [ $SKIP -gt 0 ]; then
    echo -e "  ${DIM}Skipped: ${SKIP}${NC}"
  fi
  echo ""

  if [ $FAIL -gt 0 ]; then
    echo -e "${RED}${BOLD}FAILED${NC} — ${FAIL} issue(s) found:"
    for f in "${FAILURES[@]}"; do
      echo -e "  ${RED}✗${NC} ${f}"
    done
    echo ""
    echo -e "${DIM}Suppressed rules: ${HADOLINT_IGNORE}${NC}"
    echo -e "${DIM}To see fix suggestions, re-run with --fix${NC}"
    exit 1
  else
    echo -e "${GREEN}${BOLD}ALL PASSED${NC} — ${PASS} check(s) clean."
    if [ $SKIP -gt 0 ]; then
      echo -e "${DIM}(${SKIP} skipped)${NC}"
    fi
    exit 0
  fi
}

main "$@"
