#!/usr/bin/env bash
# =============================================================================
# start-docker.sh — make Docker the ONLY runtime for the Trakop application.
#
#     sudo docker/scripts/start-docker.sh                    # cut over (docker-only)
#     sudo docker/scripts/start-docker.sh --dry-run          # show every action, change nothing
#     sudo docker/scripts/start-docker.sh --keep-host-apache # legacy: move Apache to :8080 instead
#
# AFTER this runs:
#     http://localhost              -> Docker container  (the application)
#     http://127.0.0.1:8082         -> Docker container  (Adminer, the DB client)
#     host apache2 + php7.4-fpm     -> STOPPED and DISABLED (no boot-time start)
#     host MySQL                    -> untouched, still the database (by design)
#
# WHY THE HOST WEB LAYER IS NOW DISABLED, NOT MOVED
#   The first cut-over moved host Apache to :8080 and left it running, because its
#   DocumentRoot (/var/www/html) also served adminer.php, phpmyadmin/, a second
#   cakephp/ checkout, trakoplens/ and info.php — killing it would have taken the
#   database tools with it. That was a transition state, not a destination: two web
#   servers and two PHP runtimes on one box is exactly the drift this migration
#   exists to remove (a config fixed in one is silently unfixed in the other).
#
#   The database tools are now containerised — the `adminer` service, pinned to
#   4.8.1, reaching MySQL over the same bind-mounted socket the app uses — so the
#   only reason to keep host Apache is gone.
#
#   WHAT MOVED INSTEAD OF BEING LOST: /var/www/html/cakephp — the customer-facing
#   storefront (repo mastersoftwaresolutions/cakephp, CakePHP 4.0.9) — is now
#   bind-mounted INTO the cakephp container via WEBSITE_APP_DIR, so it keeps the
#   same URL (/cakephp/code/) and its API_DOMAIN of http://localhost/trakop-web/
#   code/ still resolves. It shares this container deliberately: its paths.php
#   derives the environment from the filesystem path, and its HTTP_ROOT carries no
#   port, so a separate service on another port would break its links.
#
#   WHAT YOU DO LOSE, stated plainly: phpmyadmin/, info.php, test.php and
#   /var/www/html/trakoplens (an empty directory). Nothing in either repository
#   references them, and Adminer replaces the first. --keep-host-apache brings the
#   whole lot back on :8080 if you disagree.
#
# EVERYTHING IS REVERSIBLE
#   apache2/php7.4-fpm are stopped and disabled, never masked or uninstalled, and
#   stop-docker.sh re-enables and starts both. Apache's config files are backed up
#   before any edit. No data is touched, ever.
# =============================================================================
set -uo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 1
REPO_ROOT="$(pwd)"
ENV_FILE="$REPO_ROOT/.env.docker"
HOST_APACHE_PORT=8080
PORTS_CONF=/etc/apache2/ports.conf
VHOST_CONF=/etc/apache2/sites-available/000-default.conf
BAK_SUFFIX=.trakop-pre-docker.bak
# Host units the application no longer needs once Docker owns :80. MySQL is
# NOT in this list and never will be — business data stays on the host instance.
HOST_WEB_UNITS="apache2 php7.4-fpm"

DRY=0
KEEP_HOST_APACHE=0
for a in "$@"; do
    case "$a" in
        --dry-run)          DRY=1 ;;
        --keep-host-apache) KEEP_HOST_APACHE=1 ;;
        *) printf 'unknown argument: %s\n' "$a" >&2
           printf 'usage: %s [--dry-run] [--keep-host-apache]\n' "$0" >&2; exit 2 ;;
    esac
done

G=$'\033[32m'; R=$'\033[31m'; Y=$'\033[33m'; N=$'\033[0m'
say()  { printf '%s\n' "$*"; }
ok()   { printf "  ${G}ok${N}   %s\n" "$*"; }
warn() { printf "  ${Y}warn${N} %s\n" "$*"; }
die()  { printf "  ${R}FAIL${N} %s\n" "$*" >&2; exit 1; }
act()  { if [ "$DRY" = 1 ]; then printf "  [dry-run] %s\n" "$*"; else eval "$@"; fi; }

# `systemctl is-enabled` prints the state AND exits non-zero for a disabled or
# static unit, so `cmd || echo disabled` prints it TWICE. Only an ABSENT unit
# produces empty output, so default on emptiness, never on exit status.
enabled_state() { local s; s="$(systemctl is-enabled "$1" 2>/dev/null)"; printf '%s' "${s:-unknown}"; }
active_state()  { local s; s="$(systemctl is-active  "$1" 2>/dev/null)"; printf '%s' "${s:-unknown}"; }


