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

# dev.sh — Local development startup script for the Orchard Insights platform.
#
# Starts services in named tmux windows so you can see all logs at once.
# Supports explicit service selection, automatic dependency resolution,
# and health checks.
#
# Compatible with bash 3.2+ (macOS default).
#
# Usage:
#   ./dev.sh ows-analytics graphql-product graphql-router frontend-insights
#   ./dev.sh --stack graphql-product
#   ./dev.sh --stop
#   ./dev.sh --status
#   ./dev.sh --list
#   ./dev.sh --help

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
SESSION_NAME="orchard-dev"

# ANSI color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'

# ---------------------------------------------------------------------------
# Service registry
#
# Format: SERVICE_NAME|TYPE|START_COMMAND|PORT|HEALTH_PATH
# PORT=0 means the service is not a server (batch job).
# HEALTH_PATH is the URL path to poll for readiness.
# ---------------------------------------------------------------------------

SERVICE_REGISTRY=(
    "frontend-insights|react|yarn start|8080|/"
    "orchard-suite|react|pnpm dev|6006|/"
    "graphql-router|rust|make dev|4000|/health"
    "graphql-analytics|node|yarn start|8084|/.well-known/apollo/server-health"
    "graphql-knowledge-search|node|yarn start|8085|/.well-known/apollo/server-health"
    "graphql-knowledge|node|yarn start|8086|/.well-known/apollo/server-health"
    "graphql-product|node|yarn start|8087|/.well-known/apollo/server-health"
    "graphql-user|node|yarn start|8088|/.well-known/apollo/server-health"
    "ows-analytics|python|python dev.py|5001|/health"
    "ows-charts|python|python dev.py|5002|/health"
    "ows-playlist|python|python dev.py|5003|/health"
    "dbt-analytics|dbt|N/A|0|N/A"
)

# ---------------------------------------------------------------------------
# Dependency lookups (bash 3.2 compatible — no associative arrays)
#
# get_upstream: services that SERVICE calls at runtime
# get_downstream: services to also start for end-to-end testing
# ---------------------------------------------------------------------------

get_upstream() {
    case "$1" in
        frontend-insights)       echo "graphql-router" ;;
        graphql-analytics)       echo "ows-analytics ows-charts ows-playlist" ;;
        graphql-product)         echo "ows-analytics" ;;
        graphql-router)          echo "" ;;
        graphql-knowledge-search) echo "" ;;
        graphql-knowledge)       echo "" ;;
        graphql-user)            echo "" ;;
        ows-*)                   echo "" ;;
        orchard-suite)           echo "" ;;
        dbt-analytics)           echo "" ;;
        *)                       echo "" ;;
    esac
}

get_downstream() {
    case "$1" in
        graphql-analytics)       echo "graphql-router frontend-insights" ;;
        graphql-product)         echo "graphql-router frontend-insights" ;;
        graphql-knowledge-search) echo "graphql-router frontend-insights" ;;
        graphql-knowledge)       echo "graphql-router frontend-insights" ;;
        graphql-user)            echo "graphql-router frontend-insights" ;;
        graphql-router)          echo "frontend-insights" ;;
        *)                       echo "" ;;
    esac
}

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

log_info()    { echo -e "${BLUE}[INFO]${RESET}  $*"; }
log_ok()      { echo -e "${GREEN}[ OK ]${RESET}  $*"; }
log_warn()    { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
log_err()     { echo -e "${RED}[ERR]${RESET}  $*"; }
log_header()  { echo -e "\n${BOLD}${CYAN}$*${RESET}\n"; }

# Look up a field from the registry. Fields: 1=type, 2=cmd, 3=port, 4=health
get_field() {
    local service="$1"
    local field_index="$2"
    for entry in "${SERVICE_REGISTRY[@]}"; do
        local name type cmd port health
        IFS='|' read -r name type cmd port health <<< "$entry"
        if [[ "$name" == "$service" ]]; then
            case "$field_index" in
                1) echo "$type"   ;;
                2) echo "$cmd"    ;;
                3) echo "$port"   ;;
                4) echo "$health" ;;
            esac
            return 0
        fi
    done
    return 1
}

