#!/bin/bash
# deploy.sh — Git-based deploy for MKL
#
# Deploys by pulling from git on production, NOT by scp.
# This ensures production always matches committed code and prevents
# features from being silently overwritten by parallel sessions.
#
# Usage:
#   ./scripts/deploy.sh              # Deploy (git pull + restart changed services)
#   ./scripts/deploy.sh --core       # Deploy and restart only mkl-core
#   ./scripts/deploy.sh --products   # Deploy and restart only mkl-products
#   ./scripts/deploy.sh --all        # Deploy and restart both services
#   ./scripts/deploy.sh --status     # Show production vs git status (no deploy)
#
# Prerequisites:
#   - All changes committed and merged to main
#   - main pushed to origin
#   - SSH alias 'mkl' configured

set -euo pipefail

REMOTE="mkl"
REMOTE_DIR="/var/www/html/mkl"

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

# ── Parse args ──────────────────────────────────────────
RESTART_CORE=false
RESTART_PRODUCTS=false
STATUS_ONLY=false

for arg in "$@"; do
    case "$arg" in
        --core) RESTART_CORE=true ;;
        --products) RESTART_PRODUCTS=true ;;
        --all) RESTART_CORE=true; RESTART_PRODUCTS=true ;;
        --status) STATUS_ONLY=true ;;
        *) echo -e "${RED}Unknown arg: $arg${NC}"; exit 1 ;;
    esac
done

# If no restart flag specified, auto-detect from recent commits
AUTO_DETECT=false
if [ "$RESTART_CORE" = false ] && [ "$RESTART_PRODUCTS" = false ] && [ "$STATUS_ONLY" = false ]; then
    AUTO_DETECT=true
fi

echo -e "\n${CYAN}═══ MKL Git Deploy ═══${NC}\n"

# ── Status mode ─────────────────────────────────────────
if [ "$STATUS_ONLY" = true ]; then
    echo -e "${CYAN}Checking production vs origin/main...${NC}"
    ssh "$REMOTE" "cd $REMOTE_DIR && git fetch origin --quiet 2>/dev/null && echo 'Production HEAD:' && git log --oneline -1 HEAD && echo 'Origin/main:' && git log --oneline -1 origin/main && echo '---' && git diff origin/main --stat -- core/ products/ orders/ customer/ public/ 2>/dev/null | tail -10"
    exit 0
fi

# ── Pre-flight checks ──────────────────────────────────
DEPLOYER=$(git config user.name 2>/dev/null || whoami)
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
LOCAL_SHA=$(git rev-parse HEAD 2>/dev/null)

echo -e "Deployer:  ${GREEN}$DEPLOYER${NC}"
echo -e "Branch:    ${GREEN}$BRANCH${NC}"
echo -e "Commit:    ${GREEN}$(git log --oneline -1 HEAD)${NC}\n"

# Check for uncommitted changes
# Trailing `|| true` because grep -v returns 1 when it filters everything out
# (clean tree or only untracked files), and `set -e -o pipefail` would kill
# the script on that non-zero.
DIRTY=$(git status --porcelain 2>/dev/null | grep -v '^??' | head -5 || true)
if [ -n "$DIRTY" ]; then
    echo -e "${RED}ERROR: You have uncommitted changes:${NC}"
    echo "$DIRTY"
    echo -e "\n${RED}Commit your changes before deploying.${NC}"
    exit 1
fi

# Check we're on main or merged to main
MAIN_SHA=$(git rev-parse main 2>/dev/null || echo "none")
if [ "$BRANCH" != "main" ]; then
    # Check if current HEAD is an ancestor of main (i.e., already merged)
    if git merge-base --is-ancestor HEAD main 2>/dev/null; then
        echo -e "${GREEN}Branch merged to main ✓${NC}"
    else
        echo -e "${RED}ERROR: Current branch ($BRANCH) is not merged to main.${NC}"
        echo -e "${RED}Merge to main first, then deploy.${NC}"
        exit 1
    fi
fi