# --- sudo / docker availability ---------------------------------------------
# Root is needed to ACT, not to look. A dry run must be runnable by anyone.
if [ "$DRY" = 0 ] && [ "$(id -u)" -ne 0 ]; then
    die "run this with sudo — moving Apache's port and stopping redis-server need root:
      sudo $0
  (or preview without root:  $0 --dry-run)"
fi
docker info >/dev/null 2>&1 || die "cannot reach the docker daemon."
dkc() { docker compose --env-file "$ENV_FILE" "$@"; }

[ -f "$ENV_FILE" ] || die ".env.docker not found. Copy .env.docker.example and set PGVECTOR_PASSWORD."

# --- 1. audit ----------------------------------------------------------------
say ""
say "=== 1. AUDIT ==="
say "  listening now:"
ss -ltn 2>/dev/null | awk '$4 ~ /:(80|8080|8081|8400|3306|5433|6379|6380|11434)$/ {print "      "$4}' | sort -u
say "  host services:"
for s in apache2 php7.4-fpm mysql redis-server; do
    printf "      %-14s %s\n" "$s" "$(systemctl is-active "$s" 2>/dev/null)"
done
# Paths host Apache serves that this container does NOT. They are listed before
# anything is stopped, because "what am I about to lose" is the one question this
# script must answer before it acts — on :80 during a first cut-over, on :8080
# once a previous run already moved Apache aside.
# NOTE the probe cannot say WHO answered — after a cut-over a 200 here may already
# be the container serving a mounted path (that is the point of WEBSITE_APP_DIR).
# It answers "what is reachable today", which is what you need before stopping
# anything; `curl -I` and the X-Trakop-Runtime header identify the server.
say "  /var/www/html paths that are not this app (reachable right now):"
for base in "http://127.0.0.1" "http://127.0.0.1:$HOST_APACHE_PORT"; do
    for p in adminer.php phpmyadmin/ cakephp/ trakoplens/ info.php; do
        c="$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$base/$p" 2>/dev/null)"
        [ "$c" = "200" ] && printf "      %-34s (200 now)\n" "$base/$p"
    done
done
if [ "$KEEP_HOST_APACHE" = 0 ]; then
    say "      ^ these stop being served by the HOST. Two of them are replaced:"
    say "        /adminer.php   -> the adminer container on 127.0.0.1:$(grep -E '^ADMINER_PORT=' "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2 | tr -d ' ')"
    webdir="$(grep -E '^WEBSITE_APP_DIR=' "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2 | tr -d ' ')"
    case "$webdir" in
        ''|*no-website-app*)
            say "        /cakephp/code/ -> NOT mounted. Set WEBSITE_APP_DIR in .env.docker to keep" ;;
        *)  say "        /cakephp/code/ -> mounted into the app container from $webdir" ;;
    esac
    say "        the rest (phpmyadmin, info.php, test.php) are not replaced;"
    say "        pass --keep-host-apache to move Apache to :$HOST_APACHE_PORT instead of disabling it."
fi

# --- 2. shut down (or, with --keep-host-apache, move) the host web layer -----
say ""
if [ "$KEEP_HOST_APACHE" = 1 ]; then
say "=== 2. MOVE HOST APACHE :80 -> :$HOST_APACHE_PORT (backed up, reversible) ==="
if [ "$(systemctl is-active apache2 2>/dev/null)" != "active" ]; then
    ok "apache2 not active — nothing to move"
else
    for f in "$PORTS_CONF" "$VHOST_CONF"; do
        if [ ! -f "${f}${BAK_SUFFIX}" ]; then
            act "cp -a '$f' '${f}${BAK_SUFFIX}'"
            ok "backed up $(basename "$f") -> $(basename "$f")${BAK_SUFFIX}"
        else
            ok "backup already exists for $(basename "$f") (left as-is)"
        fi
    done
    # Only the :80 listener and the :80 vhost are rewritten. The ssl_module
    # "Listen 443" lines are indented and are deliberately not matched.
    act "sed -ri 's/^Listen 80$/Listen $HOST_APACHE_PORT/' '$PORTS_CONF'"
    act "sed -ri 's/<VirtualHost \*:80>/<VirtualHost *:$HOST_APACHE_PORT>/' '$VHOST_CONF'"
    act "apache2ctl configtest"
    act "systemctl restart apache2"
    if [ "$DRY" = 0 ]; then
        sleep 2
        c="$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "http://127.0.0.1:$HOST_APACHE_PORT/adminer.php" 2>/dev/null)"
        [ "$c" = "200" ] && ok "host Apache now on :$HOST_APACHE_PORT (adminer.php -> 200)" \
                         || warn "host Apache moved but /adminer.php returned '$c' — check: systemctl status apache2"
        ss -ltn 2>/dev/null | grep -qE ':80\s|:80$' && die ":80 is STILL occupied after moving Apache. Aborting before Docker starts." \
                                                    || ok ":80 is now free"
    fi
