#!/usr/bin/env bash
#
# tools/pgdata-adopt.sh — o cluster do PostgreSQL está no bind `${DATA_PATH}/pgdata`?
#
# O `postgres:18` declara `VOLUME /var/lib/postgresql`. Um bind em `/var/lib/postgresql/data`
# (layout antigo) é **mascarado** por esse volume no Docker: o cluster real fica num volume
# anônimo, fora do `DATA_PATH` (ADR-006/007). O compose atual monta o bind em
# `/var/lib/postgresql` e usa `PGDATA=/var/lib/postgresql/pgdata`; este helper detecta uma
# instalação antiga e copia o cluster para o bind (cópia de arquivos com o postgres parado —
# sem pg_dump/restore e sem depender do nome do projeto).
#
# Uso:
#   tools/pgdata-adopt.sh --project <nome> [--data-path <dir>] --check
#       0  = nada a fazer (bind ativo, layout novo ou sem containers)
#       10 = migrar (cluster no volume anônimo; bind do host vazio)
#       11 = ambíguo (o bind do host já tem um cluster) — não copia
#       12 = cluster inacessível (bind movido/substituído em runtime) — use o pg_dump
#   tools/pgdata-adopt.sh --project <nome> [--data-path <dir>] --copy
#       para o postgres da stack e copia o volume para o bind; não sobe de novo (o chamador
#       faz `down`/`up` logo em seguida).
#
# A stack precisa ter containers (mesmo parados): `--check`/`--copy` sobem só o postgres se
# necessário. Sem containers não há o que adotar (instalação nova).
set -Eeuo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

PROJECT=""
DATA_PATH_ARG=""
MODE=""

usage() {
    sed -n '2,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

while [ $# -gt 0 ]; do
    case "$1" in
        --project) shift; PROJECT="${1:-}" ;;
        --project=*) PROJECT="${1#*=}" ;;
        --data-path) shift; DATA_PATH_ARG="${1:-}" ;;
        --data-path=*) DATA_PATH_ARG="${1#*=}" ;;
        --check) MODE=check ;;
        --copy) MODE=copy ;;
        -h | --help) usage; exit 0 ;;
        *) echo "pgdata-adopt: flag desconhecida: $1" >&2; usage >&2; exit 2 ;;
    esac
    shift
done

[ -n "$PROJECT" ] || { echo "pgdata-adopt: --project é obrigatório." >&2; exit 2; }
[ -n "$MODE" ] || { echo "pgdata-adopt: use --check ou --copy." >&2; exit 2; }
[ -f "$ROOT/.env" ] || { echo "pgdata-adopt: '.env' ausente." >&2; exit 2; }

env_value() {
    grep -E "^$1=" "$ROOT/.env" | head -1 | cut -d= -f2- \
        | sed -E 's/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/' || true
}

DATA_PATH_V="${DATA_PATH_ARG:-$(env_value DATA_PATH)}"
DATA_PATH_V="${DATA_PATH_V:-./s3-local}"
BIND="$DATA_PATH_V/pgdata"

# shellcheck source=tools/runtime.sh
. "$ROOT/tools/runtime.sh"
COMPOSE=("${ENGINE[@]}" compose -p "$PROJECT" --env-file .env "${COMPOSE_FILES[@]}")

container() {
    "${COMPOSE[@]}" ps -aq postgres 2>/dev/null | head -1
}

# O postgres precisa estar rodando para `exec`/`stat`; `start` (e não `up -d`) não reescreve
# a configuração do container antigo (um `up -d` com o compose novo trocaria o mount antes
# da cópia e órfã o volume anônimo).
ensure_postgres() {
    local cid="$1" status waited=0
    [ "$("${ENGINE[@]}" inspect -f '{{.State.Running}}' "$cid" 2>/dev/null)" = "true" ] && return 0
    "${COMPOSE[@]}" start postgres >/dev/null 2>&1 || return 1
    while [ "$waited" -lt 120 ]; do
        status="$("${ENGINE[@]}" inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid" 2>/dev/null || echo unknown)"
        [ "$status" = "healthy" ] && return 0
        sleep 2
        waited=$((waited + 2))
    done
    return 1
}

# Montagem do caminho que o VOLUME da imagem ocupa (layout novo) e do caminho antigo.
mount_field() { # <cid> <destino> <campo>
    "${ENGINE[@]}" inspect -f \
        "{{range .Mounts}}{{if eq .Destination \"$2\"}}{{.$3}}{{end}}{{end}}" "$1" 2>/dev/null || true
}

# O bind do host já tem um cluster? (não sobrescrever dados sem decisão do operador)
host_has_cluster() {
    [ -d "$BIND" ] || return 1
    "${ENGINE[@]}" run --rm --network none \
        -v "$BIND:/v" --entrypoint sh "$IMAGE" \
        -c 'test -d /v/pgdata && [ -n "$(ls -A /v/pgdata)" ]' >/dev/null 2>&1
}