# Check main is pushed to origin
git fetch origin main --quiet 2>/dev/null || true
ORIGIN_MAIN=$(git rev-parse origin/main 2>/dev/null || echo "none")
if [ "$MAIN_SHA" != "$ORIGIN_MAIN" ]; then
    AHEAD=$(git rev-list origin/main..main --count 2>/dev/null || echo "?")
    echo -e "${RED}ERROR: main is $AHEAD commit(s) ahead of origin/main.${NC}"
    echo -e "${RED}Run 'git push origin main' first.${NC}"
    exit 1
fi
echo -e "${GREEN}main is pushed to origin ✓${NC}"

# ── Auto-detect which services changed ──────────────────
if [ "$AUTO_DETECT" = true ]; then
    PROD_SHA=$(ssh "$REMOTE" "cd $REMOTE_DIR && git rev-parse HEAD 2>/dev/null" 2>/dev/null || echo "unknown")

    if [ "$PROD_SHA" = "$ORIGIN_MAIN" ]; then
        echo -e "\n${GREEN}Production is already at origin/main — nothing to deploy.${NC}\n"
        exit 0
    fi

    # Check which directories changed between production and origin/main.
    # Fetch first — prod's origin/main ref is stale between deploys, so an
    # unfetched diff returns empty and the auto-detect falsely reports "nothing
    # to deploy" when there are actually unshipped commits.
    CHANGED=$(ssh "$REMOTE" "cd $REMOTE_DIR && git fetch origin main --quiet 2>/dev/null && git diff HEAD..origin/main --name-only 2>/dev/null" 2>/dev/null || echo "")

    if echo "$CHANGED" | grep -q "^core/"; then
        RESTART_CORE=true
        echo -e "${CYAN}Core files changed — will restart mkl-core${NC}"
    fi
    if echo "$CHANGED" | grep -q "^products/"; then
        RESTART_PRODUCTS=true
        echo -e "${CYAN}Products files changed — will restart mkl-products${NC}"
    fi
    if echo "$CHANGED" | grep -qE "^(orders/|public/)"; then
        echo -e "${CYAN}Static files changed (orders/public) — no restart needed${NC}"
    fi

    # Cross-process require dependencies. Some core/ routes (e.g. cron
    # handlers in core/routes/warehouse-routes.js) require files from
    # products/services or scripts/, and vice versa. When such a shared
    # file changes, the running service has the stale module in its
    # require cache — restart is needed even though the changed file
    # isn't in that service's primary tree.
    #
    # Walks 2 levels of the require graph because chains like
    #   core/routes/warehouse-routes.js
    #     → scripts/po-email-sync.js
    #       → products/services/po-email-summarizer.js
    # are common (cron handler in core requires a script that requires
    # the products module). Pure-direct grep would miss those.

    require_pattern() {
        # $1: basename without .js. Returns regex matching require('...basename')
        echo "require\\([^)]*['\"][^'\"]*${1}['\"]"
    }

    # Skip basenames so generic their substring matches everywhere
    is_generic_basename() {
        case "$1" in
            index|config|helpers|utils|connection|server|constants) return 0 ;;
            *) return 1 ;;
        esac
    }

    # Returns 0 if $tree_dir contains any file that requires $base directly
    tree_requires_directly() {
        local base=$1 tree_dir=$2
        grep -rl -E "$(require_pattern "$base")" "$tree_dir/" --include='*.js' 2>/dev/null | grep -q .
    }

    SHARED_CHANGES=$(echo "$CHANGED" | grep -E "^(products/services/|products/lib/|products/vendors/|scripts/|core/lib/|core/routes/)" | grep '\.js$' || true)
    for f in $SHARED_CHANGES; do
        base=$(basename "$f" .js)
        is_generic_basename "$base" && continue

        for tree in core products; do
            tree_var="RESTART_$(echo "$tree" | tr 'a-z' 'A-Z')"
            # bash indirection: ${!tree_var} reads the dynamic variable name
            [ "${!tree_var}" = true ] && continue

            # Depth 1: tree directly requires the changed file
            if tree_requires_directly "$base" "$tree"; then
                eval "$tree_var=true"
                echo -e "${CYAN}Cross-dep: $f required by $tree/ — adding mkl-$tree restart${NC}"
                continue
            fi

            # Depth 2: find any file (anywhere) that requires the changed file,
            # then check if the tree requires that intermediate.
            INTERMEDIATES=$(grep -rl -E "$(require_pattern "$base")" --include='*.js' 2>/dev/null || true)
            for inter in $INTERMEDIATES; do
                inter_base=$(basename "$inter" .js)
                is_generic_basename "$inter_base" && continue
                # Skip self
                [ "$inter_base" = "$base" ] && continue
                if tree_requires_directly "$inter_base" "$tree"; then
                    eval "$tree_var=true"
                    echo -e "${CYAN}Cross-dep: $f → $inter → $tree/ — adding mkl-$tree restart${NC}"
                    break
                fi
            done
        done
    done

    if [ -z "$CHANGED" ]; then
        echo -e "\n${GREEN}No file changes detected — nothing to deploy.${NC}\n"
        exit 0
    fi
