#!/bin/bash
# ==============================================================================
# run-epic.sh — Automated Epic Development Pipeline
# ==============================================================================
# Orchestrates BMAD workflows (create-story → dev-story → code-review)
# for each story in an epic. Each step runs in a clean Claude context.
#
# Usage:
#   ./scripts/run-epic.sh --epic <epic-path> [options]
#   ./scripts/run-epic.sh --stories <story1> <story2> ... [options]
#
# Examples:
#   # Process entire epic (creates stories first, then develops them)
#   ./scripts/run-epic.sh --epic docs/03-epics/04-banking/EPIC-001-transfers-payments/epic.md
#
#   # Process specific stories only (skip story creation)
#   ./scripts/run-epic.sh --stories \
#     docs/03-epics/04-banking/EPIC-001-transfers-payments/features/FEAT-001-1-p2p-transfers/stories/story-001.md \
#     docs/03-epics/04-banking/EPIC-001-transfers-payments/features/FEAT-001-2-wire-transfers/stories/story-001.md
#
#   # Dry run (show what would be executed)
#   ./scripts/run-epic.sh --epic docs/03-epics/04-banking/EPIC-001-transfers-payments/epic.md --dry-run
#
#   # Skip already-completed stories
#   ./scripts/run-epic.sh --epic docs/03-epics/04-banking/EPIC-001-transfers-payments/epic.md --skip-completed
#
#   # Start from a specific phase (useful for resuming)
#   ./scripts/run-epic.sh --stories story1.md --phase dev
#
# Options:
#   --epic <path>         Path to epic.md file (will discover/create stories)
#   --stories <paths...>  Explicit story paths (skip story creation phase)
#   --phase <phase>       Start from phase: create | dev | review (default: create)
#   --skip-completed      Skip stories with status: done/completed in their YAML frontmatter
#   --cooldown <seconds>  Pause between stories (default: 120)
#   --dry-run             Show execution plan without running
#   --no-review           Skip code-review phase
#   --log-dir <path>      Directory for logs (default: .logs/epic-pipeline/)
#   --help                Show this help message
# ==============================================================================

set -euo pipefail

# ── Portable Helper Functions ─────────────────────────────────────────────────
# macOS-compatible replacement for `realpath --relative-to`
relpath() {
  python3 -c "import os; print(os.path.relpath('$1', '${2:-$PWD}'))"
}

# ── Configuration ──────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR"
LOG_DIR="${PROJECT_ROOT}/.logs/epic-pipeline"
COOLDOWN=10
DRY_RUN=false
SKIP_COMPLETED=false
NO_REVIEW=false
START_PHASE="create"
EPIC_PATH=""
STORIES=()
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")

# ── Colors ─────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color

# ── Functions ──────────────────────────────────────────────────────────────────

usage() {
  head -n 38 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//'
  exit 0
}

log() {
  local level="$1"
  shift
  local msg="$*"
  local ts
  ts=$(date +"%H:%M:%S")

  case "$level" in
    INFO)  echo -e "${BLUE}[$ts]${NC} ${BOLD}INFO${NC}  $msg" ;;
    OK)    echo -e "${GREEN}[$ts]${NC} ${GREEN}${BOLD}OK${NC}    $msg" ;;
    WARN)  echo -e "${YELLOW}[$ts]${NC} ${YELLOW}${BOLD}WARN${NC}  $msg" ;;
    ERROR) echo -e "${RED}[$ts]${NC} ${RED}${BOLD}ERROR${NC} $msg" ;;
    STEP)  echo -e "${CYAN}[$ts]${NC} ${CYAN}${BOLD}STEP${NC}  $msg" ;;
  esac

  # Also append to log file
  echo "[$ts] $level $msg" >> "${LOG_DIR}/run-${TIMESTAMP}.log" 2>/dev/null || true
}

separator() {
  echo ""
  echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
  echo -e "${BOLD}  $1${NC}"
  echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
  echo ""
}

check_prerequisites() {
  if ! command -v claude &> /dev/null; then
    log ERROR "claude CLI not found. Install: https://docs.anthropic.com/en/docs/claude-code"
    exit 1
  fi

  if [ ! -d "${PROJECT_ROOT}/_bmad" ]; then
    log ERROR "Not in a valid BMAD project root (_bmad/ not found)"
    exit 1
  fi
}

# Check if a story is completed by reading its YAML frontmatter
is_story_completed() {
  local story_path="$1"
  if [ ! -f "$story_path" ]; then
    return 1  # Not completed (doesn't exist yet)
  fi
  # Check for status: done, completed, or delivered in frontmatter
  if head -50 "$story_path" | grep -qiE '^status:\s*(done|completed|delivered)'; then
    return 0  # Completed
  fi
  return 1  # Not completed
}