fi
else
say "=== 2. STOP + DISABLE THE HOST WEB LAYER (apache2, php7.4-fpm) ==="
# DISABLE as well as stop. A stopped-but-enabled apache2 comes back on the next
# reboot and takes :80 before Docker does, and the only symptom is the app
# suddenly running on the host's PHP 7.4 with none of the container's config —
# the exact silent-divergence failure this migration removes. Never masked:
# `disable` is undone by stop-docker.sh, `mask` needs a second manual step.
for u in $HOST_WEB_UNITS; do
    if ! systemctl list-unit-files "$u.service" >/dev/null 2>&1 \
       || [ -z "$(systemctl list-unit-files --no-legend "$u.service" 2>/dev/null)" ]; then
        ok "$u not installed — nothing to do"
        continue
    fi
    [ "$(systemctl is-active "$u" 2>/dev/null)" = "active" ] && act "systemctl stop '$u'"
    act "systemctl disable '$u' >/dev/null 2>&1"
    if [ "$DRY" = 0 ]; then
        printf "  ok   %s now %s / %s\n" "$u" \
            "$(active_state "$u")" "$(enabled_state "$u")"
    fi
done
# Apache's config is left EXACTLY as it is, deliberately: if a previous run moved
# it to :8080 that edit stays, so --keep-host-apache and stop-docker.sh both find
# the state they expect. Disabling the unit is what makes the port question moot.
if [ "$DRY" = 0 ]; then
    ss -ltn 2>/dev/null | awk '$4 ~ /:80$/' | grep -q . \
        && warn ":80 is still held — if this is the trakop-cakephp container that is correct (it is the app); if it is anything else, Docker cannot bind it" \
        || ok ":80 is free for the container"
fi
fi

# --- 3. free :6379 -----------------------------------------------------------
say ""
say "=== 3. FREE :6379 FOR THE COMPOSE REDIS ==="
if [ "$(systemctl is-active redis-server 2>/dev/null)" = "active" ]; then
    # Unused by the application — nothing referenced it before this migration.
    act "systemctl stop redis-server"
    ok "host redis-server stopped (still enabled; stop-docker.sh restarts it)"
else
    ok "host redis-server not active"
fi

# --- 4. point .env.docker at the canonical ports -----------------------------
say ""
say "=== 4. SET THE ENV FILE TO DOCKER MODE ==="
# The env file must describe the CURRENT mode, or a later plain `up -d` fails on a
# port it cannot bind. The scripts own this value; do not edit it by hand.
if [ "$DRY" = 1 ]; then
    say "  [dry-run] set APACHE_PORT=80 and REDIS_PORT=6379 in .env.docker"
else
    sed -ri 's/^APACHE_PORT=.*/APACHE_PORT=80/' "$ENV_FILE"
    sed -ri 's/^REDIS_PORT=.*/REDIS_PORT=6379/' "$ENV_FILE"
    ok "APACHE_PORT=80  REDIS_PORT=6379"
fi

# --- 4b. the persistent volumes ----------------------------------------------
# Both are declared `external: true` in docker-compose.yml so that `down -v`
# cannot destroy them (see the volumes block for why that matters: a ~25 minute
# embedding rebuild and a 274 MB model download). The cost of external is that
# Compose will NOT create them, so `up` on a fresh server fails with "external
# volume not found". Creating them here — idempotently, never touching one that
# already exists — is what keeps a first-time `start-docker.sh` a single command.
say ""
say "=== 4b. PERSISTENT VOLUMES ==="
for vol in trakop_lens_pgvector_data trakop_lens_ollama_models; do
    if docker volume inspect "$vol" >/dev/null 2>&1; then
        ok "$vol exists (kept — never recreated)"
    elif [ "$DRY" = 1 ]; then
        say "  [dry-run] docker volume create $vol"
    else
        docker volume create "$vol" >/dev/null \
            || die "could not create the $vol volume."
        ok "$vol created (first run: pgvector needs a reindex, ollama re-pulls the model)"
    fi
done

# --- 5. start the stack ------------------------------------------------------
say ""
say "=== 5. START THE COMPOSE STACK (waits for healthy) ==="
if [ "$DRY" = 1 ]; then
    say "  [dry-run] docker compose --env-file .env.docker up -d --wait"