fi

# ── Show what will change ───────────────────────────────
echo ""
echo -e "${CYAN}Changes to deploy:${NC}"
ssh "$REMOTE" "cd $REMOTE_DIR && git fetch origin --quiet 2>/dev/null && git log HEAD..origin/main --oneline 2>/dev/null" 2>/dev/null || true
echo ""

SERVICES=""
[ "$RESTART_CORE" = true ] && SERVICES="$SERVICES mkl-core"
[ "$RESTART_PRODUCTS" = true ] && SERVICES="$SERVICES mkl-products"
if [ -n "$SERVICES" ]; then
    echo -e "Services to restart: ${YELLOW}$SERVICES${NC}"
fi

# ── Deploy via git pull ─────────────────────────────────
echo ""
echo -e "${CYAN}Pulling origin/main on production...${NC}"
ssh "$REMOTE" "cd $REMOTE_DIR && git pull origin main --ff-only 2>&1" || {
    echo -e "${RED}ERROR: git pull failed on production.${NC}"
    echo -e "${RED}Production may have uncommitted changes. Run:${NC}"
    echo -e "${RED}  ssh mkl 'cd $REMOTE_DIR && git status'${NC}"
    exit 1
}
echo -e "${GREEN}Pull complete ✓${NC}"

# ── Restart services ────────────────────────────────────
if [ "$RESTART_CORE" = true ]; then
    echo -e "${CYAN}Restarting mkl-core...${NC}"
    ssh "$REMOTE" "pm2 restart mkl-core 2>&1" || true
    echo -e "${GREEN}mkl-core restarted ✓${NC}"
fi
if [ "$RESTART_PRODUCTS" = true ]; then
    echo -e "${CYAN}Restarting mkl-products...${NC}"
    ssh "$REMOTE" "pm2 restart mkl-products 2>&1" || true
    echo -e "${GREEN}mkl-products restarted ✓${NC}"
fi

# ── Update deploy SHA for orders ────────────────────────
DEPLOY_SHA=$(git rev-parse main 2>/dev/null || echo "unknown")
ssh "$REMOTE" "echo '$DEPLOY_SHA' > $REMOTE_DIR/orders/.deploy-sha" 2>/dev/null || true

# ── Slack notification ──────────────────────────────────
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S %Z')
COMMIT_MSG=$(git log --oneline -1 main 2>/dev/null | cut -c9-)
SLACK_TEXT="*MKL Deploy — Git Pull*\n*Who:* $DEPLOYER\n*Commit:* \`$(git rev-parse --short main)\` $COMMIT_MSG\n*Services:*${SERVICES:-" (static only)"}\n*Time:* $TIMESTAMP"

ssh -o ConnectTimeout=5 "$REMOTE" "
    WEBHOOK=\$(grep '^SLACK_DEPLOY_WEBHOOK=' $REMOTE_DIR/.env 2>/dev/null | cut -d'=' -f2- | tr -d '\"\\r')
    if [ -n \"\$WEBHOOK\" ]; then
        curl -s -X POST \"\$WEBHOOK\" \
            -H 'Content-Type: application/json' \
            -d '{\"text\": \"$SLACK_TEXT\"}' > /dev/null 2>&1
    fi
" 2>/dev/null || true

echo -e "\n${GREEN}═══ Deploy complete ═══${NC}\n"