service_exists() {
    for entry in "${SERVICE_REGISTRY[@]}"; do
        local name
        IFS='|' read -r name _ <<< "$entry"
        if [[ "$name" == "$1" ]]; then
            return 0
        fi
    done
    return 1
}

# Return a color per service type for visual distinction in output.
color_for_type() {
    case "$1" in
        react)  echo "$MAGENTA" ;;
        rust)   echo "$RED"     ;;
        node)   echo "$CYAN"    ;;
        python) echo "$GREEN"   ;;
        dbt)    echo "$YELLOW"  ;;
        *)      echo "$RESET"   ;;
    esac
}

print_service() {
    local service="$1"
    local stype
    stype="$(get_field "$service" 1)"
    local color
    color="$(color_for_type "$stype")"
    echo -e "  ${color}${BOLD}${service}${RESET} ${DIM}(${stype})${RESET}"
}

# Check if a value is in a space-separated list
list_contains() {
    local list="$1"
    local item="$2"
    for entry in $list; do
        if [[ "$entry" == "$item" ]]; then
            return 0
        fi
    done
    return 1
}

# ---------------------------------------------------------------------------
# Prerequisite checks
# ---------------------------------------------------------------------------

check_prerequisites() {
    log_header "Checking prerequisites"

    local missing=""

    command -v tmux    >/dev/null 2>&1 || missing="$missing tmux"
    command -v node    >/dev/null 2>&1 || missing="$missing node"
    command -v yarn    >/dev/null 2>&1 || missing="$missing yarn"
    command -v pnpm    >/dev/null 2>&1 || missing="$missing pnpm"
    command -v curl    >/dev/null 2>&1 || missing="$missing curl"

    # Accept either python3 or python
    if ! command -v python3 >/dev/null 2>&1 && ! command -v python >/dev/null 2>&1; then
        missing="$missing python3"
    fi

    if [[ -n "$missing" ]]; then
        log_err "Missing required tools:$missing"
        echo ""
        echo "  Install them before continuing:"
        echo "    brew install$missing"
        echo ""
        exit 1
    fi

    log_ok "All prerequisites found"

    # Print versions for debugging
    echo -e "  ${DIM}tmux   $(tmux -V 2>/dev/null || echo 'unknown')${RESET}"
    echo -e "  ${DIM}node   $(node --version 2>/dev/null)${RESET}"
    echo -e "  ${DIM}yarn   $(yarn --version 2>/dev/null)${RESET}"
    echo -e "  ${DIM}pnpm   $(pnpm --version 2>/dev/null)${RESET}"
    local py_cmd="python3"
    command -v python3 >/dev/null 2>&1 || py_cmd="python"
    echo -e "  ${DIM}python $($py_cmd --version 2>/dev/null)${RESET}"
}

# ---------------------------------------------------------------------------
# Dependency resolution (--stack mode)
# ---------------------------------------------------------------------------

resolve_deps() {
    local seed_service="$1"
    local resolved=""
    local queue="$seed_service"

    # Walk upstream dependencies (things seed_service calls)
    while [[ -n "$queue" ]]; do
        # Pop first from queue
        local current="${queue%% *}"
        if [[ "$queue" == "$current" ]]; then
            queue=""
        else
            queue="${queue#* }"
        fi

        # Skip if already resolved
        if list_contains "$resolved" "$current"; then
            continue
        fi
        resolved="$resolved $current"

        local upstream
        upstream="$(get_upstream "$current")"
        for dep in $upstream; do
            if ! list_contains "$resolved" "$dep"; then
                queue="$queue $dep"
            fi
        done
    done

    # Walk downstream dependencies (router, frontend)
    local downstream
    downstream="$(get_downstream "$seed_service")"
    for dep in $downstream; do
        if ! list_contains "$resolved" "$dep"; then
            resolved="$resolved $dep"
        fi
    done

    # Sort into boot order: OWS first, then graphql-*, then router, then frontend
    local ows="" gql="" router="" front="" other=""
    for svc in $resolved; do
        case "$svc" in
            ows-*)              ows="$ows $svc" ;;
            graphql-router)     router="$router $svc" ;;
            graphql-*)          gql="$gql $svc" ;;
            frontend-insights)  front="$front $svc" ;;
            *)                  other="$other $svc" ;;
        esac
    done

    echo "$other $ows $gql $router $front" | xargs
}