else
    dkc up -d --wait || die "compose did not come up healthy. Inspect: docker compose logs
  To roll everything back: sudo docker/scripts/stop-docker.sh"
    ok "all services healthy"
fi

# --- 6. prove :80 is the container ------------------------------------------
say ""
say "=== 6. PROVE :80 IS DOCKER ==="
if [ "$DRY" = 1 ]; then
    say "  [dry-run] skipped"
else
    hdr="$(curl -sI --max-time 15 "http://127.0.0.1/trakop-web/code/" 2>/dev/null | tr -d '\r')"
    rt="$(printf '%s' "$hdr" | grep -i '^X-Trakop-Runtime:' | awk '{print $2}')"
    cid="$(printf '%s' "$hdr" | grep -i '^X-Trakop-Container:' | awk '{print $2}')"
    if [ "$rt" = "docker" ]; then
        ok "http://localhost -> CONTAINER $cid"
        printf '      (host Apache does not set this header; its presence is positive proof)\n'
    else
        die "http://localhost is NOT the container (no X-Trakop-Runtime). Roll back with stop-docker.sh"
    fi
    printf '      %s\n' "$(printf '%s' "$hdr" | grep -i '^HTTP/')"

    aport="$(grep -E '^ADMINER_PORT=' "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2 | tr -d ' ')"
    aport="${aport:-8082}"
    ac="$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "http://127.0.0.1:$aport/" 2>/dev/null)"
    [ "$ac" = "200" ] && ok "Adminer (container) on 127.0.0.1:$aport" \
                      || warn "Adminer on :$aport returned '$ac' — the DB client replaced /adminer.php, so check: docker compose logs adminer"
    # The socket hop is what actually breaks here, and the login page renders
    # perfectly without it. Assert it the way the container's own probe does.
    if docker exec trakop-adminer bash /usr/local/bin/trakop-adminer-healthcheck >/dev/null 2>&1; then
        ok "Adminer reaches MySQL over the mounted socket (Server field: localhost)"
    else
        warn "Adminer cannot reach MySQL — docker exec trakop-adminer bash /usr/local/bin/trakop-adminer-healthcheck"
    fi

    if [ "$KEEP_HOST_APACHE" = 1 ]; then
        say "  the moved paths still work on :$HOST_APACHE_PORT:"
        for p in adminer.php phpmyadmin/; do
            printf "      :%s/%-14s HTTP %s\n" "$HOST_APACHE_PORT" "$p" \
                "$(curl -s -o /dev/null -w '%{http_code}' --max-time 6 "http://127.0.0.1:$HOST_APACHE_PORT/$p")"
        done
    else
        say "  host web layer (must be inactive/disabled):"
        for u in $HOST_WEB_UNITS; do
            printf "      %-14s %s / %s\n" "$u" \
                "$(active_state "$u")" "$(enabled_state "$u")"
        done
        printf "      %-14s %s / %s   (EXTERNAL by design — never containerised)\n" "mysql" \
            "$(active_state mysql)" "$(enabled_state mysql)"
    fi
fi

# --- 7. verify ---------------------------------------------------------------
say ""
say "=== 7. VERIFY ==="
if [ "$DRY" = 1 ]; then
    say "  [dry-run] skipped"
else
    "$REPO_ROOT/docker/scripts/verify-stack.sh" --no-reindex --no-llm || warn "verify-stack.sh reported failures — see above"
fi

say ""
if [ "$DRY" = 1 ]; then
    say "[dry-run] nothing above was executed. This is what a real run would leave you with:"
fi
if [ "$KEEP_HOST_APACHE" = 1 ]; then
    say "DOCKER MODE active (host Apache KEPT). http://localhost is the container."
    say "Host tools moved to :$HOST_APACHE_PORT (e.g. http://localhost:$HOST_APACHE_PORT/adminer.php)."
else
    aport="$(grep -E '^ADMINER_PORT=' "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2 | tr -d ' ')"
    say "DOCKER-ONLY MODE active. Every runtime for this application is a container."
    say "  application   http://localhost/trakop-web/code/"
    say "  realtime      :$(grep -E '^NODE_PORT=' "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2 | tr -d ' ')  (browser connects directly)"
    say "  database GUI  http://127.0.0.1:${aport:-8082}/    Server: localhost  (the mounted socket)"
    say "  host apache2 + php7.4-fpm are stopped and disabled; MySQL stays on the host by design."
fi
say "Roll back with:  sudo docker/scripts/stop-docker.sh"