# Extract story titles from epic.md
# Parses headers like: ### Story 1.1: Lexer & Tokenizer
extract_features_from_epic() {
  local epic_file="$1"
  grep -E '^### Story [0-9]+\.[0-9]+:' "$epic_file" \
    | sed 's/^### Story [0-9]*\.[0-9]*: //' || true
}

# Convert feature name to story path convention
feature_to_slug() {
  echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//'
}

# Discover stories from _bmad-output/implementation-artifacts/
STORY_DIR="${PROJECT_ROOT}/_bmad-output/implementation-artifacts"

discover_stories() {
  log INFO "Discovering stories in: $STORY_DIR"

  # Find story markdown files matching pattern like 1-1-lexer-tokenizer.md
  local found_stories=()
  while IFS= read -r -d '' story; do
    found_stories+=("$story")
  done < <(find "$STORY_DIR" -maxdepth 1 -name "[0-9]*-[0-9]*-*.md" -print0 2>/dev/null | sort -z)

  if [ ${#found_stories[@]} -eq 0 ]; then
    log WARN "No stories found. Stories will be created in the create phase."
    return 0
  fi

  log OK "Found ${#found_stories[@]} stories"
  for s in "${found_stories[@]}"; do
    local rel_path
    rel_path=$(relpath "$s" "$PROJECT_ROOT")
    echo "  → $rel_path"
    STORIES+=("$rel_path")
  done
}

# Generate simulated stories for dry-run mode (when stories don't exist yet)
simulate_stories_from_epic() {
  local epic_rel
  epic_rel=$(relpath "$EPIC_PATH" "$PROJECT_ROOT")

  log INFO "Simulating stories from epic features..."

  local feature_num=0
  while IFS= read -r feature_name; do
    [ -z "$feature_name" ] && continue
    ((feature_num++)) || true
    local slug
    slug=$(feature_to_slug "$feature_name")
    local simulated_path="_bmad-output/implementation-artifacts/${feature_num}-1-${slug}.md"
    STORIES+=("$simulated_path")
    log INFO "  [simulated] $simulated_path"
  done < <(extract_features_from_epic "$EPIC_PATH")

  if [ ${#STORIES[@]} -eq 0 ]; then
    log WARN "Could not extract features from epic for simulation"
  else
    log OK "Simulated ${#STORIES[@]} stories from ${feature_num} features"
  fi
}

# Run a single Claude command with clean context
run_claude() {
  local description="$1"
  local prompt="$2"
  local log_file="$3"

  if [ "$DRY_RUN" = true ]; then
    log INFO "[DRY RUN] Would execute: claude -p \"${prompt:0:80}...\""
    return 0
  fi

  log STEP "$description"
  log INFO "Log: $log_file"

  # Each invocation = fresh context (no --continue)
  # Unset CLAUDECODE to allow launching from within a Claude Code session
  # Redirect stdin from /dev/null to prevent consuming the caller's stdin (e.g., while-read loops)
  if env -u CLAUDECODE claude --dangerously-skip-permissions -p "$prompt" < /dev/null > "$log_file" 2>&1; then
    log OK "$description — completed successfully"
    return 0
  else
    local exit_code=$?
    log ERROR "$description — failed (exit code: $exit_code)"
    log ERROR "Check log: $log_file"
    return $exit_code
  fi
}

# ── Phase: Create Stories ──────────────────────────────────────────────────────
phase_create_stories() {
  if [ -z "$EPIC_PATH" ]; then
    log WARN "No epic path provided, skipping story creation"
    return 0
  fi

  separator "PHASE: Create Stories from Epic"

  local epic_rel
  epic_rel=$(relpath "$EPIC_PATH" "$PROJECT_ROOT")

  log INFO "Epic: $epic_rel"

  # Create stories one per feature using the BMAD create-story workflow
  # Extract story titles and create a story for each
  local feature_num=0
  while IFS= read -r feature_name; do
    [ -z "$feature_name" ] && continue
    ((feature_num++)) || true

    local create_log="${LOG_DIR}/${TIMESTAMP}-create-story-${feature_num}.log"
    run_claude \
      "Creating story for feature ${feature_num}: $feature_name" \
      "/bmad-create-story -- Create the next story from the epic at '${epic_rel}'. Focus on Feature ${feature_num}: ${feature_name}. Read the epic file first, then create the story following BMAD structure." \
      "$create_log"
  done < <(extract_features_from_epic "$EPIC_PATH")

  if [ $feature_num -eq 0 ]; then
    log ERROR "No features found in epic file"
    return 1
  fi

  log OK "Requested creation of $feature_num stories"

  # Re-discover stories after creation (or simulate for dry-run)
  STORIES=()
  discover_stories

  # Also check BMAD default output locations
  if [ ${#STORIES[@]} -eq 0 ]; then
    log INFO "Checking alternative story locations..."

    # Check _bmad-output/implementation-artifacts/ (BMAD default)
    for search_dir in \
      "$PROJECT_ROOT/_bmad-output/implementation-artifacts" \
      "$PROJECT_ROOT/.docs/sprint-artifacts/stories" \
      "$PROJECT_ROOT/docs/stories"; do

      if [ -d "$search_dir" ]; then
        while IFS= read -r -d '' story; do
          local rel_path
          rel_path=$(relpath "$story" "$PROJECT_ROOT")
          STORIES+=("$rel_path")
        done < <(find "$search_dir" -name "*.md" -print0 2>/dev/null | sort -z)
      fi
    done

    if [ ${#STORIES[@]} -gt 0 ]; then
      log OK "Found ${#STORIES[@]} stories in alternative locations"
      for s in "${STORIES[@]}"; do
        echo "  → $s"
      done
    fi
  fi

  # In dry-run mode, stories won't actually exist — simulate from epic features
  if [ "$DRY_RUN" = true ] && [ ${#STORIES[@]} -eq 0 ]; then
    simulate_stories_from_epic
  fi
}

# ── Phase: Develop Story ───────────────────────────────────────────────────────
phase_dev_story() {
  local story_path="$1"
  local story_name
  story_name=$(basename "$story_path" .md)
  local story_dir
  story_dir=$(dirname "$story_path")

  separator "DEV: $story_name"

  if [ "$SKIP_COMPLETED" = true ] && is_story_completed "${PROJECT_ROOT}/$story_path"; then
    log WARN "Skipping (already completed): $story_path"
    return 0
  fi

  # Step 1: Plan the implementation
  local plan_log="${LOG_DIR}/${TIMESTAMP}-plan-${story_name}.log"
  run_claude \
    "Planning: $story_name" \
    "Read the story at '$story_path' and all related documents (epic, feature). Enter plan mode and create a detailed implementation plan. Consider the existing codebase architecture, DDD boundaries, and enterprise standards. Output the plan then exit." \
    "$plan_log" || return 1

  # Step 2: Develop the story
  local dev_log="${LOG_DIR}/${TIMESTAMP}-dev-${story_name}.log"
  run_claude \
    "Developing: $story_name" \
    "/bmad-dev-story $story_path" \
    "$dev_log" || return 1

  log OK "Development complete: $story_name"
}

# ── Phase: Code Review ─────────────────────────────────────────────────────────
phase_review_story() {
  local story_path="$1"
  local story_name
  story_name=$(basename "$story_path" .md)

  separator "REVIEW: $story_name"

  local review_log="${LOG_DIR}/${TIMESTAMP}-review-${story_name}.log"
  run_claude \
    "Code review: $story_name" \
    "/bmad-code-review $story_path" \
    "$review_log" || return 1

  log OK "Code review complete: $story_name"
}

# ── Phase: Git Commit ──────────────────────────────────────────────────────────
phase_commit_story() {
  local story_path="$1"
  local story_name
  story_name=$(basename "$story_path" .md)

  separator "COMMIT: $story_name"

  if [ "$DRY_RUN" = true ]; then
    log INFO "[DRY RUN] Would commit changes for: $story_name"
    return 0
  fi

  cd "$PROJECT_ROOT"

  # Check if there are any changes to commit
  if git diff --quiet && git diff --staged --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
    log WARN "No changes to commit for: $story_name"
    return 0
  fi

  # Extract story title from file (e.g. "# Story B002.1: Fee Configuration")
  local story_title
  story_title=$(grep -m1 '^# Story' "${PROJECT_ROOT}/${story_path}" 2>/dev/null \
    | sed 's/^# Story [A-Z0-9.]*: //' \
    || echo "$story_name")

  # Determine epic number from story path for commit message prefix
  local epic_ref
  epic_ref=$(echo "$story_path" | grep -oE 'EPIC-[0-9]+' | head -1 || echo "EPIC")

  # Stage all tracked and untracked changes (excluding gitignored)
  git add -A

  # Commit with story reference — no co-author per project preference
  git commit -m "$(cat <<EOF
feat(${epic_ref,,}): implement $story_title

Story: $story_path
EOF
)"
  log OK "Committed: $story_name"
}

# ── Parse Arguments ────────────────────────────────────────────────────────────
parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --epic)
        EPIC_PATH="$2"
        shift 2
        ;;
      --stories)
        shift
        while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do
          STORIES+=("$1")
          shift
        done
        ;;
      --phase)
        START_PHASE="$2"
        shift 2
        ;;
      --skip-completed)
        SKIP_COMPLETED=true
        shift
        ;;
      --cooldown)
        COOLDOWN="$2"
        shift 2
        ;;
      --dry-run)
        DRY_RUN=true
        shift
        ;;
      --no-review)
        NO_REVIEW=true
        shift
        ;;
      --log-dir)
        LOG_DIR="$2"
        shift 2
        ;;
      --help|-h)
        usage
        ;;
      *)
        log ERROR "Unknown option: $1"
        usage
        ;;
    esac
  done

  # Validate
  if [ -z "$EPIC_PATH" ] && [ ${#STORIES[@]} -eq 0 ]; then
    log ERROR "Must provide --epic or --stories"
    usage
  fi

  # Resolve epic path to absolute if needed
  if [ -n "$EPIC_PATH" ] && [[ ! "$EPIC_PATH" = /* ]]; then
    EPIC_PATH="${PROJECT_ROOT}/${EPIC_PATH}"
  fi

  if [ -n "$EPIC_PATH" ] && [ ! -f "$EPIC_PATH" ]; then
    log ERROR "Epic file not found: $EPIC_PATH"
    exit 1
  fi
}

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

  # Setup
  mkdir -p "$LOG_DIR"
  check_prerequisites
  cd "$PROJECT_ROOT"

  separator "Epic Development Pipeline"
  log INFO "Timestamp:      $TIMESTAMP"
  log INFO "Project Root:   $PROJECT_ROOT"
  log INFO "Log Directory:  $LOG_DIR"
  log INFO "Start Phase:    $START_PHASE"
  log INFO "Cooldown:       ${COOLDOWN}s"
  log INFO "Dry Run:        $DRY_RUN"
  log INFO "Skip Completed: $SKIP_COMPLETED"
  log INFO "No Review:      $NO_REVIEW"

  if [ -n "$EPIC_PATH" ]; then
    local epic_rel
    epic_rel=$(relpath "$EPIC_PATH" "$PROJECT_ROOT")
    log INFO "Epic:           $epic_rel"
  fi

  echo ""

  # Phase 1: Create stories (if starting from create phase)
  if [ "$START_PHASE" = "create" ] && [ -n "$EPIC_PATH" ]; then
    phase_create_stories

    if [ ${#STORIES[@]} -eq 0 ]; then
      log ERROR "No stories found after creation phase. Aborting."
      exit 1
    fi
  elif [ "$START_PHASE" = "create" ] && [ ${#STORIES[@]} -gt 0 ]; then
    log INFO "Stories provided explicitly, skipping creation phase"
  fi

  # Discover stories if epic provided but starting from dev/review
  if [ ${#STORIES[@]} -eq 0 ] && [ -n "$EPIC_PATH" ]; then
    discover_stories
    if [ ${#STORIES[@]} -eq 0 ]; then
      log ERROR "No stories found to process"
      exit 1
    fi
  fi

  # Summary
  separator "Execution Plan: ${#STORIES[@]} stories"
  for i in "${!STORIES[@]}"; do
    local num=$((i + 1))
    echo -e "  ${BOLD}${num}.${NC} ${STORIES[$i]}"
  done
  echo ""

  # Track results
  local total=${#STORIES[@]}
  local succeeded=0
  local failed=0
  local skipped=0

  # Phase 2 & 3: Dev + Review each story
  for i in "${!STORIES[@]}"; do
    local story="${STORIES[$i]}"
    local num=$((i + 1))

    separator "Story $num/$total: $(basename "$story" .md)"

    # Dev phase
    if [[ "$START_PHASE" = "create" || "$START_PHASE" = "dev" ]]; then
      if phase_dev_story "$story"; then
        :  # continue to review
      else
        log ERROR "Dev failed for: $story"
        ((failed++)) || true
        continue
      fi
    fi

    # Review phase
    if [ "$NO_REVIEW" = false ] && [[ "$START_PHASE" != "review" || true ]]; then
      if phase_review_story "$story"; then
        ((succeeded++)) || true
      else
        log WARN "Review found issues for: $story (counted as succeeded, review logged)"
        ((succeeded++)) || true
      fi
    else
      ((succeeded++)) || true
    fi

    # Commit phase — commit everything produced by dev + review
    phase_commit_story "$story" || log WARN "Commit failed for: $story (continuing)"

    # Cooldown between stories (skip after last)
    if [ $((i + 1)) -lt $total ] && [ "$DRY_RUN" = false ]; then
      log INFO "Cooldown: ${COOLDOWN}s before next story..."
      sleep "$COOLDOWN"
    fi
  done

  # Final summary
  separator "Pipeline Complete"
  echo -e "  ${GREEN}Succeeded:${NC} $succeeded"
  echo -e "  ${RED}Failed:${NC}    $failed"
  echo -e "  ${YELLOW}Skipped:${NC}   $skipped"
  echo -e "  ${BOLD}Total:${NC}     $total"
  echo ""
  log INFO "Full logs: $LOG_DIR/"

  if [ $failed -gt 0 ]; then
    exit 1
  fi
}

main "$@"