# ---------------------------------------------------------------------------
# Start services in tmux
# ---------------------------------------------------------------------------

start_services() {
    local services=("$@")

    if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
        log_warn "tmux session '${SESSION_NAME}' already exists."
        echo "  Use ${BOLD}./dev.sh --stop${RESET} first, or ${BOLD}tmux attach -t ${SESSION_NAME}${RESET} to reconnect."
        exit 1
    fi

    check_prerequisites

    log_header "Starting ${#services[@]} service(s)"
    for svc in "${services[@]}"; do
        print_service "$svc"
    done
    echo ""

    # Validate all service directories exist
    for svc in "${services[@]}"; do
        local svc_dir="${PARENT_DIR}/${svc}"
        if [[ ! -d "$svc_dir" ]]; then
            log_err "Directory not found: ${svc_dir}"
            log_err "Make sure '${svc}' is checked out as a sibling of insights-ai."
            exit 1
        fi
    done

    # Warn about port conflicts
    local seen_ports=""
    local seen_owners=""
    local conflicts=false
    for svc in "${services[@]}"; do
        local port
        port="$(get_field "$svc" 3)"
        if [[ "$port" == "0" ]]; then
            continue
        fi
        # Find who already claimed this port
        local owner=""
        local i=0
        for p in $seen_ports; do
            if [[ "$p" == "$port" ]]; then
                # Get the i-th owner
                local j=0
                for o in $seen_owners; do
                    if [[ $j -eq $i ]]; then
                        owner="$o"
                        break
                    fi
                    j=$((j + 1))
                done
                break
            fi
            i=$((i + 1))
        done
        if [[ -n "$owner" ]]; then
            if [[ "$conflicts" == "false" ]]; then
                log_warn "Port conflicts detected:"
                conflicts=true
            fi
            echo -e "  ${YELLOW}Port ${port}${RESET}: ${svc} conflicts with ${owner}"
        fi
        seen_ports="$seen_ports $port"
        seen_owners="$seen_owners $svc"
    done
    if [[ "$conflicts" == "true" ]]; then
        echo ""
        log_warn "Override ports via .env files (PORT=XXXX) to avoid conflicts."
        echo "  See ${BOLD}./dev.sh --help${RESET} for recommended port assignments."
        echo ""
        read -r -p "  Continue anyway? [y/N] " confirm
        case "$confirm" in
            y|Y|yes|YES) ;;
            *) echo "  Aborted."; exit 1 ;;
        esac
        echo ""
    fi

    # Check .env files exist
    for svc in "${services[@]}"; do
        local svc_dir="${PARENT_DIR}/${svc}"
        if [[ ! -f "${svc_dir}/.env" ]] && [[ -f "${svc_dir}/.env.shadow" ]]; then
            log_warn "${svc}: .env not found. Copying from .env.shadow"
            cp "${svc_dir}/.env.shadow" "${svc_dir}/.env"
        fi
    done

    # Verify .env PORT matches expected port from registry
    local mismatched_svcs=""
    local mismatched_info=""
    for svc in "${services[@]}"; do
        local expected_port
        expected_port="$(get_field "$svc" 3)"
        if [[ "$expected_port" == "0" ]]; then
            continue
        fi
        local svc_dir="${PARENT_DIR}/${svc}"
        local env_file="${svc_dir}/.env"
        if [[ -f "$env_file" ]]; then
            local env_port
            env_port="$(grep -E '^\s*PORT\s*=' "$env_file" 2>/dev/null | tail -1 | sed 's/.*=\s*//; s/[" ]//g; s/#.*//' || true)"
            if [[ -n "$env_port" && "$env_port" != "$expected_port" ]]; then
                mismatched_svcs="$mismatched_svcs $svc"
                mismatched_info="$mismatched_info|$svc:$env_port:$expected_port"
            fi
        fi
    done
    if [[ -n "$mismatched_svcs" ]]; then
        log_warn "Port mismatches between .env and dev.sh registry:"
        for entry in $mismatched_svcs; do
            local env_port expected_port
            for info in $(echo "$mismatched_info" | tr '|' ' '); do
                local info_svc="${info%%:*}"
                if [[ "$info_svc" == "$entry" ]]; then
                    local rest="${info#*:}"
                    env_port="${rest%%:*}"
                    expected_port="${rest#*:}"
                    break
                fi
            done
            echo -e "  ${YELLOW}${entry}${RESET}: .env has PORT=${env_port}, dev.sh expects ${expected_port}"
        done
        echo ""
        read -r -p "  Fix .env files to match? [Y/n] " confirm
        case "$confirm" in
            n|N|no|NO)
                log_info "Skipped. Health checks may not work correctly."
                echo ""
                ;;
            *)
                for entry in $mismatched_svcs; do
                    for info in $(echo "$mismatched_info" | tr '|' ' '); do
                        local info_svc="${info%%:*}"
                        if [[ "$info_svc" == "$entry" ]]; then
                            local rest="${info#*:}"
                            local old_port="${rest%%:*}"
                            local new_port="${rest#*:}"
                            local env_file="${PARENT_DIR}/${entry}/.env"
                            # Replace the PORT= line
                            if [[ "$(uname)" == "Darwin" ]]; then
                                sed -i '' "s/^\([[:space:]]*PORT[[:space:]]*=[[:space:]]*\)${old_port}/\1${new_port}/" "$env_file"
                            else
                                sed -i "s/^\([[:space:]]*PORT[[:space:]]*=[[:space:]]*\)${old_port}/\1${new_port}/" "$env_file"
                            fi
                            log_ok "${entry}: PORT=${old_port} -> ${new_port}"
                            break
                        fi
                    done
                done
                echo ""
                ;;
        esac
    fi

    # Create tmux session with the first service
    local first="${services[0]}"
    local first_cmd
    first_cmd="$(get_field "$first" 2)"
    local first_dir="${PARENT_DIR}/${first}"

    tmux new-session -d -s "$SESSION_NAME" -n "$first" -c "$first_dir"
    tmux send-keys -t "${SESSION_NAME}:${first}" "$first_cmd" C-m

    # Create additional windows for remaining services
    local i=1
    while [[ $i -lt ${#services[@]} ]]; do
        local svc="${services[$i]}"
        local cmd
        cmd="$(get_field "$svc" 2)"
        local svc_dir="${PARENT_DIR}/${svc}"

        tmux new-window -t "$SESSION_NAME" -n "$svc" -c "$svc_dir"
        tmux send-keys -t "${SESSION_NAME}:${svc}" "$cmd" C-m
        i=$((i + 1))
    done

    # Select the first window
    tmux select-window -t "${SESSION_NAME}:${first}"

    log_ok "All services launched in tmux session '${SESSION_NAME}'"
    echo ""

    # Run health checks
    run_health_checks "${services[@]}"

    echo ""
    log_info "Attach to the session:"
    echo -e "  ${BOLD}tmux attach -t ${SESSION_NAME}${RESET}"
    echo ""
    log_info "Navigate windows: ${BOLD}Ctrl-b n${RESET} (next) / ${BOLD}Ctrl-b p${RESET} (prev) / ${BOLD}Ctrl-b w${RESET} (list)"
}

# ---------------------------------------------------------------------------
# Health checks
# ---------------------------------------------------------------------------

run_health_checks() {
    local services=("$@")
    local max_wait=120   # seconds
    local interval=3     # seconds between polls

    log_header "Health checks (timeout: ${max_wait}s)"

    local pending=""
    for svc in "${services[@]}"; do
        local port
        port="$(get_field "$svc" 3)"
        if [[ "$port" == "0" ]]; then
            log_ok "${svc} ${DIM}(batch service, no health check)${RESET}"
            continue
        fi
        pending="$pending $svc"
    done
    pending="$(echo "$pending" | xargs)"

    if [[ -z "$pending" ]]; then
        return 0
    fi

    local elapsed=0
    while [[ -n "$pending" && $elapsed -lt $max_wait ]]; do
        local still_pending=""
        for svc in $pending; do
            local port health_path
            port="$(get_field "$svc" 3)"
            health_path="$(get_field "$svc" 4)"
            local url="http://localhost:${port}${health_path}"

            if curl -sf --max-time 2 "$url" >/dev/null 2>&1; then
                log_ok "${svc} ${DIM}(port ${port})${RESET}"
            else
                still_pending="$still_pending $svc"
            fi
        done
        pending="$(echo "$still_pending" | xargs)"

        if [[ -n "$pending" ]]; then
            sleep "$interval"
            elapsed=$((elapsed + interval))
            echo -ne "${DIM}.${RESET}"
        fi
    done

    # Clear the dot-progress line
    [[ $elapsed -gt 0 ]] && echo ""

    if [[ -n "$pending" ]]; then
        echo ""
        log_warn "The following services did not respond within ${max_wait}s:"
        for svc in $pending; do
            local port
            port="$(get_field "$svc" 3)"
            echo -e "  ${YELLOW}${svc}${RESET} ${DIM}(expected on port ${port})${RESET}"
        done
        echo ""
        log_info "They may still be starting. Check tmux logs:"
        echo -e "  ${BOLD}tmux attach -t ${SESSION_NAME}${RESET}"
    fi
}

# ---------------------------------------------------------------------------
# Stop all services
# ---------------------------------------------------------------------------

stop_services() {
    log_header "Stopping Orchard dev environment"

    if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
        tmux kill-session -t "$SESSION_NAME"
        log_ok "tmux session '${SESSION_NAME}' destroyed"
    else
        log_info "No active tmux session '${SESSION_NAME}' found"
    fi
}

# ---------------------------------------------------------------------------
# Status
# ---------------------------------------------------------------------------

show_status() {
    log_header "Orchard Insights — Service Status"

    # Check if session exists
    if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
        log_info "No active tmux session '${SESSION_NAME}'"
        echo ""
        return
    fi

    # List windows in the session
    local windows
    windows="$(tmux list-windows -t "$SESSION_NAME" -F '#{window_name}' 2>/dev/null)"

    printf "  ${BOLD}%-30s %-7s %s${RESET}\n" "SERVICE" "PORT" "STATUS"
    echo -e "  $(printf '%.0s-' {1..55})"

    while IFS= read -r svc; do
        if ! service_exists "$svc"; then
            continue
        fi

        local port stype
        port="$(get_field "$svc" 3)"
        stype="$(get_field "$svc" 1)"
        local color
        color="$(color_for_type "$stype")"

        local status_label="${YELLOW}starting${RESET}"
        if [[ "$port" == "0" ]]; then
            status_label="${DIM}batch${RESET}"
        else
            local health_path
            health_path="$(get_field "$svc" 4)"
            if curl -sf --max-time 1 "http://localhost:${port}${health_path}" >/dev/null 2>&1; then
                status_label="${GREEN}running${RESET}"
            elif lsof -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
                status_label="${YELLOW}listening${RESET}"
            else
                status_label="${RED}down${RESET}"
            fi
        fi

        printf "  ${color}%-30s${RESET} %-7s %b\n" "$svc" "${port:-N/A}" "$status_label"
    done <<< "$windows"

    echo ""
    log_info "Attach: ${BOLD}tmux attach -t ${SESSION_NAME}${RESET}"
}

# ---------------------------------------------------------------------------
# List all available services
# ---------------------------------------------------------------------------

list_services() {
    log_header "Available services"

    printf "  ${BOLD}%-30s %-9s %-6s %s${RESET}\n" "SERVICE" "TYPE" "PORT" "START COMMAND"
    echo -e "  $(printf '%.0s-' {1..70})"

    for entry in "${SERVICE_REGISTRY[@]}"; do
        local name stype cmd port
        IFS='|' read -r name stype cmd port _ <<< "$entry"
        local color
        color="$(color_for_type "$stype")"
        printf "  ${color}%-30s${RESET} %-9s %-6s %s\n" "$name" "$stype" "${port}" "${cmd}"
    done

    echo ""
    log_info "Start specific: ${BOLD}./dev.sh service1 service2 ...${RESET}"
    log_info "Auto-resolve:   ${BOLD}./dev.sh --stack service-name${RESET}"
}

# ---------------------------------------------------------------------------
# Usage
# ---------------------------------------------------------------------------

usage() {
    cat << 'HELPEOF'

Orchard Insights — Local Dev Startup

USAGE
  ./dev.sh <service> [<service> ...]     Start specific services
  ./dev.sh --stack <service>             Start service + auto-resolved deps
  ./dev.sh --stop                        Stop all running services
  ./dev.sh --status                      Show status of running services
  ./dev.sh --list                        List all available services
  ./dev.sh --help                        Show this help

EXAMPLES
  # Start a typical feature development stack:
  ./dev.sh ows-analytics graphql-product graphql-router frontend-insights

  # Auto-resolve the same stack from a single service:
  ./dev.sh --stack graphql-product

  # Work on search:
  ./dev.sh --stack graphql-knowledge-search

  # Start only the OWS layer:
  ./dev.sh ows-analytics ows-charts ows-playlist

  # Check what is running:
  ./dev.sh --status

  # Tear everything down:
  ./dev.sh --stop

DEPENDENCY RESOLUTION (--stack)
  --stack walks the dependency graph in both directions:
    Upstream:   services that the target calls (e.g., OWS for graphql-product)
    Downstream: router + frontend (so you can test end-to-end)

  Example: --stack graphql-product resolves to:
    ows-analytics -> graphql-product -> graphql-router -> frontend-insights

TMUX NAVIGATION
  Ctrl-b n     Next window
  Ctrl-b p     Previous window
  Ctrl-b w     List all windows
  Ctrl-b d     Detach (services keep running)
  Ctrl-b &     Kill current window

PORT CONFLICTS
  Services that share the same default port (e.g., multiple graphql-* on 8080)
  will conflict if started simultaneously. Override in each service's .env:

  Recommended port assignments for local development:
    graphql-analytics          8084
    graphql-knowledge-search   8085
    graphql-knowledge          8086
    graphql-product            8087
    graphql-user               8088
    graphql-router             8080
    ows-analytics              5001
    ows-charts                 5002
    ows-playlist               5003
    frontend-insights          3000
    orchard-suite              6006

HELPEOF
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

main() {
    if [[ $# -eq 0 ]]; then
        usage
        exit 0
    fi

    case "$1" in
        --help|-h)
            usage
            exit 0
            ;;
        --stop)
            stop_services
            exit 0
            ;;
        --status)
            show_status
            exit 0
            ;;
        --list)
            list_services
            exit 0
            ;;
        --stack)
            if [[ $# -lt 2 ]]; then
                log_err "--stack requires a service name"
                echo "  Usage: ./dev.sh --stack <service>"
                exit 1
            fi
            local seed="$2"
            if ! service_exists "$seed"; then
                log_err "Unknown service: ${seed}"
                echo -e "  Run ${BOLD}./dev.sh --list${RESET} to see available services."
                exit 1
            fi

            log_info "Resolving dependency stack for ${BOLD}${seed}${RESET}"
            local resolved
            resolved="$(resolve_deps "$seed")"

            log_info "Resolved services (boot order):"
            for svc in $resolved; do
                print_service "$svc"
            done
            echo ""

            # shellcheck disable=SC2086
            start_services $resolved
            ;;
        --*)
            log_err "Unknown flag: $1"
            usage
            exit 1
            ;;
        *)
            # Validate all service names
            for svc in "$@"; do
                if ! service_exists "$svc"; then
                    log_err "Unknown service: ${svc}"
                    echo -e "  Run ${BOLD}./dev.sh --list${RESET} to see available services."
                    exit 1
                fi
            done
            start_services "$@"
            ;;
    esac
}

main "$@"