# O cluster está no volume do container? O PGDATA do container é relativo à raiz do volume
# (`/var/lib/postgresql`). Um bind que foi movido/substituído em runtime deixa o cluster
# inacessível pelo host (nem no bind atual, nem no volume): isso não é migrável por cópia.
volume_has_cluster() {
    local volume="$1" src="$2"
    "${ENGINE[@]}" run --rm --network none \
        -v "$volume:/from:ro" --entrypoint sh "$IMAGE" \
        -c "test -f '/from/$src/PG_VERSION'" >/dev/null 2>&1
}

# Decide o estado de uma stack existente. Saída: 0 nada a fazer, 10 migrar, 11 ambíguo,
# 12 cluster inacessível (bind substituído em runtime — use o pg_dump).
classify() {
    local cid="$1" type_new dest_old host_inode ctr_inode volume src
    type_new="$(mount_field "$cid" /var/lib/postgresql Type)"
    case "$type_new" in
        bind) return 0 ;; # layout novo: o bind cobre o VOLUME da imagem
    esac

    # Layout antigo: bind em /var/lib/postgresql/data. Se ele está ativo, o cluster já está
    # no host (o novo mount enxerga o mesmo `${DATA_PATH}/pgdata/pgdata`).
    dest_old="$(mount_field "$cid" /var/lib/postgresql/data Destination)"
    if [ -n "$dest_old" ]; then
        host_inode="$(stat -c '%d:%i' "$BIND" 2>/dev/null || true)"
        ctr_inode="$("${ENGINE[@]}" exec "$cid" stat -c '%d:%i' /var/lib/postgresql/data 2>/dev/null || true)"
        if [ -n "$host_inode" ] && [ "$host_inode" = "$ctr_inode" ]; then
            return 0
        fi
    fi

    # O container usa um volume para o PGDATA: ele tem o cluster? Se não, o bind foi
    # movido/substituído com a stack rodando (o dado só existe no namespace do container).
    if [ "$type_new" = "volume" ]; then
        volume="$(mount_field "$cid" /var/lib/postgresql Name)"
        src="$(pgdata_rel "$cid")"
        volume_has_cluster "$volume" "$src" || return 12
    fi

    host_has_cluster && return 11
    return 10
}

# PGDATA do container, relativo à raiz do volume (`/var/lib/postgresql`).
pgdata_rel() {
    local pgdata
    pgdata="$("${ENGINE[@]}" inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$1" | sed -n 's/^PGDATA=//p' | head -1)"
    pgdata="${pgdata:-/var/lib/postgresql/data/pgdata}"
    printf '%s\n' "${pgdata#/var/lib/postgresql/}"
}

CID="$(container)"
[ -n "$CID" ] || exit 0

IMAGE="$("${ENGINE[@]}" inspect -f '{{.Config.Image}}' "$CID" 2>/dev/null || true)"
[ -n "$IMAGE" ] || { echo "pgdata-adopt: não resolvi a imagem do postgres." >&2; exit 1; }

case "$MODE" in
    check)
        ensure_postgres "$CID" || { echo "pgdata-adopt: postgres não subiu para inspeção." >&2; exit 1; }
        classify "$CID"
        ;;
    copy)
        TYPE_NEW="$(mount_field "$CID" /var/lib/postgresql Type)"
        if [ "$TYPE_NEW" = "bind" ]; then
            exit 0
        fi
        VOLUME="$(mount_field "$CID" /var/lib/postgresql Name)"
        [ -n "$VOLUME" ] || { echo "pgdata-adopt: volume do PGDATA não encontrado." >&2; exit 1; }
        host_has_cluster && { echo "pgdata-adopt: '$BIND/pgdata' já tem um cluster; não sobrescrevo." >&2; exit 11; }

        ensure_postgres "$CID" || { echo "pgdata-adopt: postgres não subiu." >&2; exit 1; }
        SRC="$(pgdata_rel "$CID")"
        volume_has_cluster "$VOLUME" "$SRC" || { echo "pgdata-adopt: o cluster não está no volume '$VOLUME' (bind substituído em runtime); restaure o pg_dump." >&2; exit 12; }

        "${COMPOSE[@]}" stop postgres >/dev/null
        "${ENGINE[@]}" run --rm --network none \
            -v "$VOLUME:/from:ro" -v "$BIND:/to" --entrypoint sh "$IMAGE" \
            -c "mkdir -p /to/pgdata && rm -f /to/pgdata/postmaster.pid && cp -a '/from/$SRC/.' /to/pgdata/ && chown -R postgres:postgres /to/pgdata"
        echo "pgdata-adopt: cluster copiado de '$VOLUME' para '$BIND/pgdata'." >&2
        ;;
esac
